SSM 教程
WebApplicationInitializer
当一个请求到达基于Java的Spring框架时,它会经历以下几个主要部分:
- DispatcherServlet:这是Spring MVC的核心组件,负责接收所有的HTTP请求并分发给相应的处理器。它充当前端控制器,是整个请求处理流程的起点。
- Handler Mapping:这部分负责根据请求URL找到对应的处理器(Controller)。Spring MVC支持多种Handler Mapping实现,如
BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping等。 - Controller:这是实际处理请求的组件。通常,你会在这里编写业务逻辑,处理用户输入,并与模型(Model)交互。
- Model:模型包含业务数据和业务逻辑。通常,模型数据会存储在数据库中,Controller会从模型中获取或更新数据。
- View Resolver:视图解析器负责解析逻辑视图名称到具体视图实现。例如,当Controller返回一个逻辑视图名称时,
View Resolver会将其解析为具体的JSP页面、Thymeleaf模板等。 - View:视图负责渲染模型数据,以呈现给用户。在Spring MVC中,可以使用多种视图技术,如
JSP、Thymeleaf、Freemarker等。
整个处理流程大致如下:
- 用户发送HTTP请求到
DispatcherServlet。 DispatcherServlet将请求发送给合适的Handler Mapping。Handler Mapping找到处理该请求的Controller。DispatcherServlet将请求发送给找到的Controller。Controller处理请求,与Model交互,并返回一个逻辑视图名称。DispatcherServlet将逻辑视图名称发送给View Resolver。View Resolver解析逻辑视图名称为具体的视图实现。DispatcherServlet将模型数据发送给解析后的视图。- 视图渲染模型数据,生成响应内容。
- 视图将响应内容返回给
DispatcherServlet。 DispatcherServlet将响应发送给用户。
这个流程涵盖了Spring MVC的主要组件和它们之间的交互。当然,实际的处理过程可能会根据具体的配置和业务需求有所不同。 generated by 文心一言
课程链接:【尚硅谷】SSM框架全套教程,MyBatis+Spring+SpringMVC+SSM整合一套通关】
MyBatis
1 MyBatis introduction
2 搭建MyBatis
2.1创建MyBatis核心配置文件
文件位置:src/main/resource/mybatis_config.xml MyBatis核心配置文件
习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。 核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息核心配置文件存放的位置是src/main/resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm? serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<package name="mappers/UserMapper.xml"/>
</mappers>
</configuration>2.2 创建 mapper接口
相当于Dao,区别在于mapper仅仅是接口,不需要实现
UserMapper 是一个接口,定义数据库的方法,
public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}2.3 MyBatis映射文件
- 相关概念:ORM(Object Relation Mapping)对象关系映射
- 对象:java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的关系
| Java概念 | 数据库概念 |
|---|---|
| 类 | 表 |
| 属性 | 字段/列 |
| 对象 | 记录/行 |
- 映射文件的命名规则:
表所对应的
实体类的类名+Mapper.xml例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml因此一个映射文件对应一个实体类,对应一张表的操作MyBatis映射文件用于编写SQL,访问以及操作表中的数据MyBatis映射文件存放的位置是src/main/resources/mappers目录下 - MyBatis中可以面向接口操作数据,要保证两个一致:
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
</insert>
</mapper>2.4 通过junit测试功能
//读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
//SqlSession sqlSession = sqlSessionFactory.openSession();
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过代理模式创建UserMapper接口的代理实现类对象 UserMapper
userMapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,
//通过调用的方法名匹配 映射文件中的SQL标签,并执行标签中的SQL语句
int result = userMapper.insertUser();
//sqlSession.commit();
System.out.println("结果:"+result);SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)SqlSessionFactory: 是“生产”SqlSession的“工厂”- 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。
3 核心配置文件详解
核心配置文件中的标签必须按照固定的顺序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
MyBatis核心配置文件中,标签的顺序:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
plugins?,environments?,databaseIdProvider?,mappers?
-->
<!--引入properties文件-->
<properties resource="jdbc.properties"/>
<!--设置类型别名-->
<typeAliases>
<!--
typeAlias:设置某个类型的别名
属性:
type:设置需要设置别名的类型
alias:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,即类名
且不区分大小写
-->
<!--<typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>-->
<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<!--
environments:配置多个连接数据库的环境
属性:
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
environment:配置某个具体的环境
属性:
id:表示连接数据库的环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理方式
属性:
type="JDBC|MANAGED"
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事
务的提交或回滚需要手动处理
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:配置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
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>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssmserverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>4 增删改查
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男')
</insert>
<!--int deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 7
</delete>
<!--int updateUser();-->
<update id="updateUser">
update t_user set username='ybc',password='123' where id = 6
</update>
<!--查询一个实体对象-->
<!--User getUserById();-->
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User">
select * from t_user where id = 2
</select>
<!-- 查询list集合-->
<!--List<User> getUserList();-->
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User">
select * from t_user
</select>注意: 1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射 关系 resultType:自动映射,用于属性名和表中字段名一致的情况 resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
5 获取参数值的两种方式
MyBatis获取参数值的两种方式:${}和#{}
${}的本质就是字符串拼接,#{}的本质就是占位符赋值${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
- 单个字面量类型的参数
若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号 - 多个字面量类型的参数
若mapper接口中的方法参数为多个时
此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1...为键,以参数为值;以param1,param2...为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号 - map集合参数的类型
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过
${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号 - 实体类类型的参数
若mapper接口中的方法参数为实体类对象时此时可以使用
${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号 - 使用
@Param标识参数 可以通过@Param注解标识mapper接口中的方法参数
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以param1,param2...为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
6 各种查询功能
-
查询一个实体类对象
/** * 根据用户id查询用户信息 * @param id * @return */ User getUserById(@Param("id") int id);<!--User getUserById(@Param("id") int id);--> <select id="getUserById" resultType="User"> select * from t_user where id = #{id} </select> -
查询一个list集合
/** * 查询所有用户信息 * @return */ List<User> getUserList();<!--List<User> getUserList();--> <select id="getUserList" resultType="User"> select * from t_user </select> -
查询单个数据
/** * 查询用户的总记录数 * @return * 在MyBatis中,对于Java中常用的类型都设置了类型别名 * 例如: java.lang.Integer-->int|integer * 例如: int-->_int|_integer * 例如: Map-->map,List-->list */ int getCount();<!--int getCount();--> <select id="getCount" resultType="_integer"> select count(id) from t_user </select> -
查询一条数据为map集合
/** * 根据用户id查询用户信息为map集合 * @param id * @return */ Map<String, Object> getUserToMap(@Param("id") int id);<!--Map<String, Object> getUserToMap(@Param("id") int id);--> <!--结果: {password=123456, sex=男 , id=1, age=23, username=admin}--> <select id="getUserToMap" resultType="map"> select * from t_user where id = #{id} </select> -
查询多条数据为map集合
- 方式一
/** * 查询所有用户信息为map集合 * @return * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此 时可以将这些map放在一个list集合中获取 */ List<Map<String, Object>> getAllUserToMap();<!--Map<String, Object> getAllUserToMap();--> <select id="getAllUserToMap" resultType="map"> select * from t_user </select>- 方式二
/** * 查询所有用户信息为map集合 * @return * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的map集合 */ @MapKey("id") Map<String, Object> getAllUserToMap();<!--Map<String, Object> getAllUserToMap();--> <!-- { 1={password=123456, sex=男, id=1, age=23, username=admin}, 2={password=123456, sex=男, id=2, age=23, username=张三}, 3={password=123456, sex=男, id=3, age=23, username=张三} } --> <select id="getAllUserToMap" resultType="map"> select * from t_user </select>
7 特殊SQL的执行
模糊查询
/**
* 测试模糊查询
* @param mohu
* @return
* */
List<User> testMohu(@Param("mohu") String mohu);<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from t_user where username like "%"#{mohu}"%"
</select>批量删除
/**
* 批量删除
* @param ids
* @return
* */
int deleteMore(@Param("ids") String ids);<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>动态设置表名
/**
* 动态设置表名,查询所有的用户信息
* @param tableName
* @return
* */
List<User> getAllUser(@Param("tableName") String tableName);<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
select * from ${tableName}
</select>添加功能获取自增的组件
/**
* 添加用户信息
* @param user
* @return
* useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参 数user对象的某个属性中 */
int insertUser(User user);<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>8 自定义映射resultMap
8.1 resultMap处理字段和属性的映射关系
<!--
resultMap:设置自定义映射
属性:
id:表示自定义映射的唯一标识
type:查询的数据要映射的实体类的类型
子标签:
id:设置主键的映射关系
result:设置普通字段的映射关系
association:设置多对一的映射关系
collection:设置一对多的映射关系
属性:
property:设置映射关系中实体类中的属性名
column:设置映射关系中表中的字段名
-->
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="userName" column="user_name"></result>
<result property="password" column="password"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</resultMap>
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultMap="userMap">
<!--select * from t_user where username like '%${mohu}%'-->
select id,user_name,password,age,sex from t_user where user_name like
concat('%',#{mohu},'%')
</select>8.2 多对一映射处理
8.3 一对多映射处理
9 动态SQL
if
if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行
<!--List<Emp> getEmpListByCondition(Emp emp);-->
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp where 1=1
<if test="ename != '' and ename != null">
and ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</select>where
where和if一般结合使用:
- 若
where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字 - 若
where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
注意:where标签不能去掉条件最后多余的and
<select id="getEmpListByMoreTJ2" resultType="Emp">
select * from t_emp
<where>
<if test="ename != '' and ename != null">
ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</where>
</select>trim
trim用于去掉或添加标签中的内容
常用属性:
prefix:在trim标签中的内容的前面添加某些内容
prefixOverrides:在trim标签中的内容的前面去掉某些内容
suffix:在trim标签中的内容的后面添加某些内容
suffixOverrides:在trim标签中的内容的后面去掉某些内容
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
</if>
<if test="age != '' and age != null">
age = #{age} and
</if>
<if test="sex != '' and sex != null">
sex = #{sex}
</if>
</trim>
</select>choose when otherwise
choose、when、 otherwise相当于if...else if..else
<!--List<Emp> getEmpListByChoose(Emp emp);-->
<select id="getEmpListByChoose" resultType="Emp">
select <include refid="empColumns"></include> from t_emp
<where>
<choose>
<when test="ename != '' and ename != null">
ename = #{ename}
</when>
<when test="age != '' and age != null">
age = #{age}
</when>
<when test="sex != '' and sex != null">
sex = #{sex}
</when>
<when test="email != '' and email != null">
email = #{email}
</when>
</choose>
</where>
</select>foreach
<!--int insertMoreEmp(List<Emp> emps);-->
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
</delete>
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>SQL片段
SQL片段
sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
<sql id="empColumns">
eid,ename,age,sex,did
</sql>
select <include refid="empColumns"></include> from t_emp10 缓存
一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:
-
不同的
SqlSession对应不同的一级缓存 -
同一个
SqlSession但是查询条件不同 -
同一个
SqlSession两次查询期间执行了任何一次增删改操作 -
同一个
SqlSession两次查询期间手动清空了缓存
二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签
<cache/> - 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
二级缓存的相关配置
MyBatis缓存查询的顺序
整合第三方缓存EHCache
11 逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支持正向工程的。 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
创建逆向工程的步骤
-
添加依赖和插件
<!-- 依赖MyBatis核心包 --> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- log4j日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> </dependencies> <!-- 控制Maven在构建过程中相关配置 --> <build> <!-- 构建过程中用到的插件 --> <plugins> <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!-- 插件的依赖 --> <dependencies> <!-- 逆向工程的核心依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> </dependencies> </plugin> </plugins> </build> -
创建MyBatis的核心配置文件
-
创建逆向工程的配置文件,文件名必须是generatorConfig.xml
<?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: 执行生成的逆向工程的版本 MyBatis3Simple: 生成基本的CRUD(清新简洁版) MyBatis3: 生成带条件的CRUD(奢华尊享版) --> <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 数据库的连接信息 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis? serverTimezone=UTC" userId="root" password="123456"> </jdbcConnection> <!-- javaBean的生成策略--> <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- SQL映射文件的生成策略 --> <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- Mapper接口的生成策略 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 逆向分析的表 --> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName --> <!-- domainObjectName属性指定生成出来的实体类的类名 --> <table tableName="t_emp" domainObjectName="Emp" /> <table tableName="t_dept" domainObjectName="Dept" /> </context> </generatorConfiguration> -
执行MBG插件的generate目标
QCB查询
@Test public void testMBG(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询所有数据
/*List<Emp> list = mapper.selectByExample(null);
list.forEach(emp -> System.out.println(emp));*/
//根据条件查询
/*EmpExample example = new EmpExample();
example.createCriteria().andEmpNameEqualTo("张 三").andAgeGreaterThanOrEqualTo(20);
example.or().andDidIsNotNull(); List<Emp> list = mapper.selectByExample(example);
list.forEach(emp -> System.out.println(emp));*/
mapper.updateByPrimaryKeySelective(new Emp(1,"admin",22,null,"456@qq.com",3));
} catch (IOException e) {
e.printStackTrace();
}
}分页插件
pageHelper 添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>配置分页插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>使用
Spring
内容介绍
- 框架概述
- IOC容器
- IOC底层原理
- IOC接口(BeanFactory)
- IOC操作Bean管理(基于XML)
- IOC操作Bean管理(基于注解)
- AOP
- JdbcTemplate
- 事务管理
- Spring5新特性
1. Spring简介
1、Spring 是轻量级的开源的 JavaEE 框架 2、Spring 可以解决企业应用开发的复杂性 3、Spring 有两个核心部分:IOC 和 Aop (1)IOC:控制反转,把创建对象过程交给 Spring 进行管理 (2)Aop:面向切面,不修改源代码进行功能增强 4、Spring 特点 (1)方便解耦,简化开发 (2)Aop 编程支持 (3)方便程序测试 (4)方便和其他框架进行整合 (5)方便进行事务操作 (6)降低 API 开发难度
IOC
IOC概念和原理
思想 IOC 就是一种反转控制的思想, 而 DI(Dependency Injection) 是对 IOC 的一种具体实现。
-
控制反转,把对象创建和对象之间的调用过程,交给spring进行管理
-
使用IOC的目的:为了降低耦合度
IOC容器
IOC思想 IOC:Inversion of Control,翻译过来是反转控制。
IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式(两个接口):
(1) BeanFactory
(2) ApplicationContext
ApplicationContext的主要实现类
| 类型名 | 简介 |
|---|---|
| ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
| FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
| ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
| WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
基于XML管理bean
1、入门案例
-
引入依赖
-
创建类
public class HelloWorld { public void sayHello(){ System.out.println("helloworld"); } } -
创建Spring的配置文件
-
在Spring的配置文件中配置bean
<!-- 配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理 通过bean标签配置IOC容器所管理的bean 属性: id:设置bean的唯一标识 class:设置bean所对应类型的全类名 --> <bean id="helloworld" class="com.atguigu.spring.bean.HelloWorld"></bean> -
创建测试类
@Test
public void testHelloWorld(){
ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
// 获取bean
HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
helloworld.sayHello();
}2、获取bean
- 根据id获取
ac.getBean("helloworld") - 根据类型获取
ac.getBean(HelloWorld.class); - 根据类型和id获取
ac.getBean("helloworld", HelloWorld.class)
3、依赖注入之setter注入
<bean id="studentOne" class="com.atguigu.spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>4、依赖注入之构造器注入
<bean id="studentTwo" class="com.atguigu.spring.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>5、特殊值处理
- null值
<property name="name">
<null />
</property>- xml实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>- CDATA节
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>6、为类类型属性赋值
-
引用外部已声明的bean
<bean id="studentFour" class="com.atguigu.spring.bean.Student"> <property name="id" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="sex" value="女"></property> <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 --> <property name="clazz" ref="clazzOne"></property> </bean> -
内部bean
<bean id="studentFour" class="com.atguigu.spring.bean.Student"> <property name="id" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="sex" value="女"></property> <property name="clazz"> <!-- 在一个bean中再声明一个bean就是内部bean --> <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 --> <bean id="clazzInner" class="com.atguigu.spring.bean.Clazz"> <property name="clazzId" value="2222"></property> <property name="clazzName" value="远大前程班"></property> </bean> </property> </bean> -
级联属性赋值
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>7、为数组类型属性赋值
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
</bean>8、为集合类型属性赋值
-
为List集合类型属性赋值
<bean id="clazzTwo" class="com.atguigu.spring.bean.Clazz"> <property name="clazzId" value="4444"></property> <property name="clazzName" value="Javaee0222"></property> <property name="students"> <list> <ref bean="studentOne"></ref> <ref bean="studentTwo"></ref> <ref bean="studentThree"></ref> </list> </property> </bean> -
为Map集合类型属性赋值
<bean id="teacherOne" class="com.atguigu.spring.bean.Teacher"> <property name="teacherId" value="10010"></property> <property name="teacherName" value="大宝"></property> </bean> <bean id="teacherTwo" class="com.atguigu.spring.bean.Teacher"> <property name="teacherId" value="10086"></property> <property name="teacherName" value="二宝"></property> </bean> <bean id="studentFour" class="com.atguigu.spring.bean.Student"> <property name="id" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="sex" value="女"></property> <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 --> <property name="clazz" ref="clazzOne"></property> <property name="hobbies"> <array> <value>抽烟</value> <value>喝酒</value> <value>烫头</value> </array> </property> <property name="teacherMap"> <map> <entry> <key> <value>10010</value> </key> <ref bean="teacherOne"></ref> </entry> <entry> <key> <value>10086</value> </key> <ref bean="teacherTwo"></ref> </entry> </map> </property> </bean> -
引用集合类型的bean
<!--list集合类型的bean-->
<util:list id="students">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</util:map>
<bean id="clazzTwo" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="4444"></property>
<property name="clazzName" value="Javaee0222"></property>
<property name="students" ref="students"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<property name="teacherMap" ref="teacherMap"></property>
</bean>9、p命名空间
10、引入外部属性文件
-
引入依赖
<!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> -
创建外部属性文件
jdbc.user=root jdbc.password=atguigu jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver -
引入属性文件
<context:property-placeholder location="classpath:jdbc.properties"/> -
配置bean
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean>测试
@Test public void testDataSource() throws SQLException { ApplicationContext ac = new ClassPathXmlApplicationContext("spring datasource.xml"); DataSource dataSource = ac.getBean(DataSource.class); Connection connection = dataSource.getConnection(); System.out.println(connection); }
11、bean的作用域
概念:在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
| 取值 | 含义 | 创建对象时机 |
|---|---|---|
| singleton | 在IOC容器中,这个bean的对象始终为单实例 | IOC容器初始化时 |
| prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 |
12、bean的生命周期
具体的生命周期过程
- bean对象创建
- 给bean对象设置属性
- bean对象初始化之前操作(由bean的后置处理器负责)
- bean对象初始化(需在配置bean是指定初始化方法)
- bean初始化之后操作(由bean的后置处理器负责)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
13、FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是
getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
public class UserFactoryBean implements FactoryBean<User> {
`@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}<bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
@Test
public void testUserFactoryBean(){
//获取IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring
factorybean.xml");
User user = (User) ac.getBean("user");
System.out.println(user);
}14、基于XML的自动装配
配置bean 使用bean标签的autowire属性设置自动装配效果 自动装配: 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类 型属性赋值
1. 自动装配方式:byType
根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值
null
若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
<bean id="userController"
class="com.atguigu.autowire.xml.controller.UserController" autowire="byType">
</bean>
<bean id="userService"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>2. 自动装配方式:byName
将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
<bean id="userController"
class="com.atguigu.autowire.xml.controller.UserController" autowire="byName">
</bean>
<bean id="userService"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userServiceImpl"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl">
</bean>基于注解管理bean
标记与扫描
标识组件的常用注解:
@Component:将类标识为普通组件@Controller:将类标识为控制层组件@Service:将类标识为业务层组件@Repository:将类标识为持久层组件
@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字
扫描组件
<context:component-scan base-package="com.atguigu">
</context:component-scan>指定要排除的组件
<context:component-scan base-package="com.atguigu">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>仅扫描指定组件
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>基于注解的自动装配
-
导入依赖
<dependencies> <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>@Component@Controller@ServiceRepository
-
@Autowired注解
-
通过注解+扫描所配置的
bean的id,默认值为类的小驼峰,即类名的首字母为小写的结果 -
可以通过标识组件的注解的
value属性值设置bean的自定义的id -
@Autowired:实现自动装配功能的注解
@Autowired注解能够标识的位置- a>标识在成员变量上,此时不需要设置成员变量的set方法
- b>标识在set方法上
- c>标识在为当前成员变量赋值的有参构造上
@Autowired注解的原理- 默认通过
byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值 - 若有多个类型匹配的
bean,此时会自动转换为byName的方式实现自动装配的效果,即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值 - 若
byType和byName的方式都无妨实现自动装配,即IOC容器中有多个类型匹配的bean 且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NoUniqueBeanDefinitionException - 此时可以在要赋值的属性上,添加一个注解
@Qualifier通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值
- 默认通过
- 注意:若IOC容器中没有任何一个类型匹配的bean,此时抛出异常:
NoSuchBeanDefinitionException - 在
@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配 - 可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值
AOP
代理模式
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
AOP概念及相关术语
概述:AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
相关术语
- 横向关注点
- 通知
- 切面
- 目标
- 代理
- 连接点
- 切入点 作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
基于注解的AOP
技术说明
- 动态代理(
InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。 cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
注解AOP准备工作
-
添加依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency> -
准备被代理的目标资源
public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }@Component public class CalculatorPureImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int sub(int i, int j) { int result = i - j; System.out.println("方法内部 result = " + result); return result; } @Override public int mul(int i, int j) { int result = i * j; System.out.println("方法内部 result = " + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("方法内部 result = " + result); return result; } } -
创建切面类并配置
// @Aspect表示这个类是一个切面类
// @Component注解保证这个切面类能够放入IOC容器
@Aspect
@Component
public class LogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*
(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参
数:"+args);
}
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
@AfterReturning(value = "execution(*
com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结
果:"+result);
}
@AfterThrowing(value = "execution(*
com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}在spring的配置文件中配置:
<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.atguigu.aop.annotation">
</context:component-scan>
<aop:aspectj-autoproxy />各种通知
- 前置通知:使用
@Before注解标识,在被代理的目标方法前执行 - 返回通知:使用
@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝) - 异常通知:使用
@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命) - 后置通知:使用
@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论) - 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
切入点表达式语法
重用切入表达式
-
声明
@PointCut("excution(* com.atguigu.aop.annotation.*.*(..))") public void pointCut(){} -
在同一个切面中使用
@Before("pointCut()") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); } -
在不同切面中使用
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}获取通知的相关信息
- 获取连接点信息
- 获取目标方法返回值
- 获取目标方法异常
环绕通知
切面的优先级
基于XML的AOP(了解)
声明式事务
JdbcTemplate
声明式事务概念
编程式事务
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
conn.close();
}声明式事务 既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。 封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。 好处1:提高开发效率 好处2:消除了冗余的代码 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性 能等各个方面的优化 所以,我们可以总结下面两个概念: 编程式:自己写代码实现功能 声明式:通过配置让框架实现功能
基于注解的声明式事务
注解声明式事务准备工作
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
加入事务
-
添加事务配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <! 开启事务的注解驱动 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 > <! transaction manager属性的默认值是transactionManager,如果事务管理器bean的id正好就 是这个默认值,则可以省略这个属性 > <tx:annotation driven transaction manager="transactionManager" /> -
添加事务注解 因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在
service层处理 在BookServiceImpl的buybook()添加注解@Transactional
@Transactional注解标识的位置
@Transactional标识在方法上,咋只会影响该方法
@Transactional标识的类上,咋会影响类中所有的方法
事务属性:只读
@Transactional(readOnly = true)
事务属性:超时
@Transactional(timeout = 3)
事务属性:回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。 可以通过@Transactional中相关属性设置回滚策略 rollbackFor属性:需要设置一个Class类型的对象 rollbackForClassName属性:需要设置一个字符串类型的全类名 noRollbackFor属性:需要设置一个Class类型的对象 rollbackFor属性:需要设置一个字符串类型的全类名
事务属性:事务隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 隔离级别一共有四种:
- 读未提交:READ UNCOMMITTED 允许Transaction01读取Transaction02未提交的修改。
- 读已提交:READ COMMITTED、 要求Transaction01只能读取Transaction02已提交的修改。
- 可重复读:REPEATABLE READ 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
- 串行化:SERIALIZABLE 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ UNCOMMITTED)//读未提交@Transactional(isolation = Isolation.READ COMMITTED)//读已提交@Transactional(isolation = Isolation.REPEATABLE READ)//可重复读@Transactional(isolation = Isolation.SERIALIZABLE)//串行化事务属性:事务传播行为
@Transactional(propagation = Propagation.REQUIRED), @Transactional(propagation = Propagation.REQUIRES_NEW)
SpringMVC
什么是MVC
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的JavaBean,作用是处理数据 JavaBean分为两类:
- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
- 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。
V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据 C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程: 用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。
入门案例
web.xml中
- 注册SpringMVC的前端控制器
DispatcherServlet - 配置SpringMVC的前端控制器
DispatcherServletSpringMVC的配置文件默认的位置和名称:
位置:WEB-INF下
名称:<servlet-name>-servlet.xml,当前配置下的配置文件名为SpringMVC-servlet.xmlurl-pattern中/和/*的区别:
/:匹配浏览器向服务器发送的所有请求(不包括.jsp)
/*:匹配浏览器向服务器发送的所有请求(包括.jsp)
jsp文件由tomcat的JspServlet处理 - 配置web.xml文件
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!--设置SpringMVC配置文件的位置和名称-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<url-pattern>标签中使用/和/*的区别:
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径,但是/不能匹配.jsp请求路径的请求因此就可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面
/*则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法
- 创建请求控制器
由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器
请求控制器中每一个处理请求的方法成为控制器方法
因为SpringMVC的控制器由一个POJO(普通的Java类)担任,因此需要通过
@Controller注解将其标识为一个控制层组件,交给Spring的IoC容器管理,此时SpringMVC才能够识别控制器的存在。
@Controller
public class HelloController{
}初始化DispatcherServlet时会读取springMVC配置文件。
SpringMVC的配置文件
<!-- 自动扫描包 -->
<context:component-scan base-package="com.atguigu.mvc.controller"/>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver"
class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>案例总结
浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。
前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。
处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面
@RequestMapping注解
@RequestMapping注解标识的位置@RequestMapping标识一个类:设置映射请求的请求路径的初始信息@RequestMapping标识一个方法:设置映射请求请求路径的具体信息
@RequestMapping注解value属性- 作用:通过请求的请求路径匹配请求
value属性是数组类型,即当前浏览器所发送请求的请求路径匹配value属性中的任何一个值 ,则当前请求就会被注解所标识的方法进行处理
@RequestMapping注解的method属性- 作用:通过请求的请求方式匹配请求
method属性是RequestMethod类型的数组,即当前浏览器所发送请求的请求方式匹配method属性中的任何一中请求方式- 则当前请求就会被注解所标识的方法进行处理
- 若浏览器所发送的请求的请求路径和
@RequestMapping注解value属性匹配,但是请求方式不匹配,此时页面报错:405 - Request method 'xxx' not supported - 在@RequestMapping的基础上,结合请求方式的一些派生注解:
@GetMapping,@PostMapping,@DeleteMapping,@PutMapping
@RequestMapping注解的params属性- 作用:通过请求的请求参数匹配请求,即浏览器发送的请求的请求参数必须满足params属性的设置
params可以使用四种表达式:"param":表示当前所匹配请求的请求参数中必须携带param参数"!param":表示当前所匹配请求的请求参数中一定不能携带param参数"param=value":表示当前所匹配请求的请求参数中必须携带param参数且值必须为value"param!=value":表示当前所匹配请求的请求参数中可以不携带param,若携带值一定不能是value- 若浏览器所发送的请求的请求路径和@RequestMapping注解value属性匹配,但是请求参数不匹配
- 此时页面报错:
400 - Parameter conditions "username" not met for actual request parameters:
@RequestMapping注解的headers属性- 作用:通过请求的请求头信息匹配请求,即浏览器发送的请求的请求头信息必须满足headers属性的设置
- 若浏览器所发送的请求的请求路径和
@RequestMapping注解value属性匹配,但是请求头信息不匹配 - 此时页面报错:404
- SpringMVC支持ant风格的路径
- 在
@RequestMapping注解的value属性值中设置一些特殊字符 ?:任意的单个字符(不包括?)*:任意个数的任意字符(不包括?和/)**:任意层数的任意目录,注意使用方式只能**写在双斜线中,前后不能有任何的其他字符
- 在
- @RequestMapping注解使用路径中的占位符
- 传统:
/deleteUser?id=1 - rest:
/user/delete/1 - 需要在
@RequestMapping注解的value属性中所设置的路径中,使用{xxx}的方式表示路径中的数据 - 在通过
@PathVariable注解,将占位符所标识的值和控制器方法的形参进行绑定
- 传统:
SpringMVC获取请求参数
- 通过servletAPI获取
- 只需要在控制器方法的形参位置设置
HttpServletRequest类型的形参 - 就可以在控制器方法中使用
request对象获取请求参数
- 只需要在控制器方法的形参位置设置
- 通过控制器方法的形参获取
- 只需要在控制器方法的形参位置,设置一个形参,形参的名字和请求参数的名字一致即可
@RequestParam:将请求参数和控制器方法的形参绑定@RequestParam注解的三个属性:value、required、defaultValuevalue:设置和形参绑定的请求参数的名字required:设置是否必须传输value所对应的请求参数- 默认值为true,表示value所对应的请求参数必须传输,否则页面报错:
- 400 - Required String parameter ‘xxx’ is not present
- 若设置为false,则表示value所对应的请求参数不是必须传输,若为传输,则形参值为null
defaultValue:设置当没有传输value所对应的请求参数时,为形参设置的默认值,此时和required属性值无关
@RequestHeader:将请求头信息和控制器方法的形参绑定@CookieValue:将cookie数据和控制器方法的形参绑定- 通过控制器方法的实体类类型的形参获取请求参数
- 需要在控制器方法的形参位置设置实体类类型的形参,要保证实体类中的属性的属性名和请求参数的名字一致
- 可以通过实体类类型的形参获取请求参数
- 解决获取请求此参数的乱码问题
- 在
web.xml中配置Spring的编码过滤器org.springframework.web.filter.CharacterEncondingFilter
- 在
web.xml文件
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filterclass>
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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>域对象共享数据
- 向域对象共享数据:
- 使用ServletAPI向request域对象共享数据
- 通过
ModelAndView向请求域共享数据- 使用
ModelAndView时,可以使用其Model功能向请求域共享数据 - 使用View功能设置逻辑视图,但是控制器方法一定要将
ModelAndView作为方法的返回值
- 使用
- 使用
Model向请求域共享数据 - 使用
ModelMap向请求域共享数据 - 使用
map向请求域共享数据 Model和ModelMap和map的关系- 其实在底层中,这些类型的形参最终都是通过
BindingAwareModelMap创建 public class BindingAwareModelMap extends ExtendedModelMap {}public class ExtendedModelMap extends ModelMap implements Model {}public class ModelMap extends LinkedHashMap<String, Object> {}
- 其实在底层中,这些类型的形参最终都是通过
- 向
session域共享数据
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}- 向
application域共享数据
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}springMVC的视图
SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
当工程引入jstl的依赖,转发视图会自动转换为JstlView
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视 图解析器解析之后所得到的是ThymeleafView
thymeleafView
InternalResourceViewResolver 视图解析器
RedirectView
RESTFUL
REST:Representational State Transfer,表现层资源状态转移。
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter 处理put和delete请求的条件:
- 当前请求的请求方式必须为
post - 当前请求必须传输请求参数
_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为
请求参数_method的值,因此请求参数_method的值才是最终的请求方式
在web.xml中注册HiddenHttpMethodFilter
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding)方法设置字
符集的request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
String paramValue = request.getParameter(this.methodParam);
一个典型的ssm web应用配置的web.xml文件如下 包括编码过滤器,请求方式过滤器,前端控制器
<!--设置Spring的编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--设置处理请求方式的过滤器-->
<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>
<!--设置SpringMVC的前端控制器-->
<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>RESTFUL案例
9. SpringMVC处理ajax请求
-
@RequestBody -
@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
- 9.5、
@RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
10 文件上传和下载
-
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文使用ResponseEntity实现下载文件的功能 -
MultipartFile文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data"SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
-
添加依赖
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> -
在SpringMVC配置文件中添加配置
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> </bean> -
编写控制器方法
/**
* Date:2022/7/9
* Author:ybc
* Description:
* ResponseEntity:可以作为控制器方法的返回值,表示响应到浏览器的完整的响应报文
*
* 文件上传的要求:
* 1、form表单的请求方式必须为post
* 2、form表单必须设置属性enctype="multipart/form-data"
*/
@Controller
public class FileUpAndDownController {
@RequestMapping("/test/up") //上传
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//获取上传的文件的后缀名
String hzName = fileName.substring(fileName.lastIndexOf("."));
//获取uuid
String uuid = UUID.randomUUID().toString();
//拼接一个新的文件名
fileName = uuid + hzName;
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取当前工程下photo目录的真实路径
String photoPath = servletContext.getRealPath("photo");
//创建photoPath所对应的File对象
File file = new File(photoPath);
//判断file所对应目录是否存在
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//上传文件
photo.transferTo(new File(finalPath));
return "success";
}
@RequestMapping("/test/down")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("img");
realPath = realPath + File.separator + "1.jpg";
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组,is.available()获取输入流所对应文件的字节数
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.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);
//关闭输入流
is.close();
return responseEntity;
}
}11 拦截器
拦截器的配置
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/testRequestEntity"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->拦截器的三个抽象方法
SpringMVC中的拦截器有三个抽象方法:
preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
postHandle:控制器方法执行之后执行postHandle()
afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
多个拦截器执行顺序
①若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterCompletion()会按照配置的反序执行
②若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterCompletion()会执行
12 异常处理器
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和
SimpleMappingExceptionResolver
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式:
13 注解配置springMVC
14 springMVC 执行流程
14.1、SpringMVC常用组件
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求HandlerMapping:处理器映射器,不需要工程师开发,由框架提供 作用:根据请求的url、method等信息查找Handler,即控制器方法Handler:处理器,需要工程师开发 作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供 作用:通过HandlerAdapter对处理器(控制器方法)进行执行ViewResolver:视图解析器,不需要工程师开发,由框架提供 作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectViewView:视图 作用:将模型数据通过页面展示给用户
14.2、DispatcherServlet初始化过程
P174 https://www.bilibili.com/video/BV1Ya411S7aT/?p=174 javax.servlet.Servlet
DispatcherServlet 本质上是一个Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet生命周期来进行调度。(从javax.servlet.Servlet的init方法开始向下看)
- 初始化
WebApplicationContext所在类:org.springframework.web.servlet.FrameworkServlet#initWebApplicationContextwebApplicationContext web中的ioc容器 - 创建
WebApplicationContext所在类:org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext - DispatcherServlet初始化策略
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件 所在类:org.springframework.web.servlet.DispatcherServlet#initStrategies
14.3、DispatcherServlet调用组件处理请求
p175 https://www.bilibili.com/video/BV1Ya411S7aT/?p=175 同样也是根据Serlet的生命周期去看,从Servlet的service方法开始。
-
org.springframework.web.servlet.FrameworkServlet#processRequest -
org.springframework.web.servlet.DispatcherServlet#doService -
org.springframework.web.servlet.DispatcherServlet#doDispatch -
“org.springframework.web.servlet.DispatcherServlet#processDispatchResult`
14.4、SpringMVC的执行流程
-
用户向服务器发送请求,请求被
SpringMVC前端控制器DispatcherServlet捕获。 -
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射: a) 不存在 i. 再判断是否配置了mvc:default-servlet-handlerii. 如果没配置,则控制台报映射查找不到,客户端展示404错误 iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误 b) 存在则执行下面的流程 -
根据该URI,调用
HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以 Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。 -
DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。 -
如果成功获得
HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】 -
提取
Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作: a)HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息 b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等 c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等 d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中 -
Handler执行完成后,向
DispatcherServlet返回一个ModelAndView对象。 -
此时将开始执行拦截器的
postHandle(...)方法【逆向】。 -
根据返回的
ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。 -
渲染视图完毕执行拦截器的
afterCompletion(…)方法【逆向】。 -
将渲染结果返回给客户端
SSM整合
spring 配置文件默认位置和名称:
/WEB-INF/applicationContext.xml
可通过上下文自定义spring配置文件的位置和名称