尚硅谷SSM框架笔记
MyBatis
MyBatis特性
- MyBatis是客制化SQL、存储过程和高级映射的持久层框架
- MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- MyBatis使用简单的xml或注解用于配置和原始映射,将接口或者Java对象映射为数据库中的记录
- MyBatis是一个半自动的ORM框架
使用MyBatis
数据库连接
MySql版本不同的差异
- MySql 5版本使用 com.mysql.jdbc.Driver
- MySql 5版本连接Url为:jdbc:mysql://localhost:3306/database
- MySql 8版本使用 com.mysql.cj.jdbc.Driver
- MySql 8版本连接Url为:jdbc:mysql://localhost:3306/database?serverTimezone=UTC
MyBatis核心配置
配置文件放在Maven工程的Resources目录下
MyBatis配置文件中的标签必须按照 properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers 的顺序去配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
引入properties配置文件 通过#{key}的形式访问value
-->
<properties resource="jdbc.properties"/>
<!--
设置别名 在MyBatis的作用范围中都可用
-->
<typeAliases>
<!--
type:源内容
alias:设置后的别名 如果不设置alias属性,那么默认的别名就是类名且不区分大小写
设置完成后使用 user 即可访问源内容
-->
<typeAlias type="com.xinnn.mybasis.pojo.User" alias="user"/>
<!--
以包的形式设置别名 包下的所以类都会拥有默认的别名
-->
<package name="com.xinnn.mybasis.pojo"/>
</typeAliases>
<!--
配置连接数据库的环境
default和子标签的id对应 表示默认使用
-->
<environments default="development">
<!--
连接数据库的配置 id是唯一标识
-->
<environment id="development">
<!--
设置事务管理器 type属性用于设置事务的管理方式
JDBC:使用原生的事务管理
MANAGED:被管理 Spring
-->
<transactionManager type="JDBC"/>
<!--
连接的数据源 type属性设置数据源类型
POOLED:使用数据库连接池
UNPOOLED:不使用数据库连接池
JNDI:使用上下文中的数据源
-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!-- 单个映射文件的引入 -->
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
<!--
以包的形式引入映射文件 映射文件所在的包要个mapper接口的包一致
映射文件的名字要和对应的mapper接口的名字一致
-->
<package name="com.xinnn.mybatis.mapper"/>
</mappers>
</configuration>
|
创建mapper接口
mapper接口类似DAO层,但是不需要实现方法
1
2
3
|
public interface UserMapper{
List<User> getUserList();
}
|
创建映射文件
ORM(Object Relationship Mapping)对象关系映射
对象指Java的实体对象,关系指关系型数据库,映射指二者之间的对应关系
映射文件的命名规则为实体类的类名 + Mapper.xml,存放路径还是在Maven工程的配置文件目录下
mapper包中接口的全类名要和映射文件中的 namespace 属性一致
mapper接口中的方法名要和映射文件中SQL标签的 id 属性一致
1
2
3
4
5
6
7
8
9
10
|
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xinnn.mybatis.mapper">
<!--resultType表示返回的是一个实体类-->
<select id="getUserList" resultType="User">
select * from ssm_user
</select>
</mapper>
|
log4j日志功能
日志的级别
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
FATAL只记录致命信息 DEBUG记录全部信息
MyBatis获取参数值
MyBatis获取参数值有两种方式 ${} 和 #{}
${}采用的是字符串拼接,遇到字符串类型或者日期时间类型时需要手动添加单引号
#{}采用的是占位符方式,会自动添加单引号并防止SQL注入
-
若mapper接口方法的参数为单个时,获取数据时可以通过任意名称
-
若mapper接口方法的参数为多个时,mybatis会将其放在一个map集合中,可以用两种方式获取
arg0,arg1…为键,参数为值
param1,param2…为键,参数为值
-
若mapper接口方法的参数为map集合时,只需要访问其中的键即可获取对应的值
-
若mapper接口方法的参数是一个实体类类型时,通过访问类中的属性名即可获取相应的值
类中设置器和访问器去掉get``set后将首字母小写后的名字就是属性名,类中没有这个参数,但是有get方法时也能获取到属性
-
在mapper接口的方法参数上设置@param注解,mybatis此时会以两种方式将参数存储在map集合中
以@param注解的value为键,参数为值
以param1,param2为键,参数为值
1
2
3
4
5
|
public interface UserMapper{
public getUserByUsername(@param("username")String username){
}
}
|
MyBatis查询
查询语句如果需要返回一个实体类时,需要在select标签里面加上resultType属性。属性值就是需要返回的类的类名。
1
2
3
|
<select id="getUserById" resultType="User">
select * from ssm_user where id = #{id}
</select>
|
若需要返回多条结果时,不能以实体类作为返回值,否则会抛出异常。
需要查询多条结果并返回为一个Map集合时,需要加上@MapKey注解,定义一个字段作为键,其他的字段作为值。
也可以将单个的map集合放在一个List集合里面,两种方法都能实现需求。
1
2
3
4
5
6
|
//使用id作为map的键 所以查询结果都存储在一个map里面
@MapKey("id")
Map<String, Object> getUserToMap(){}
//返回为一个List集合
List<Map<String, Object> getUserToMap(){}
|
MyBatis处理特殊SQL语句
MyBatis处理特殊SQL语句时,因为 #{} 会自动为里面的值加上单引号,可能会报错。有三种解决办法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-- ${}的本质就是字符串拼接
select * from ssm_user where username like '%${value}%';
--使用sql语句的字符串拼接
select * from ssm_user where username like concat('%',#{value},'%');
-- 把需要拼接的字符串写成双引号
select * from ssm_user where username like "%"#{value}"%";
-- 处理需要批量删除的场景也需要使用 ${}
delete from ssm_user where id in (${value});
-- 需要动态设置表名时也需要使用 ${} 表名不能有单引号
select * from ${tableName};
|
MyBatis获取添加后的自增id
当传入的参数为一个实体类时,MyBatis可以获取向数据库中插入这条数据后,获取所在行数的自增ID并向传入的参数自动赋值。
1
2
3
4
5
6
7
8
9
|
<!-- 执行完插入语句后 user的id属性会被自动赋值 -->
void insertUser(User user);
<!--
useGeneratedKeys: 表示启用了自增的主键
keyProperty:需要把自增的主键赋值给哪一个属性
-->
<inster id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into ssm_user values(#{username}...)
</inster>
|
自定义映射resultMap
当数据库中的字段和Java中实体类中的属性名不一致时,需要去处理它们的映射关系
-
查询数据库时为字段设置别名,别名与实体类的属性一致
-
开始MyBatis的自动驼峰命名映射,把字段中的下划线映射为驼峰。需要在MyBatis的核心文件中进行配置
1
2
3
4
5
6
|
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--
student_id > studentId,student_name > studentName
-->
|
-
使用resultMap标签实现自定义映射
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!--
id属性是唯一的 type表示实体类
id用来处理主键 result处理普通字段
column表示数据库中的字段名 propetry表示需要映射到的实体类中的属性名
select标签需要把resultMap属性设置为resuleMap标签的id
-->
<resultMap id="empMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
</resultMap>
<select id="getEmpList" resultMap="empMap">
select * from ssm_emp
</select>
|
-
使用级联方式处理多对一映射关系(类中包含其他实体类)
1
2
3
4
5
6
|
<!--
Emp类中有一个dept的属性为实体类 Dept类中有deptId和deptName两个属性
使用多表连接查询
-->
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
|
-
使用association标签处理多对一映射关系(association标签处理实体类类型的属性)(多表查询)
1
2
3
4
|
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
|
-
使用collection标签处理一对多映射关系(处理集合属性)(多表查询)
1
2
3
4
5
6
7
8
9
|
<!--
ofType:集合中的实体类类型
-->
<collection property="empList" ofType="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
</collection>
|
-
使用分步查询,先获取主要信息,再通过关联获取其他信息
分布查询支持延迟加载,需要调用时才会去执行对应的SQL语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!--
fetchType:在设置了全局延迟加载的环境下设置当前的分布查询是否使用延迟加载
eager:立即加载 lazy:延迟加载
-->
<!--
多对一使用分步查询
先获取员工信息 再获取员工所关联的的部门信息
将dept_id字段的值作为参数去调用select标签中的方法
-->
<association property="dept" fetchType="eager"
select="com.xinnn.mybatis.mapper.DeptMapper.getDeptByDeptId"
column="dept_id"/>
<!--
一对多使用分步查询
先获取部门信息 再通过部门id获取所对应的员工集合
-->
<collection property="empList"
select="com.xinnn.mybatis.mapper.EmpMapper.getEmpListByDeptId"
column="dept_id"/>
|
延迟加载
MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式延迟加载与深度延迟加载。
- 直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询
- 侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询
- 深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询
1
2
3
4
5
6
|
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--侵入式延迟加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
|
动态SQL
- 通过if标签test属性中的表达式来判断if标签内的SQL语句是否拼接。
- where标签可以通过if标签的判断结果来自动生成where关键字,并且去除内容前的and和or关键字。
-
- prefix 在标签中内容前面添加指定内容
- suffix 在标签中内容后面添加指定内容
- prefixOverrides 在标签中内容前面去掉指定内容BaseResultMap
- choose,when,otherwise 相当于 if,else if,else
1
2
3
4
5
6
7
8
9
10
|
<where>
<choose>
<when test="name != null and name != ''"> <!--当name条件匹配时,后面的when语句不会被执行-->
name = #{name}
</when>
<when test="age != null and age != ''">
age = #{age}
</when>
</choose>
</where>
|
- 循环标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<!--
循环插入数据
collection 需要循环的数据
item 循环的临时变量
separator 分隔符 每次循环后添加分隔符
-->
<insert id="">
insert into ssm_emp values
<foreach collection="empList" item="emp" separator=",">
<!-- 循环生成指定数据 -->
(null,#{emp.empName},#{emp.age},#{emp.gender},null)
</foreach>
</insert>
<!--
批量删除数据
open 以指定内容开始
close 以指定内容接受
-->
<delete id="delEmpByEmpId">
delete from ssm_emp where emp_id in
<foreach collection="empIds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>
|
- 定义一段sql语句 在需要的地方使用include引用
1
2
3
4
|
<sql id="empColumns">
emp_id,enp_name,age,gender.dept_id
</sql>
select <include refid="empColumns"></include> from ssm_emp
|
MyBatis缓存
一级缓存
一级缓存是SqlSession级别的,SqlSession查询的数据会被缓存,通过同一SqlSession查询相同的数据时,会直接从缓存里面获取。
缓存失效的情况:
- 使用不同的SqlSeeion
- 同一SqlSession但查询条件不同
- 两次查询期间对数据进行了增删改操作,也会使二级缓存失效
- 两次查询期间手动清空了缓存
二级缓存
二级缓存是SqlSessionFactory级别的,通过同一SqlSessionFactory创建的SqlSession查询的数据都会被缓存,只要是当前SqlSessionFactory所创建的SqlSession再次获取该数据,就会从二级缓存里面获取。
二级缓存开启的条件:
- 核心配置文件设置全局属性
cacheEnable=true,默认为true
- Mapper映射文件中添加
<cache/>标签
- 二级缓存只在sqlsession提交或者关闭后有效
- 查询数据的实体类型必须实现序列化接口
配置二级缓存,
<cache/>标签的属性
eviction: 缓存回收策略。默认LRU
LRU 优先移除最长时间没有使用的
FIFO 按进入缓存的顺序进行移除
SOFT 移除基于缓存回收器状态和软引用规则的对象
WEAK 更积极的移除基于缓存回收器状态和软引用规则的对象
flushlnterval: 刷新间隔,单位毫秒。默认没有
size: 引用数目,正整数。代表缓存最多存储多少个对象
readOnly: 只读 true/false 默认为false
true 只读缓存,返回缓存对象的实例
false 读写缓存,通过序列化返回缓存对象的副本
缓存查询顺序
先查询二级缓存,如二级缓存没有命中再查询一级缓存,若一级缓存也没命中,则直接查询数据库。
MyBatis逆向工程
通过数据库表自动生产对应的实体类和Mapper接口,需要使用maven插件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<!-- 修改pom.xml文件 添加如下字段 -->
<!-- 构建过程执行的操作 -->
<build>
<plugins>
<!-- 具体插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<!-- 需要的依赖 -->
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
|
添加generatorConfig.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 生产的逆向工程的版本
MyBatis3: 完整版
MyBatis3Slmple: 只包含基本的CRUD
-->
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://rm-2vc0319r4esx4w7l71o.mysql.cn-chengdu.rds.aliyuncs.com/demo?serverTimezone=UTC"
userId="lisang"
password="Cola00000">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成POJO类的位置 -->
<javaModelGenerator targetPackage="com.xinnn.mybatis.pojo" targetProject="./src/main/java">
<!-- 是否开启子包名 -->
<property name="enableSubPackages" value="true" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.xinnn.mybatis.mapper"
targetProject="./src/main/resources">
<!-- 是否开启子包名 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.xinnn.mybatis.mapper"
targetProject="./src/main/java">
<!-- 是否开启子包名 -->
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 指定数据库表和生成的实体类名-->
<table tableName="ssm_emp" domainObjectName="Emp"/>
<table tableName="ssm_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
|
分页插件
MyBatis分页使用插件pagehelper实现,需要在核心配置文件中添加插件。
1
2
3
4
|
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
|
分页功能的使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//开启分页 pageNum当前页码 pageSize每一页的条数
//返回的page对象会有与分页相关的数据 Mapper的查询操作需要紧跟在开启分页后面
Page<Object> page = PageHelper.startPage(int pageNum, int pageSize);
List<Scenic> scenicList = ScenicMapper.getScenicList();
//可以使用PageInfo来获取分页的详细信息
PageInfo<Scenic> pageInfo = new PageInfo(scenicList, 3); //3表示导航分页的页码数
/**
pageInfo的属性解释:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
*/
|
Spring
IOC 控制反转
IOC: Inversion of Control 控制反转
DI: 依赖注入
BeanFactory
IOC容器的基本实现,Spring内部接口
ApplicationContext
BeanFactory的子接口,面向使用者提供,下面还有具体的实现类。
| 类名 |
简介 |
| ClassPathApplicationContext |
根据类路径下的XML配置文件创建IOC容器 |
| FileSystemApplicationContext |
根据磁盘路径的XML配置文件创建IOC容器 |
| ConfigurableApplicationContext |
扩展方法refresh() 和 close() ,让 ApplicationContext具有启动、关闭和刷新上下文的能力。 |
| WebApplicationContext |
为 Web 应用准备,基于Web环境创建IOC容器对象,并将对象引入存入ServletContext 域中 |
XML管理Bean
创建Spring XML配置文件
1
2
3
4
|
<!--
id:唯一标识 class:类路径
-->
<bean id="student" class="com.xinnn.spring.poji.Studnet"></bean>
|
在类中使用IOC容器创建的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//通过类路径下的XML配置文件创建IOC容器
ApplicationContext application = new ClassPathApplicationContext("applicationContext.xml");
//IOC容器创建对象是通过反射调用bean标签中class属性的无参构造方法
//通过name获取对象 (bean标签id属性)
Student stu = (Student) application.getBean("student");
//通过bean类型获取对象
//如果配置文件中此类型的bean标签不止一个,抛出异常:NoUniqueBean
//若没有匹配的bean标签,抛出异常:NoSuchBean
Student stu = application.getBean(Student.class);
//通过bean的id和类型获取对象
Student stu = application.getBean('student', Student.class);
//如果一个类实现了接口,可以通过接口类型获取bean,但是接口实现类的bean要唯一。
|
依赖注入
setter和构造器注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<bean id="student" class="com.xinnn.spring.poji.Studnet">
<!--
property:通过成员变量的set方法进行赋值
name:类的属性 value:值 ref:其他bean的引用
-->
<property name="id" value="1"/>
<property name="name" value="jony"/>
<property name="age" value="18"/>
</bean>
<bean id="student" class="com.xinnn.spring.poji.Studnet">
<!--
constructor-arg:通过类的构造方法进行赋值 有几个参数就调用几个参数的构造方法
name:参数的名字 value:参数的值
-->
<constructor-arg name="id" value="1"/>
</bean>
|
为类属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<!--
使用ref属性引用IOC容器中的某个bean
-->
<bean id="student" class="com.xinnn.spring.pojo.Student">
<property name="book" ref="book"/>
</bean>
<bean id="book" class="com.xinnn.spring.pojo.book"></bean>
<!--
使用级联方式赋值
-->
<bean id="student" class="com.xinnn.spring.pojo.Student">
<property name="book" ref="book"/>
<!-- 级联方式赋值 需要先实例化book属性 -->
<property name="book.id" value="1"/>
</bean>
<!--
使用内部bean
-->
<bean id="student" class="com.xinnn.spring.pojo.Student">
<property name="book">
<bean id="book" class="com.xinnn.spring.pojo.Book">
<property name="book.id" value="1"/>
</bean>
</property>
</bean>
|
为数组和集合赋值
标签内部赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<bean id="student" class="com.xinnn.spring.pojo.Student">
<!-- 字面量类型的数组 集合把array标签替换为list标签 -->
<property name="stars">
<array>
<value>11</value>
<value>22</value>
</array>
</property>
<!-- 类类型的集合 -->
<property name="books">
<list>
<ref bean="book"/>
</list>
</property>
</bean>
<!-- 为map属性赋值 -->
<bean id="student" class="com.xinnn.spring.pojo.Student">
<property name="books" ref="bookList">
<map>
<!-- 字面量类型可以支持赋值 -->
<entry key="" value=""/>
<!-- 也可以引用其他bean -->
<entry key-ref="" value-ref=""/>
</map>
</property>
</bean>
|
引用式,需要util约束
1
2
3
4
5
6
7
|
<bean id="student" class="com.xinnn.spring.pojo.Student">
<property name="books" ref="bookList">
</bean>
<util:list id="bookList">
<ref bean="book"/>
</util:list>
|
引入外部属性文件(properties)
由Spring来管理数据源,引入jdbc.propertis和Druid
1
2
3
4
5
6
7
8
9
|
<!-- 外部约束 加载properties配置文件 -->
<context:property-placeholder location="database.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 使用${key} 的形式访问配置文件中的值-->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
|
bean的作用域
默认为单例模式,每次获取的都是同一个对象,可以通过scope属性切换为多例模式
1
2
3
4
5
|
<!--
prototype: 多例模式
singleton:单例模式
-->
<bean id="student" class="com.xinnn.spring.pojo.Student" scope="prototype"></bean>
|
bean的生命周期
- 实例化
- 依赖注入
- 初始化
- 销毁
初始化和销毁可以通过bean标签的init-method和destory-method属性来设置需要执行的操作
bean对象会在ioc容器关闭时销毁
当bean的生命周期为单例时,生命周期的前三个步骤会在获取IOC容器时执行
为多例时,生命周期的前三个步骤会在获取bean对象时执行 并且关闭ioc容器并不会销魂bean对象
后置处理器,初始化时额外的操作
需要实现BeanPostProcessor接口,并将其配置在ioc容器中,ioc中的所有bean在初始化时都会执行。
1
2
3
4
5
6
7
8
9
10
11
12
|
public class MyBeanPro implements BeanPostProcessor {
//初始化之前的方法
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean初始化之前");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
//初始化之后的方法
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean初始化之后");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
|
如果配置了后置处理器,那么此时生命周期就变成了6个步骤
- 实例化
- 依赖注入
- 初始化之前
- 初始化
- 初始化之后
- 销毁
FactoryBean
通过工程模式创建bean,需要实现类
1
2
3
4
5
6
7
8
9
10
11
12
|
public class StudentFactoryBean implements FactoryBean<Student> {
//ioc容器管理的是这里返回的对象
public Student getObject() throws Exception {
return new Student();
}
public Class<?> getObjectType() {
return Student.class;
}
}
//<bean class="com.xinnn.spring.pojo.factory.StudentFactoryBean"></bean>
//获取通过工厂模式创建的bean
Student stu = applicationContext.getBean(Student.class);
|
自动装配
根据指定的策略,在IOC容器中匹配某个bean,自动为bean中类类型和接口类型的属性赋值,标签中的autowire属性表示自动装配策略
1
2
3
4
5
6
7
8
9
10
|
<!--
autowire:
default,no:不自动装配
byType:根据bean中属性的类型来匹配IOC中的bean类型并自动赋值
1.若没有一个匹配的bean则使用默认值
2.若匹配到的bean有多个会抛出异常
byName:根据bena中的属性名来匹配IOC中bean的id并自动赋值
1.若没有一个匹配的bean则使用默认值
-->
<bean id="userController" class="com.xinnn.spring.controller.UserController" autowire="byType"></bean>
|
基于注解管理bean
标记组件的注解:
- @Component 将类标记为普通组件
- @Controller 将类标记为控制层组件
- @Service 将类标记为业务层组件
- @Repositry 将类标记为持久层组件
这4个注解在功能上没有区别,后三个注解只是在@Component上做了扩展,方法程序阅读。
使用注解后,需要在配置文件中配置扫描组件
1
2
3
4
|
//通过注解配置的bean的id默认值为类名的小驼峰 默认的id为userController
//可以在注解后面加上value属性来自定义id
@Controller("controller")
public class UserController(){ }//此时id为controller
|
1
2
|
<!-- 定义组件扫描的包名-->
<context:component-scan base-package="com.xinnn.spring"/>
|
自定义扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<context:component-scan base-package="com.xinnn.spring">
<!--
context:exclude-filter:排除扫描
当一个类或者注解被排除扫描后,便不能再通过IOC去获取
type:annotation 基于注解排除扫描 expression:排除的注解的全类名
type:assignable 基于类排除扫描 expression:排除的类的全类名
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="com.xinnn.spring.controller.UserController"/>
<!--
context:exclude-filter:包含扫描 只扫描知道的注解或类 需要在context:component-scan标签中配置 use-default-filters="false" 属性
type:annotation 基于注解包含扫描 expression:需要扫描的注解的全类名
type:assignable 基于类包含扫描 expression:需要扫描的类的全类名
-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="com.xinnn.spring.controller.UserController"/>
</context:component-scan>
|
注解自动装载
使用@Autowired注解实现自动装载,默认通过byType的方式,在IOC容器中去匹配bean。如果有多个类型匹配的bean,会自动转换为byName的方式实现自动装载。注解可以标识在成员变量、set方法和为成员变量赋值的有参构造上。
如果同类型的bean有多个并且id匹配不上会抛出异常,此时可以加上@Qualifier("id")注解去匹配指定id的bean并自动装载。
代理模式
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。比如把日志功能放在代理对象中,目标对象只有实现核心业务的功能。
静态代理
静态代理就是再声明一个静态代理类,实现与目标对象同样的接口,把非核心业务的功能都放在静态代理类中来实现,再让静态代理类方法调用目标对象的方法。
1
2
3
4
5
6
7
|
public int add(int i, int y) {
System.out.println("日志,方法:add,参数" + i + "," + y);
//调用目标对象的方法
int result = target.add(i, y);
System.out.println("日志,方法执行后,结果" + result);
return result;
}
|
动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class ProxyFactory {
//目标类
private Object target;
//初始化目标类
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxy(){
/**
newProxyInstance():创建一个代理实例
其中有三个参数:
1、classLoader:加载动态生成的代理类的类加载器
2、interfaces:目标对象实现的所有接口
3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
*/
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] classes = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
try{
System.out.println("日志,方法" + method.getName() + "参数:" + Arrays.toString(objects));
//执行目标对象的方法
Object result = method.invoke(target, objects);
System.out.println("日志,方法执行后,结构:" + result);
return result;
}catch (Exception e){
e.printStackTrace();
System.out.println("日志,方法报错,异常:" + e);
return null;
}finally {
System.out.println("方法执行完毕");
}
}
} ;
return Proxy.newProxyInstance(classLoader, classes, invocationHandler);
}
}
|
AOP
AOP: AspectvOriented Programming 面向切面编程
AOP的作用主要有两点:
- 把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
- 把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
AOP术语
- 横切关注点:代码被抽取的位置,需要抽取10个位置的代码,那么就有10个横切关注点
- 通知:每一个横切关注点上被抽取的代码需要写一个方法来实现,这个方法就叫通知方法。
- 前置通知:在被代理的目标方法前执行(方法体之前)
- 返回通知:在被代理的目标方法返回语句成功执行后执行(try语句块)
- 异常通知:在被代理的目标方法异常技术后执行(catch语句块执行完毕)
- 后置通知:在被代理的目标方法执行结束后执行(方法体执行完毕或finally语句块执行完毕)
- 环绕通知:使用try…catch…finally语句围绕整个被代理的方法,对应上面的位置
- 切面:封装通知方法的类,根据不同的业务逻辑,创建不同的切面类
- 目标:被代理的目标对象
- 代理:向目标对象插入通知方法后创建的代理对象
- 连接点:把类中的方法体横向排开,方法中需要插入通知方法的横切关注点,就可以当做是一个连接点。
- 切入点:定位连接点的方式,通过切入点,可以将通知方法精准的插入到目标方法的指定位置。
动态代理:JDK原生的实现方式,被代理的类必须实现接口,代理类也同样实现这个接口。
cglib:代理类继承被代理类,不需要实现接口
Aspectl:本质是静态代理,通过weaver将通知方法织入到目标方法编译得到的class文件内。
基于注解的AOP实现
加入需要的依赖
1
2
3
4
5
6
7
8
9
|
<dependency>
<groupId>nz.net.osnz.composite</groupId>
<artifactId>composite-spring-aspect</artifactId>
<version>4.1.1</version>
</dependency>
<!--
Spring的配置文件里面加上 <aop:aspectj-autoproxy/>
-->
|
- @Aspect:声明这是一个切面类
- @Pointcut:切入点表达式重用
- @Before:声明这是一个前置通知
- @After:后置通知
- @AfterReturning:返回通知
- @AfterThrowing:异常通知
- @Around:环绕通知
具体实现(想要实现AOP,必须将切面类和目标类交由IOC容器管理)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Component //声明这是一个组件
@Aspect //声明这是一个切面
public class LoggerAspect {
//前置通知
//value值为固定写法 execution后面写方法的修饰符 返回值 和方法在包里面的全名以及参数类型
//可以用*号来表示任意修饰符、返回值的方法和包里面的任意类,类里面的任意方法
//可以用..表示任意的方法参数
//execution(* com.xinnn.spring.anntation.CalculatorImpl.*(..))
@Before("execution(public int com.xinnn.spring.anntation.CalculatorImpl.add(int, int))")
public void beforeMethod(){
System.out.println("前置通知");
}
}
|
重用切入点表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//使用注解声明一个表达式传递给一个方法
@Pointcut("execution(* com.xinnn.spring.anntation.CalculatorImpl.*(..))")
public void pointcut(){};
//需要使用表达式的时候调用方法即可
@Before("pointcut()")
public void beforeMethod(){
System.out.println("前置通知");
}
@After("pointcut()")
public void afterMethod(){
System.out.println("后置通知");
}
|
获取连接点参数
1
2
3
4
5
6
7
8
|
@Before("pointcut()")
public void beforeMethod(JoinPoint joinPoint){
//获取目标方法的签名信息
Signature signature = joinPoint.getSignature();
//获取目标方法的方法参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:"+signature.getName()+"参数:"+ Arrays.toString(args));
}
|
获取目标方法的返回值、异常
1
2
3
4
5
6
7
8
9
10
11
12
|
//获取目标方法的返回值 使用returning参数 参数为要接收目标方法返回值的参数名
@AfterReturning(value = "pointcut()", returning = "result")
//方法参数需要和AfterReturning注解的returning参数重名
public void afterReturningMethod(Object result){
System.out.println("方法返回值:" + result);
}
//获取目标方法抛出的异常 使用throwing参数 参数为要接收目标方法所抛出异常的参数名
@AfterThrowing(value = "pointcut()", throwing = "e")
//方法参数需要和AfterThrowing注解的throwing参数重名
public void afterThrowing(Exception e){
System.out.println("方法异常:" + e);
}
|
环绕通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//环绕通知相当于对前四个通知进行了整合 更接近于JDK原生的动态反射
@Around("pointcut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try{
System.out.println("环绕通知,前置通知");
//执行目标对象的方法并获取返回值
result = joinPoint.proceed();
System.out.println("环绕通知,返回通知");
}catch (Throwable e){
e.printStackTrace();
System.out.println("环绕通知,异常通知");
}finally {
System.out.println("环绕通知,后置通知");
}
return result;
}
|
切面的优先级
通过@Order注解设置优先级,value值默认为Integer的最大值,值越小优先级越高。
1
2
3
4
|
@Component
@Aspect
@Order(1)
public class LoggerAspect {}
|
声明式事务
在不使用声明式事务时,所有事务的操作全部需要我们受到编写代码实现,存在以下缺陷
- 细节没有被屏蔽:具体操作过程中,所有细节都需要自己手动完成
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要编写代码,代码就没有得到复用。
使用声明式事务可以将固定模式的代码抽取出来,进行相关的封装。在Spring中,只需要简单配置即可。
- 提高开发效率
- 消除冗余代码
- 框架在健壮性和性能方面有优化
基于注解使用声明式事务
配置步骤:
- Spring配置文件配置事务管理器
- 配置开始事务的的注解驱动
- 在需要事务管理的方法或者类上,添加
@Transactional注解即可。若添加在类上,会对类中的所以方法都进行事务管理。
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--
事务管理器的bean只需要装配数据源,其他属性保持默认值即可
数据源需要自己配置
-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启基于注解的声明式事务功能 -->
<!-- 使用transaction-manager属性指定当前使用是事务管理器的bean -->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
|
1
2
3
|
@Service
@Transactional9801
public class BookServiceImpl implements BookService
|
事务属性
- readOnly = true:将当前事务设为只读操作,设计到增删改操作时,会抛出异常。
- timeout = xx:设置事务的超时时间,程序运行超出了指定时间时,强制回滚关闭资源。
- rollbackFor = Xxx.class:设置需要回滚的异常,默认为所有的运行时异常都回滚。
- rollbackForClassName:全类名设置需要回滚的异常
- noRollbackFor:不回滚事务的异常
- noRollbackForClassName
- isolation:设置事务隔离级别
- DEFAULT:使用数据库默认级别
- READ_COMMITTED:读提交
- READ_UNCOMMITTED:读未提交
- REPEATABLE_READ:可重复读
- SERIALIZABLE:序列化
- propagation:事务传播行为
- REQUIRED:默认值,如果当前线程上已经开启了事务,就使用已经开启的事务。若没有,则开启一个新的事务。
- REQUIRES_NEW:始终开启一个新的事务来运行
1
2
3
4
5
6
7
8
9
10
|
@Transactional(
//开启只读操作
readOnly = true,
//设置超时时间为5秒
timeout = 5,
//数字转换异常和文件异常不影响事务的提交
noRollbackFor = {NumberFormatException.class, FileSystemException.class},
isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED
)
|
基于XML管理事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<!--需要设置事务的方法和事务属性,方法可以使用通配符-->
<tx:method name="buyBook" read-only="false"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置事务通知需要插入到哪 -->
<aop:advisor advice-ref="transactionInterceptor" pointcut="execution(* com.xinnn.spring.service.*.*(..))"/>
</aop:config>
|
SpringMVC
SpringMVC是Spring为表述层开发提供的一套完备的解决方案
表述层框架需要解决的问题:
- 请求映射
- 数据输入
- 视图界面
- 请求分发
- 表单回显
- 会话控制
- 过滤拦截
- 异步交互
- 文件上传
- 文件下载
- 数据校验
- 类型转换
使用SpringMVC
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<dependencies>
<!--测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--SpringMVC核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.2</version>
</dependency>
<!--thymeleaf日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--Spring整合thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
|
-
修改web.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化参数-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--自定义配置文件路径-->
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!--将初始化时机设置为服务器启动时-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
|
-
添加Spring配置文件,如果不自定义配置文件路径的话,默认配置文件在WEB-INF目录下-servlet.xml,这是SpringMVC配置文件的默认形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- SpringMVC-servlet.xml -->
<!-- 自动扫描包 -->
<context:component-scan base-package="com.atguigu.mvc.handler"/>
<!-- 整合Thymeleaf视图解析器 -->
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!--页面所在位置-->
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".html"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver"/>
</bean>
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
|
-
向客户端渲染返回页面
1
2
3
4
5
6
7
|
@Controller
public class HelloController {
@RequestMapping("/")
public String index(){
return "index";
}
}
|
@RequestMapping注解
@RequestMapping注解的标识位置
- 标识在类上,表示映射请求路径的初始信息
- 表示在方法上,表示映射请求路径的具体信息
1
2
3
4
5
6
7
|
@RequestMapping("/user")
public class UserController{
@RequestMapping("/login")
public String login(){
}
}
//此时login方法的访问路径为 /user/login
|
@RequestMapping注解的属性
- value:通过客户端请求的请求路径来匹配请求,value属性是一个数组类型,可以设置多个请求路径。路径支持ant风格。
- ?:表示任意的单个字符
- *:表示0个或多个任意字符
- **:表示任意层数的目录,需要写在双斜线中
- method:设置请求方式,数组类型,可以设置多个。在请求路径匹配的情况下再来匹配请求方式
GET, POST, HEAD, PUT, DELETE, PATCH, TRACE, OPTIONS
衍生出的派生注解:
- @GetMapping
- @PostMapping
- @DeleteMapping
- @PutMapping
- params:通过请求参数来匹配,数组类型,参数必须同时满足
- headers:通过请求的请求头信息来匹配,数组类型,参数必须同时满足
1
2
3
4
5
|
@RequestMapping(
value = "/login",
method = RequestMethod.GET,
params = "username"
)
|
使用路径中的占位符
可以在@RequestMapping注解中使用{value}表示一个占位符,然后通过@PathVariable注解获取占位符的值。
1
2
3
4
5
|
@RequestMapping(value = "/login/{id}")
public String login(@PathVariable("id") Integer id){
System.out.println(id);
return "login";
}
|
获取请求参数
使用ServletAPI获取
使用HttpServletRequest、HttpServletResponse、HttpServletSession
通过控制器方法的形参获取
- 控制器方法设置和请求参数同名的形参,SpringMVC会自动为其赋值。
1
2
|
@RequestMapping("/user/login")
public String login(Stirng username, String password){}
|
- 使用
@RequestParam注解获取请求参数
- name:请求参数的名称
- required:是否必须
- defaultValue:如果没有传参,设置默认值
1
2
|
//注解会将userName的值赋给username
public String login(@RequestParam(value = "userName", required = true) String username){}
|
- 获取请求头信息和Cookie数据,使用方法和
@RequestParam一样
- @RequestHeader:
- @CookieValue
- 通过实体类类型的参数获取数据,实体类的属性名需要和请求参数同名
1
|
public String login(User user){}
|
设置请求参数编码格式
修改web.xml文件,添加SpringMVC自带的编码过滤器。编码过滤器要配置在其他过滤器前,不然无效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--设置编码格式-->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!--为响应请求也开启编码-->
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
|
域数据共享
请求域数据共享
- Model
- ModelMap
- Map
- ModeAndView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//作于域仅在一次请求中有效 Model和MaodeMap的使用方法一致
public String login(Model model){
model.addAttribute("msg", "hello")
}
//Map就是原生的map使用方法,需要自定义泛型
//ModelAndView相当于把Model和View进行了整合,使用这个保存数据时,方法的返回值必须是ModelAndView
public ModelAndView index(ModelAndView modelAndView){
//保存数据
modelAndView.addObject("msg", "hello");
//设置需要渲染的视图
modelAndView.setViewName("index");
return modelAndView;
}
//SpringMVC的底层会把每一次请求需要保存的数据和渲染的视图都转换为一个ModelAndView对象
|
会话域和应用域数据共享
使用Servlet原生的HttpSession和ApplicationContext
SpringMVC视图
SpringMVC中的视图是View接口,视图的作用是渲染数据,展示给用户。SpringMVC默认有转发视图和重定向视图,如果在配置文件中配置了thymeleaf视图解析器的话,由此视图解析器解析之后得到的就是thymeleafView
- ThymeleafView:当控制方法返回的视图名称没有任何前缀时,此时的视图会被配置文件中配置是视图解析器解析,拼接上前缀和后缀,渲染数据展示给用户。
- 转发视图:默认的转发视图是InternalResourceView。当控制器方法返回的视图名称以
forward作为前缀时,会创建InternalResourceView视图,将前缀forward去掉,剩余部分作为路径通过转发实现跳转。(Servlet的内部转发)
- 重定向视图:默认的重定向视图是RedirectView,以前缀
redirect开头时,会将前缀去掉,剩下部分作为路径,让客户端重新请求这个地址。(Servlet的重定向)
MVC视图控制器
如果控制器方法中没有逻辑处理,只是单独的返回视图名称时,就可以使用视图控制器来设置一个路径的视图名称,不再需要单独的编写一个控制器方法。
1
2
3
4
5
6
7
8
|
<!--开启MVC注解驱动-->
<mvc:annotation-driven/>
<!--
视图控制器 需要设置路径和视图名称
启用视图控制器后,只有视图控制器所配置的请求路近会被处理 其他请求全部404
所以需要开启mvc的注解驱动
-->
<mvc:view-controller path="/" view-name="index"/>
|
使用RESTFUl
REST:表现层资源状态转移。REST规范倡导使用URL对应网络上的各种资源,任何一个资源都可以通过一个URL访问,REST倡导针对资源本身操作。
REST规范要求
- 四种请求方式
| 操作 |
请求方式 |
| 查询操作 |
GET |
| 保存操作 |
POST |
| 更新操作 |
PUT |
| 删除操作 |
DELETE |
- URL地址风格:REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。
传统URL地址:/user/delete?id=1 GET请求
REST风格地址:/user/1 DELETE请求
四种请求方式映射
由于REST使用不同的请求方式来表示不同的操作,程序只需要使用一个统一的URL即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//对应查询操作 也可以使用派生注解 @GetMapping
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser(){}
//根据id获取当个用户信息
@GetMapping("/user/{id}")
public String getUserById(@PathVariable("id") Integer id){}
//保存
@PostMapping("/user")
//更新
@PutMapping("/user")
//删除
@DeleteMapping("/user/@{id}")
|
由于浏览器只能发出GET、POST请求,在需要发出PUT或DELETE请求时,浏览器还是发出POST请求,然后通过MVC的HiddenHttpMethodFilter过滤器进行转换。过滤器通过获取请求参数中_method的值来判断到底是什么请求,并进行转换。
1
2
3
4
5
|
<form action="/user" method="post">
<!-- 转换请求的参数 name必须为_method value为需要转换的请求方式 -->
<input type="hidden" name="_method" value="put">
<input type="submit" value="修改用户信息">
</form>
|
配置HiddenHttpMethodFilter过滤器
1
2
3
4
5
6
7
8
|
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
|
处理静态资源
在配置DispatcherServlet的时候,默认让它接管所有的请求,当需要获取服务器的静态资源的时候,DispatcherServlet也会去控制器里面找相应的路径,这时候就会报404错误。如果需要访问静态资源,需要使用Servlet默认的控制器去处理,处理不了的请求再丢给DispatcherServlet。
1
2
3
4
|
<!--必须启用mvc注解驱动-->
<mvc:annotation-driven/>
<!--启用servlet默认的控制器-->
<mvc:default-servlet-handler/>
|
SpringMVC处理Json请求
接受Json请求
使用@RequestBody获取请求体,请求体可以被转换为字符串,实体类和Map键值对。需要转换为实体类和Map键值对时,需要引入第三方处理Json的jar包,SpringMVC原生整合了Gson和Jackson,只需要引入依赖即可。
1
2
3
4
5
6
7
8
9
|
@RequestMapping("/ajax/test")
//请求体的值会自动赋值给 requestBody
public String testAjax(@RequestBody String requestBody){}
//引入处理Json的Jar包
//将请求体参数转换为实体类
public String testAjax(@RequestBody User user){}
//将请求体参数转换为Map集合
public String testAjax(@RequestBody Map<String, Object> map){}
|
返回Json数据
引入处理Json的Jar包后,需要向客户端返回Json格式的数据时,只需要使用@ResponseBody注解。并将需要返回的实体类、Map集合、List集合设置为方法的返回值即可。
1
2
3
4
5
6
7
8
9
|
@RequestMapping("/ajax/test")
@ResponseBody
public List<User> testAjax(){
User user1 = new User(1, "admin", "admin", 18);
User user2 = new User(2, "admin", "admin", 18);
List<User> userList = Arrays.asList(user1, user2);
return userList;
//userList会被转换为Json数组,并作为响应体响应给客户端
}
|
当一个控制器类中所有的方法都需要@ResponseBody注解时,可以直接给类使用@RestController注解。相当于给类添加了@Controller注解,给类中所有方法添加了@ResponseBody注解。
文件上传下载
SpringMVC封装好了实现文件上传和下载的方法。
文件下载
使用ResponseEntity实现文件下载,需要控制器方法的返回值是ResponseEntity类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public ResponseEntity<byte[]> fileDown(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器存储文件的目录
String realPath = servletContext.getRealPath("img");
//获取文件 File.separato表示路径分割符
String fileName = File.separator + "1.png";
realPath = realPath + fileName;
//创建输入流
InputStream inputStream = new FileInputStream(realPath);
//获取输入流的字节长度并创建输入流
byte[] bytes = new byte[inputStream.available()];
//将流读到字节数组中
inputStream.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
inputStream.close();
return responseEntity;
}
|
文件上传
- 引用
commons-fileupload依赖
- 修改SpringMVC配置文件,加入文件上传解析器到IOC容器中。
1
2
|
<!--此处id必须为 multipartResolver 否则SpringMVC无法获取-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
|
- 前端提交文件的form表单提交方式必须为post,
enctype属性必须设置为multipart/form-data,以二进制的方式上传。
- 控制器方法需要
MultipartFile类型的参数,参数名必须和form表单中file标签的name属性重名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public String fileUpload(MultipartFile video, HttpSession session) throws IOException{
//获取上传文件的存放路径
String filePath = session.getServletContext().getRealPath("upload");
File file = new File(filePath);
//判断目录是否存在 不存在则创建
if(!file.exists()){
file.mkdir();
}
//获取上传文件的文件名
String fileName = video.getOriginalFilename();
//文件的最终路径
String finalPath = filePath + File.separator + fileName;
System.out.println(finalPath);
//转存
video.transferTo(new File(finalPath));
return "success";
}
|
文件上传重命名
不对文件进行重命名的话,有重名文件上传时文件会被覆盖。
1
2
3
4
5
6
|
//获取上传文件的文件名
String fileName = video.getOriginalFilename();
//获取文件后缀
String fileHz = fileName.substring(fileName.lastIndexOf("."));
//生成UUID并去掉其中的 - ,再做为文件名
fileName = UUID.randomUUID().toString().replaceAll("-", "") + fileHz;
|
SpingMVC拦截器
拦截器作用相当于Servlet原生的过滤器。都是对请求执行拦截、过滤和放行。
| 名称 |
工作平台 |
过滤范围 |
IOC支持 |
| 过滤器 |
Servlet容器 |
整个Web应用 |
需要调用专门的工具方法 |
| 拦截器 |
SpringMVC |
整个SpringMVC的请求 |
完整支持 |
功能需要如果能用拦截器实现,就不使用过滤器。
使用拦截器
拦截器类需要实现HandlerInterceptor接口,接口中有三个方法。
- preHandle():控制器方法执行前运行
- postHandle():控制器方法执行后运行
- afterCompletion():视图渲染完后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Component
public class loginInterceptor implements HandlerInterceptor{
//返回true表示放行 返回false表示拦截
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//通过session来判断用户是否登录 存在则放行 不存在则重定向到登录页并拦截请求
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
if (username != null){
return true;
}else{
response.sendRedirect("/login");
return false;
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
|
注册拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<!--
添加拦截器配置之前一定要启用SpringMVC的注解驱动
修改SpringMVC配置文件 添加拦截器配置
-->
<mvc:interceptors>
<!--声明一个拦截器 可配置多个-->
<mvc:interceptor>
<!--拦截路径 *表示一层目录 **表示任意层目录-->
<mvc:mapping path="/*"/>
<!--需要排除的路径-->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/sign"/>
<mvc:exclude-mapping path="/user/login"/>
<!--引用拦截器类-->
<ref bean="loginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
<!--简单写法 拦截所有路径的请求-->
<mvc:interceptors>
<ref bean="loginInterceptor"/>
</mvc:interceptors>
|
拦截器的执行顺序
- preHandle() 按照配置文件所配置的顺序执行,某一个preHandle()方法返回false后不会继续执行
- 目标handler()方法 当有一个preHandle()方法返回为false时不会被执行
- postHandle() 按照配置文件配置的相反顺序执行 当有一个preHandle()方法的返回值为false时,都不会执行
- afterCompletion() 按照配置文件配置的相反顺序执行,只会执行到preHandle()方法返回false的前一个
异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
基于XML的异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<!--自定义异常映射类-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
key表示可能出现的异常名称
值表示需要返回的视图名称,并跳转到该页面
-->
<prop key="java.io.IOException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性可以设置一个属性名 将异常信息共享在请求域内
-->
<property name="exceptionAttribute" value="ex"/>
</bean>
|
基于注解的异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//表示这是一个异常处理类
@ControllerAdvice
public class MyExceptionHandler {
//标识方法处理的异常 可以设置多个
@ExceptionHandler(IOException.class)
//ex 表示发生的异常信息
public String handlerIOException(Exception ex, Model model){
//向请求域中添加异常信息
model.addAttribute("ex", ex);
//跳转到指定视图
return "error";
}
}
|
SSM整合
Spring整合SpringMVC
Spring整合SpringMVC时,会出现两个IOC容器,SpringMVC的IOC容器管理控制层(Controller)和它需要的组件,Spring管理其他组件。同时SpirngMVC的IOC容器是SpringIOC容器的子容器,子容器可以随意访问父容器中的组件。Spring提供了ContextLoaderListener监听器,用于在创建Tomcat容器时初始化Spring IOC容器。
1
2
3
4
5
6
7
8
9
10
11
|
<!--web.xml-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!--
Spring的配置文件位置
-->
<param-value>classpath:Spring.xml</param-value>
</context-param>
|
Spring整合Mybatis
在Spring的配置文件中配置Mybatis,并使用bean来管理SqlSessionFactory和其相关的组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<!--SpringMVC.xml-->
<context:component-scan base-package="com.xinnn.spring">
<!--排除扫描Controller层-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--引入jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置druid数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--使用bean管理sqlSession-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--POJO包 用于实体类映射-->
<property name="typeAliasesPackage" value="com.xinnn.spring.pojo"/>
<!--Mapper配置文件路径-->
<property name="mapperLocations" value="classpath:mappers/*.xml"/>
<!--全局配置-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<!--开启驼峰映射-->
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
<!--配置插件-->
<property name="plugins">
<!--配置分页插件-->
<bean class="com.github.pagehelper.PageInterceptor"/>
</property>
</bean>
<!--为mapper接口通过sqlsession提供实现-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--需要扫描的mapper接口包-->
<property name="basePackage" value="com.xinnn.spring.mapper"/>
</bean>
|