一、什么是SSM框架?
SSM框架是spring、spring MVC 、和mybatis框架的整合,是标准的MVC模式。标准的SSM框架有四层,分别是dao层(mapper),service层,controller层和View层。使用spring实现业务对象管理,使用spring MVC负责请求的转发和视图管理,mybatis作为数据对象的持久化引擎。
1)持久层:dao层(mapper)层
作用:主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。
- Dao层首先设计的是接口,然后再Spring的配置文件中定义接口的实现类。
- 然后可以在模块中进行接口的调用来进行数据业务的处理。(不在关心接口的实现类是哪个类)
- 数据源的配置以及有关数据库连接的参数都在Spring的配置文件中进行配置。
2)业务层:Service层
作用:Service层主要负责业务模块的逻辑应用设计。
- 先设计接口然后再设计实类,然后再在Spring的配置文件中配置其实现的关联。(业务逻辑层的实现具体要调用到自己已经定义好的Dao的接口上)这样就可以在应用中调用Service接口来进行业务处理。
- 建立好Dao之后再建立service层,service层又要在controller层之下,因为既要调用Dao层的接口又要提供接口给controller层。每个模型都有一个service接口,每个接口分别封装各自的业务处理的方法。
3)表现层:Controller层(Handler层)
作用:负责具体的业务模块流程的控制。
- 配置也同样是在Spring的配置文件里面进行,
- 调用Service层提供的接口来控制业务流程。
- 业务流程的不同会有不同的控制器,在具体的开发中可以将我们的流程进行抽象的归纳,设计出可以重复利用的子单元流程模块。
4)View层
作用:主要和控制层紧密结合,主要负责前台jsp页面的表示。
各层之间的联系
这里是引用
DAO层,Service层这两个层次都可以单独开发,互相的耦合度很低,完全可以独立进行,这样的一种模式在开发大项目的过程中尤其有优势,Controller,View层因为耦合度比较高,因而要结合在一起开发,但是也可以看作一个整体独立于前两个层进行开发。这样,在层与层之前我们只需要知道接口的定义,调用接口即可完成所需要的逻辑单元应用,一切显得非常清晰简单。
MyBatis
mybatis 中文官网
http://www.mybatis.org/mybatis-3/zh/getting-started.html
MyBatis特性
- MyBatis是支持定制化SQL,存储过程以及高级映射的优秀持久层框架
- MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects)映射成数据库中的记录
- MyBatis是一个半自动的ORM(Object Relation Mapping)框架
MyBatis开发环境
MYSQL不同版本的注意事项
- 驱动类driver-class-name
MySQL5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver
MySQL8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver
- 连接地址url
MySQL 5版本的url:
jdbc:mysql://localhost:3306/数据库名称
MySQL 8版本的url:
jdbc:mysql://localhost:3306/数据库名称?serverTimezone=UTC
未添加时区(?serverTimezone=UTC)会出现一下异常
java.sql.SQLException: The server time zone value ÖD’ú±êx14E+14a is unrecognized or represents more
创建实体类
package com.test.pojo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户对象
*/
@Data
public class User {
/**编号*/
private Integer id;
/**用户名*/
private String username;
/**密码*/
private String userPassword;
/**创建时间*/
private LocalDateTime createDate;
/**修改时间*/
private LocalDateTime updateDate;
}
创建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>
<!--
MyBatis核心配置文件中的标签必须要按照指定顺序配置
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
-->
<!--引入jdbc.properties文件-->
<properties resource="jdbc.properties"/>
<!--
typeAliases:设置类型别名,为某个具体的类型设置一个别名
在MyBatis的范围中,就可以使用别名表示一个具体的类型
-->
<typeAliases>
<!--
type:设置需要起别名的类型
alias:设置别名名称(如果设置type没有设置alias当前的类型默认别名是类名)
-->
<!--<typeAlias type="com.test.pojo.User" alias="user"></typeAlias>-->
<!--
通过包设置类型别名,指定包下所有的类型拥有别名
-->
<package name="com.test.pojo"/>
</typeAliases>
<!-- 默认使用的环境 ID(比如:default="development")也就是说我们可以配置多套<environment>环境-->
<environments default="development">
<!-- 每个 environment 元素定义的环境 ID-->
<environment id="development">
<!--
transactionManager 事务管理器
type的值有JDBC和MANAGED
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。-->
<transactionManager type="JDBC"/>
<!--
dataSourcedataSource 数据源 dbcp c3p0 druid
type="[UNPOOLED|POOLED|JNDI]"
POOLED意思有连接池的连接
UNPOOLED意思没有连接池的连接
-->
<dataSource type="POOLED">
<!--JDBC 驱动-->
<property name="driver" value="${jdbc.driver}"/>
<!--url数据库的 JDBC URL地址。-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--defaultTransactionIsolationLevel – 默认的连接事务隔离级别。–>-->
<!--<property name="defaultTransactionIsolationLevel" value=""/>-->
<!--defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)–>-->
<!--<property name="efaultNetworkTimeout" value=""/>-->
</dataSource>
</environment>
</environments>
<!--映入mybatis映射标签-->
<mappers>
<!--<mapper resource="com/test/mapper/UserMapper.xml" />-->
<!--
以包的方式引入映射文件,但是必须满足两个条件:
1、mapper接口和映射文件所在的包必须一致
2、mapper接口的名字和映射文件的名字必须一致
-->
<package name="com.test.mapper"/>
</mappers>
</configuration>
创建mapper接口
MyBatis
package com.test.mapper;
import com.test.pojo.User;
import java.util.List;
/**
* 用户Mapper
*/
public interface UserMapper {
/**
* 查询用户
* @return 用户数组
*/
List<User> selectUserAll();
}
<?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.test.mapper.UserMapper">
<resultMap id="userResult" type="com.test.pojo.User">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="userPassword" column="user_password"/>
<result property="createDate" column="create_date"/>
<result property="updateDate" column="update_date"/>
</resultMap>
<sql id="selectUser">
id,username,user_password,create_date,update_date
</sql>
<select id="selectUserAll" resultMap="userResult">
SELECT <include refid="selectUser"/> FROM sys_user
</select>
</mapper>
基础使用MyBatis
package com.test.mapper;
import com.test.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MainTest {
/**
* MyBatis基础配置
* @return 获取操作数据库对象(sqlSession)
*/
public SqlSession getSqlSession(){
//获取核心配置文件流
try(InputStream is = Resources.getResourceAsStream("mybatis-config.xml");) {
//获取sqlSessionFactoryBuilder对象(MyBatis工厂)
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//SqlSessionFactory 对象
SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
/*
* 获取SQL的会话对象,SqlSession(自动提交事务),是MyBatis提供的操作数据库对象
* SqlSession sqlSession = build.openSession(true);
* */
//获取SQL的会话对象,SqlSession(不会自动提交事务),是MyBatis提供的操作数据库对象
SqlSession sqlSession = build.openSession();
return sqlSession;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 测试查询
* 提示:
* 如果进行增、删、改操作需要手动提交事务:sqlSession.commit()
*/
@Test
public void queryUser(){
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectUserAll();
System.out.println("users = " + users);
}
}
结果
users = [User(id=1, username=ljw, userPassword=admin, createDate=2023-06-01T09:52:29, updateDate=2023-06-01T09:53:41)]
Log4j
依赖
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置
配置文件名称必须叫:Log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-4r [%t] %-5p %c %x - %m%n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug"/>
</logger>
<logger name="org.apache.ibatis">
<level value="info"/>
</logger>
<root>
<priority value="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
日志级别
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
从左到右打印内容越来越详细
MyBatis获取参数的两种方式
MyBatis获取参数值的两种方式:${}和#{}
${}的本质就是字符串拼接,#{}的本质就是占位符赋值
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;
#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。#{}占位赋值可以避免sql注入
添加功能获取自增的主键
该功能不是MyBatis的功能它是JDBC中的
JDBC实现获取自增主键
public void testJDBC(){
try {
Class.forName("");
Connection connection = DriverManager.getConnection("", "", "");
String sql = "insert into sys_user values ()";
/*
* Statement.RETURN_GENERATED_KEYS允许获取自动递增的主键
* connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
* 如果connection.prepareStatement(sql);JDBC是不允许直接获自动递增的主键的
* */
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.executeUpdate();
ResultSet resultSet = ps.getGeneratedKeys();
resultSet.next();
/*因为添加只有单行单列所有getInt(1)*/
int id = resultSet.getInt(1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
MyBatis实现获取自增主键
Mapper.xml
<!--
useGeneratedKeys="true" 便是当前添加功能使用了自增的主键
keyProperty=”属性“ :将添加的数据的自增主键为实体类类型的参数的属性赋值
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO sys_user
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="username != null">username,</if>
<if test="userPassword != null">user_password,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="username != null">#{username},</if>
<if test="userPassword != null">#{userPassword},</if>
</trim>
</insert>
Test.java测试
@Test
public void addUser(){
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("Nick");
user.setUserPassword("admin");
Integer v = userMapper.insertUser(user);
System.out.println("user = " + user);
}
日志结果
0 [main] DEBUG com.test.mapper.UserMapper.insertUser - ==> Preparing: INSERT INTO sys_user ( username, user_password ) values ( ?, ? )
28 [main] DEBUG com.test.mapper.UserMapper.insertUser - ==> Parameters: Nick(String), admin(String)
32 [main] DEBUG com.test.mapper.UserMapper.insertUser - <== Updates: 1
user = User(id=3, username=Nick, userPassword=admin, createDate=null, updateDate=null)
多对一association
用户对象
package com.test.pojo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户对象
*/
@Data
public class User {
/**编号*/
private Integer id;
/**用户名*/
private String username;
/**密码*/
private String userPassword;
/**创建时间*/
private LocalDateTime createDate;
/**修改时间*/
private LocalDateTime updateDate;
/**部门Id*/
private Integer deptId;
}
部门对象
package com.test.pojo;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* sys_dept
* @author
*/
@Data
public class Dept implements Serializable {
/**
* 编号
*/
private Integer id;
/**
* 部门名称
*/
private String deptName;
/**
* 创建时间
*/
private Date createDate;
/**
* 修改时间
*/
private Date updateDate;
private static final long serialVersionUID = 1L;
}
用户Vo
package com.test.pojo.vo;
import com.test.pojo.Dept;
import com.test.pojo.User;
import lombok.Data;
/**
* 用户Vo
*/
@Data
public class UserVo extends User {
private Dept dept;
}
映射Mapper
<resultMap id="userInnerDept" type="com.test.pojo.vo.UserVo">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="userPassword" column="user_password"/>
<result property="createDate" column="create_date"/>
<result property="updateDate" column="update_date"/>
<association property="dept" javaType="com.test.pojo.Dept">
<id property="id" column="id"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
<select id="associationQueryUser" resultMap="userInnerDept">
SELECT * FROM sys_user AS A INNER JOIN sys_dept AS B ON A.dept_id = B.id
</select>
测试输出
package com.test.mapper;
import com.test.pojo.Dept;
import com.test.pojo.User;
import com.test.pojo.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.List;
public class MainTest {
/**
* MyBatis基础配置
* @return 获取操作数据库对象(sqlSession)
*/
public SqlSession getSqlSession(){
//获取核心配置文件流
try(InputStream is = Resources.getResourceAsStream("mybatis-config.xml");) {
//获取sqlSessionFactoryBuilder对象(MyBatis工厂)
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//SqlSessionFactory 对象
SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
/*
* 获取SQL的会话对象,SqlSession(自动提交事务),是MyBatis提供的操作数据库对象
* */
SqlSession sqlSession = build.openSession(true);
/*获取SQL的会话对象,SqlSession(不会自动提交事务),是MyBatis提供的操作数据库对象
SqlSession sqlSession = build.openSession();*/
return sqlSession;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 测试部门查询
*/
@Test
public void queryDept(){
SqlSession sqlSession = getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.selectByPrimaryKey(1);
System.out.println("dept = " + dept);
}
/**
* 多对一链表查询
*/
@Test
public void queryUserInnerDept(){
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<UserVo> vos = mapper.associationQueryUser();
System.out.println("vos = " + vos);
}
}
日志
0 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Preparing: SELECT * FROM sys_user AS A INNER JOIN sys_dept AS B ON A.dept_id = B.id
24 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Parameters:
59 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - <== Total: 3
vos = [User(id=1, username=ljw, userPassword=admin, createDate=2023-06-01T09:52:29, updateDate=2023-06-06T15:32:40, deptId=null)Dept(id=1, deptName=运营部门, createDate=null, updateDate=null), User(id=2, username=Nick, userPassword=admin, createDate=2023-06-06T13:56:23, updateDate=2023-06-06T15:32:41, deptId=null)Dept(id=2, deptName=运营部门, createDate=null, updateDate=null), User(id=3, username=admin, userPassword=admin, createDate=2023-06-06T14:12:02, updateDate=2023-06-08T21:32:18, deptId=null)Dept(id=3, deptName=运营部门, createDate=null, updateDate=null)]
分步查询
<sql id="selectUser">
SELECT id,
username,
user_password,
create_date,
update_date,
dept_id
FROM sys_user
</sql>
<resultMap id="userDept" type="com.test.pojo.vo.UserVo">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="userPassword" column="user_password"/>
<result property="createDate" column="create_date"/>
<result property="updateDate" column="update_date"/>
<result property="deptId" column="dept_id"/>
<association property="dept" select="com.test.mapper.DeptMapper.selectByPrimaryKey" column="dept_id"></association>
</resultMap>
<select id="associationQueryUser" resultMap="userDept">
<include refid="selectUser"></include>
</select>
延迟加载
lazyLoadingEnabled
<setting name="lazyLoadingEnabled" value="true"/>
延迟加载是配合分步查询的,在查询中如果配置了延迟加载并且调用了分步查询的Mapper查询。
如果未指定获取类中的信息,分步查询将会执行两个Sql
日志:
0 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Preparing: SELECT id, username, user_password, create_date, update_date, dept_id FROM sys_user
32 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Parameters:
90 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - <== Total: 3
91 [main] DEBUG com.test.mapper.DeptMapper.selectByPrimaryKey - ==> Preparing: select id , dept_name, create_date, update_date from sys_dept where id = ?
91 [main] DEBUG com.test.mapper.DeptMapper.selectByPrimaryKey - ==> Parameters: 1(Integer)
93 [main] DEBUG com.test.mapper.DeptMapper.selectByPrimaryKey - <== Total: 1
如果指定他将只执行被执行的Sql,因此它可以增强性能
@Test
public void queryUserDept(){
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<UserVo> vos = mapper.associationQueryUser();
List<String> collect = vos.stream().map(v -> {
return v.getUsername();//指定获取User中的userName属性
}).collect(Collectors.toList());
System.out.println("collect = " + collect);
}
日志:
0 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Preparing: SELECT id, username, user_password, create_date, update_date, dept_id FROM sys_user
34 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - ==> Parameters:
97 [main] DEBUG com.test.mapper.UserMapper.associationQueryUser - <== Total: 3
collect = [ljw, Nick, admin]
延迟加载是由两个配置所控制的
-
<!--开启延迟加载--> <setting name="lazyLoadingEnabled" value="true"/>
-
<!--按需加载--> <setting name="aggressiveLazyLoading" value="false"/>
一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
使以及缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空缓存(session.clearCache)
二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同查询语句,结果就会从缓存中获取。
二级缓存开启条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中配置标签
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型实现序列化的接口使二级缓存失效的情况。
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。
二级缓存的相关配置
在Mapper配置文件中添加的cache标签可以设置一些属性:
- eviction属性:缓存回收策略,默认的是LRU。
- LRU(Least Recently Used) 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT-软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEA-弱引用:更积极地移除基于垃圾回收器状态和弱引用规则的对象。
- flushlnterval属性:刷新间隔,单位毫秒
默认情况是不设置也就是没有刷新间隔,缓存仅仅调用语句时刷新。
- size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象相同实例。因此这些对象不能被修改。者提供了很重要的性能优势。
false:读写缓存;会返回缓存对象拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
MyBatis缓存查询顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
分页插件
分页需要数据
Limit index,pageSize
pageSize:每页显示条数(提前设置)
pageNum:当前页的页码(前端提供)
index:当前页的起始索引 index=(pageNum-1)*pageSize
count:总条数
totalPage:总页数
totalPage=count/pageSize
if(count%pageSize != 0){ totlaPage+=1 }
分页插件的使用步骤
- 添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<plugins>
<!--配置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
-
在查询功能之前使用PageHelper.startPage(int pageNum,int pageSize)开启分页功能
- pageNum:当前页的页码
- pageSize:每页显示条数
-
在查询获取list集合之后,在使用PageInfo pageInfo = new PageInfo<>(List list,int navigatePages)获取分页相关数据
- list:分页之后的数据
- navigatePages:导航分页的页码数
-
分页相关数据
PageInfo{pageNum=1, pageSize=10, size=10, startRow=1, endRow=10, total=127, pages=13, list=Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=10, total=127, pages=13, reasonable=false, pageSizeZero=false}
prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}
-
PageInfo
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真是条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码[1,2,3,4,5]
Spring
Spring概述
官网:https://spring.io/
Spring是最受欢迎的企业级Java应用开发框架,数以百万的来自世界各地的开发人员使用Spring框架来创建性能好(IOC /AOP)、易于测试、可重用的代码。
Spring框架是一个开源的Java平台,他最初是由Rod Johnson编写,并且于2003年6月首次在Apache2.0许可发布。
Spring是轻量级的框架,其基础版本只有2MB左右的大小。
Spring框架的核心特性是可以用于开发任何Java应用程序,但是在JavaEE平台上构建web应用程序时需要扩展的。Spring框架的目标是使J2EE开发变得更容易使用通过启用POJO编程模型来促进良好变成实践。
Spring家族
项目列表:https://spring.io/projects
Spring Framework
Spring基础框架,可以视为Spring基础设施,基本上任何其他Spring项目都是以Spring Framework 为基础的。
Spring Framework特性
- 非侵入试:使用Spring Framework开发应用程序时,Spring对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进行简化。这就使得基于Spring Framework开发应用程序时结构清晰、简洁优雅。
- 控制反转:IOC–Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
- 面向切面编程:AOP–Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
- 容器:Spring Ioc是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化管理、替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
- 声明试:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
- 一站试:在IOC和AOP的基础上可以整合各种企业引用的开源框架和优秀的第三方类库。而且Spring旗下的项目以及覆盖了广泛领域,很多方面的功能需要可以在Spring Framework 的基础上全部使用Spring来实现
Spring Franework五大功能模块
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,在Spring环境下使用任何功能都必须基于IOC容器。 |
Aop&Aspects | 面向切面编程 |
Testing | 提供了对Junit或TestNG测试框架的整合。 |
Data Access/Integration | 提供了对数据访问/集成的功能 |
SpringMVC | 提供了面向Web应用程序的集成功能。 |
IOC
IOC思想
IOC:Inversion of Control,(反转控制)
DI:DependencyInjection,翻译过来的依赖注入
DI是IOC的另一种表达方式:即组件以一些预先定义好的方式(例如:Setter方法)接受自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现。
IOC容器在Spring中的实现
Spring的IOC容器就是IOC思想的一个落地的产品实现。IOC容器中管理的组件也叫做Bean。在创建Bean之前,首先需要创建IOC容器。Spring提供了IOC容器的两种实现方式:
- BeanFactory
这是IOC容器的基本实现,是Spring内部使用的接口。面向Spring本身,不提供给开发人员使用。
- ApplicationContext
BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory
3. ApplicationContext的主要实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的Xml格式的配置文件创建IOC容器对象 |
FileSystemXmlApplicationContext | t通过文件系统路径读取XML格式的配置文件创建IOC容器对象 |
ConfigurableApplicationContext | ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力 |
WebApplicationContext | 专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入存入ServletContext域中。 |
基于XML管理Bean入门案例
创建Maven Module
引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
在spring的配置文件中配置Bean
创建对象
package com.test.spring.pojo;
public class HelloWorld {
public void helloWorld(){
System.out.println("hello,World");
}
}
创建配置文件进行配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean:将对象交给IOC管理,配置一个Bean对象
属性:
id:bean的唯一标识,不能重复
class:设置bean对象所对应的类型
-->
<bean id="helloWorld" class="com.test.spring.pojo.HelloWorld" ></bean>
</beans>
创建测试类进行测试
package com.test.spring;
import com.test.spring.pojo.HelloWorld;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloWorldTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器中的Bean
HelloWorld helloWorld = (HelloWorld) ioc.getBean("helloWorld");
helloWorld.helloWorld();;
}
}
IOC创建对象是根据Class的类路径利用反射的机制通过 类的无参构造进行创建。
三种Bean的获取方式
- 获取Bean的三种方式
- 1、根据Bean的ID获取
- 2、根据Bean的类型获取(常用)
- 注意:根据类型获取Bean时,要求IOC容器中有且只有一个类型匹配的Bean
- 若没有任何一个类型匹配的Bean,此时抛出异常:NoSuchBeanDefinitionException
- 若有多个类型匹配的Bean,此时抛出异常:NoUniqueBeanDefinitionException
- 3、根据Bean的ID和类型获取
根据ID获取
@Test
public void testIOCID(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器创建的对象(通过ID获取)
Student student = (Student) ioc.getBean("studentOne");
System.out.println("student = " + student);
}
根据Class获取
@Test
public void testIOCClass(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器创建的对象(通过Class获取)
Student student = ioc.getBean(Student.class);
System.out.println("student = " + student);
}
根据ID和Class获取
@Test
public void testIOCIDAndClass(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器创建的对象(通过ID和Class获取)
Student student = ioc.getBean("studentOne",Student.class);
System.out.println("student = " + student);
}
依赖注入
set注入
<bean id="studentTwo" class="com.test.spring.pojo.Student">
<!--property标签:通过组件类的setXxx()方法组件对象设置属性-->
<!--name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->
<!--value属性:指定属性值-->
<property name="id" value="1001"></property>
<property name="name">
<!--为当前属性设置为NULL-->
<null></null>
</property>
</bean>
<!--
scope:设置bean的作用域
scope:”singleton|prototype“
singleton(单例):表示获取bean所对应的对象都是同一个
prototype(多例):表示获取该bean所对应的对象都不是同一个
-->
<bean id="helloWorldScope" class="com.test.spring.pojo.HelloWorld" scope="prototype"></bean>
<bean id="user" class="com.test.spring.pojo.User">
<property name="id" value="1"></property>
<property name="userName" value="小李"></property>
<property name="birthday" value="2003-08-26"></property>
</bean>
构造器注入
<!--构造器注入有参构造赋值-->
<bean id="studentThree" class="com.test.spring.pojo.Student" >
<!--
constructor-arg:构造器注入
name:指定属性名
type:属性类型
value:值
-->
<constructor-arg value="1" name="id" type="java.lang.Integer"></constructor-arg>
<constructor-arg value="小李子" name="name" type="java.lang.String"></constructor-arg>
<constructor-arg value="23" type="java.lang.Integer"></constructor-arg>
</bean>
xml实体
<!--小于号在XML文档中用来定义标签的开始,不能随便使用-->
<!--解决方案一:使用XML实体来代替 (<="<") (>= ">") -->
<property name="expression" value="a < b">
CDATA节
<!--在CDATA节中写的内容可以被yua-->
<property name="name">
<value>
<![CDATA[
<666>
]]>
</value>
</property>
自定义类型注入
<bean id="studentTwo" class="com.test.spring.pojo.Student">
<property name="id" value="1"></property>
<property name="name" value="小王"></property>
<property name="age" value="23"></property>
<!--ref指定配置文件中的bean的ID-->
<property name="clazz" ref="clazzOne"></property>
</bean>
<bean id="clazzOne" class=" com.test.spring.pojo.Clazz">
<property name="clazzName" value="1001班"></property>
</bean>
List集合赋值注入
<!--学生1-->
<bean id="studentOne" class="com.test.spring.pojo.Student">
<property name="id" value="1"></property>
<property name="name" value="小王"></property>
<property name="age" value="23"></property>
</bean>
<!--学生1-->
<bean id="studentTwo" class="com.test.spring.pojo.Student">
<property name="id" value="1"></property>
<property name="name" value="小王"></property>
<property name="age" value="23"></property>
</bean>
<!--班级-->
<bean id="clazzOne" class=" com.test.spring.pojo.Clazz">
<property name="clazzName" value="1001班"></property>
<!--
为Clazz设置
List集合:List集合使用<list>标签(需求提前为类设置List集合)
Array数组:Array数组使用<array>标签(需要提前为类设置Array数组)
-->
<property name="StudentArray">
<list>
<ref bean="studentOne">
<ref bean="studentTwo">
<list>
</property>
<property name="StudentMap">
<map>
</map>
</property>
</bean>
集合类型bean
需要在配置文件头部分添加约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!--班级-->
<bean id="clazzOne" class=" com.test.spring.pojo.Clazz">
<property name="clazzName" value="1001班"></property>
<property name="Studentlist" ref="studentList"></property>
</bean>
<!--配置一个集合类型的bean,需要使用util的约束:xmlns:util="http://www.springframework.org/schema/util"-->
<util:list id="studentList">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</util:list>
</beans>
Map集合赋值注入
<bean id="clazzOne" class="com.test.spring.pojo.Clazz">
<property name="id" value="1"></property>
<property name="clazzName" value="1001班"></property>
<property name="studentMap">
<map>
<entry key="1001" value-ref="studentOne"></entry>
<entry key="1002" value-ref="studentTwo"></entry>
<entry key="1003" value-ref="studentThree"></entry>
</map>
</property>
</bean>
p命名空间注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!--在使用命名空间注入对象时需要添加约束:xmlns:p="http://www.springframework.org/schema/p"-->
<bean id="studentSix" class="com.test.spring.pojo.Student"
p:id="2001" p:name="小李子"
></bean>
</beans>
引入外部属性文件
1、添加依赖
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
2、添加外部属性文件
2.1、直接配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/db_ssm?serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<!--初始化连接个数(默认值=0)-->
<property name="initialSize" value="10"></property>
<!--当前数据库最大连接数量(默认值=8)-->
<property name="maxActive" value="20"></property>
<!--获取数据最大等待时间-->
<property name="maxWait" value="10000"></property>
</bean>
</beans>
2.2、properties文件配置
jdbc.properties文件
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
spring-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入properties文件,之后可以通过${key}的方式访问value-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!--初始化连接个数(默认值=0)-->
<property name="initialSize" value="10"></property>
<!--当前数据库最大连接数量(默认值=8)-->
<property name="maxActive" value="20"></property>
<!--获取数据最大等待时间-->
<property name="maxWait" value="10000"></property>
</bean>
</beans>
3、加载数据源
package com.test.spring;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class IOCByDataSource {
@Test
public void testDataSource() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml");
DruidDataSource dataSource = ioc.getBean(DruidDataSource.class);
System.out.println(dataSource.getConnection());
}
}
Bean的作用域
概念
在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值 | 含义 | 创建对象的时机 |
---|---|---|
singleton(默认) | 在IOC容器中,这个bean的对象始终为单实例 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 |
如果时在WebApplicationContext环境下会有另外两个作用域(但不常用):
取值 | 含义 |
---|---|
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
Bean的生命周期
生命周期过程
- bean对象创建(调用无参构造器)
- 给bean对象设置属性
- bean对象初始化之前操作(bean的后置处理器负责)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean对象初始化之后操作(由bean的后置处理器负责)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
创建User类
package com.test.spring.pojo;
import java.time.LocalDate;
import java.util.Date;
public class User {
private Integer id;
private String userName;
private LocalDate birthday;
public User(){
System.out.println("生命周期1:实例化");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("生命周期2:依赖注入");
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", birthday=" + birthday +
'}';
}
public void initMethod(){
System.out.println("生命周期3:初始化");
}
public void destroyMethod(){
System.out.println("生命周期4:销毁");
}
}
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="user" class="com.test.spring.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1"></property>
<property name="userName" value="小李"></property>
<property name="birthday" value="#{T(java.time.LocalDate).of(2023, 8, 30)}" />
</bean>
<!--
使用 util 命名空间来创建日期类型
<bean id="myBean" class="com.example.MyBean">
<property name="dateField" value="#{T(java.text.SimpleDateFormat).parse('2023-08-30')}" />
<property name="localDateField" value="#{T(java.time.LocalDate).of(2023, 8, 30)}" />
<property name="localDateTimeField" value="#{T(java.time.LocalDateTime).of(2023, 8, 30, 12, 0)}" />
</bean>
-->
</beans>
测试
package com.test.spring;
import com.test.spring.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LifeCycleTest {
@Test
public void test() {
/*
bean的生命周期:
1:实例化、2:依赖注入、3:初始化、4:销毁
ApplicationContext进行实例化、依赖注入、初始化对象但是Application不携带ioc容器关闭的方法
因此在生命周期中不会执行销毁方法。除非在服务器关闭、ioc关闭才会执行。
生命周期1:实例化
生命周期2:依赖注入
生命周期3:初始化
User{id=1, userName='小李', birthday=2023-08-30}
------------------------------------------------------------------------------------------------
ConfigurableApplicationContext 是存在 close()方法的因此可以执行销毁方法
生命周期1:实例化
生命周期2:依赖注入
生命周期3:初始化 (init-method来指定初始化方法)
User{id=1, userName='小李', birthday=2023-08-30}
生命周期4:销毁 (destroy-method来指定销毁方法)
*/
// ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
ConfigurableApplicationContext configIoc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
User user = configIoc.getBean(User.class);
System.out.println(user);
configIoc.close();
}
}
Bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
创建bena的后置处理器
package com.test.spring.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 此方法在Bean的生命周期初始化之前执行
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor-->后置处理器 postProcessBeforeInitialization");
return bean;
}
/**
* 此方法在Bean的生命周期初始化之后执行
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor-->后置处理器 postProcessAfterInitialization");
return bean;
}
}
添加后置处理器的Bean生命周期
生命周期1:实例化
生命周期2:依赖注入
生命周期3:后置处理器的postProcessBeforeInitialization
生命周期4:初始化 (init-method来指定初始化方法)
生命周期5:后置处理器的postProcessAfterInitialization
生命周期6:销毁 (destroy-method来指定销毁方法)
FactoryBean
简介
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们复杂组件创建的详细过程和繁琐细节都屏蔽起来只把最简洁的使用界面展示给我们。
将来我们整合MyBatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的
创建FactoryBean(工厂Bean)
package com.test.spring.factory;
import com.test.spring.pojo.User;
import org.springframework.beans.factory.FactoryBean;
/**
* FactoryBean是一个接口,需要创建一个类实现该接口
* 其中有三个方法:
* getObject(); 通过一个对象交给IOC容器管理
* getObjectType(); 设置所提供对象的类型
* isSingleton();所提供的对象是否单例
* 当吧FactoryBean的实现类配置为Bean时,会将当前类中getObject()所返回的对象交给IOC容器管理
*/
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
Spring配置文件配置工厂Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="userFactoryBean" class="com.test.spring.factory.UserFactoryBean"></bean>
</beans>
测试
package com.test.spring;
import com.test.spring.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FactoryBeanTest {
@Test
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-factory.xml");
User user = ioc.getBean(User.class);
System.out.println("user = " + user);
}
}
结果
生命周期1:实例化
user = User{id=null, userName='null', birthday=null}
基于Xml的自动装配
自动装配
根据指定的策略,在IOC容器中配置每一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
场景模拟
创建UserController
package com.test.spring.controller;
import com.test.spring.service.UserService;
public class UserController {
/**用户实现*/
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
/**
* 用户保存
*/
public void save(){
getUserService().saveUser();
}
}
创建UserService
/**用户实现接口*/
public interface UserService {
/**
* 保存用户
*/
void saveUser();
}
/**用户实现*/
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
创建UserDao
/**用户持久层接口*/
public interface UserDao {
void saveUser();
}
/**用户持久层实现*/
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
spring配置
注意:此配置**
没有配置对象注入自动装配
**
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="userController" class="com.test.spring.controller.UserController">
<property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="com.test.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.test.spring.dao.impl.UserDaoImpl"></bean>
</beans>
注意:此配置**
开启配置对象注入自动装配
**,可以通过bean标签中autowire属性设置自动装配的策略
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!--自动装配-->
<bean id="userController" class="com.test.spring.controller.UserController" autowire="byType" ></bean>
<bean id="userService" class="com.test.spring.service.impl.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.test.spring.dao.impl.UserDaoImpl" autowire="byType"></bean>
</beans>
测试
/**
* 自动装配:
* 根据指定策略,在IOC容器中匹配某个bean,在自动为bean中的类类型的属性或接口类型的属性赋值
* 可以通过bean标签中autowire属性设置自动装配的策略
*
* 自动装配的策略:
* 1、no,default:表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值
* 2、byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean<为属性赋值
*
* 注意:
* a>若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值
* b>容通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUniqueBeanDefinitionException
* 总结:当使用byType实现自动装配时,IOC容器中有且只有一个类型匹配的bean能够为属性赋值
* 3、byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值
*/
@Test
public void testAutowire(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml");
UserController userController = ioc.getBean(UserController.class);
userController.save();
}
结果
保存成功
基于注解管理Bean
标记与扫描
- 注解
和Xml配置文件一样,注解本身并不能执行,注解本身仅仅是做一个标记,具体的功能是框架测试到注解标记的未知,然后针对这个位置按照标记的功能来执行具体操作。
本质上:所有一些的操作都是java代码来完成的,xml和注解只是告诉框架的java代码如何执行。
- 扫描
spring为了指定程序员在哪些地方标记了扫描注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
3. 新建Maven Module
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
-
创建spring配置文件
-
标识组件的常用注解
@Component:将类标识为普通组件
@Controller:将类标识为控制层组件
@Service:将类标识为业务层组件
@Repository:将类标识为持久层组件
问:以上四个注解有扫描关系和区别?
通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Serive、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然他们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便标记注解。
- 创建组件
组件扫描
context:component-scan:组件扫描
属性:
- base-package:扫描某包下
- use-default-filters(默认值为TRUE):
- true(表示扫描所以包)
- false(指关闭这个默认行为,需要手动指定扫描的注解,所以一般use-default-filters=false与include-filter配合使用。与include-filter在springMVC.xml里面搭配使用,限制只扫描controller,同样避免重复扫描)
内部标签:
context:exclude-filter:排除扫描
context:include-filter:指定扫描
属性:
- type:排除方式
- annotation(注解排除)
- assignable(根据类排除)
- expression:表达式(注解或者类的全类名)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--
context:component-scan:组件扫描
base-package:扫描包
-->
<context:component-scan base-package="com.test.spring" use-default-filters="false">
<!--
context:exclude-filter:排除扫描
type(排除扫描的方式):annotation(注解排除)、assignable(根据类排除)
expression(表达式):全类名
-->
<!--<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<!--
context:include-filter:指定扫描
type(指定扫描的方式):annotation(注解)、assignable(根据类)
expression(表达式):全类名
-->
<context:include-filter type="assignable" expression="com.test.spring.controller.UserController"/>
</context:component-scan>
</beans>
基于注解的自动装配
@Autowired
- 标识在成员变量上,此时不需要设置成员变量的set方法
- 标识在set方法上
- 标识在为当前成员变量赋值的有参构造上
@Autowired注解的原理
- 默认通过ByType的方式,在IOC容器中通过类型匹配某个Bean为属性的赋值
- 若有多个类型匹配的bean,此时会自动转换为ByName的方式实现自动装配的效果即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值。
- 若ByType和ByName的方式都无妨实现自动装配,即IOC容器中有多个类型匹配的bean且这些bean的id和要赋值的属性名都不一致,此时抛出异常:NoUniqueBeanDefinitionException
- 此时可以在要赋值的属性上,添加一个注解@Qualifier 通过该注解的value属性值没指定某个bean的id,将这个bean为属性赋值
注意:若IOC容器中没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitonException在@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配,可以将required设置为false,此时能装配则装配,无法装配使用属性的默认值
@Autowired中required属性默认值true
- true:代表必须进行自动装配
- false:将默认值取消
AOP
场景模似
- 完成加减乘除的计算
- 在计算前后进行日志记录
声明接口
声明计算器的接口Calculator,包含加减乘除的抽象方式
package com.test.spring.proxy;
//计算器接口
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);
}
实现类
package com.test.spring.proxy.impl;
import com.test.spring.proxy.Calculator;
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("日志:方法add开启计算");
int result = i+j;
System.out.println("日志:方法运输结果result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志:方法sub开启计算");
int result = i-j;
System.out.println("日志:方法运输结果result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志:方法mul开启计算");
int result = i*j;
System.out.println("日志:方法运输结果result = " + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("方法div开启计算");
int result = i/j;
System.out.println("方法运输结果result = " + result);
return result;
}
}
提出问题
- 现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷
- 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力。
- 附加功能分散在各个功能方法中,不利于统一维护
-
解决这两个问题,核心就是:解耦。我们需要吧附加功能从业务功能代码中出去出来。
-
困难
解决问题的困难:要抽取的代码在方法内部,靠以前吧类中的重复代码抽取到父类的方法没法解决。所以需要引入新的技术
代理模式
概念
介绍
二十三种的设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不在时直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑代码从目标方法中剥离出来–解耦调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
未使用代理:
使用代理后:
静态代理
创建静态代理:
package com.test.spring.proxy.impl.staticProxy;
import com.test.spring.proxy.Calculator;
import com.test.spring.proxy.impl.CalculatorImpl;
public class CalculatorStaticProxy implements Calculator {
//目标对象
private CalculatorImpl target;
public CalculatorStaticProxy(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
Integer result = null;
try {
System.out.println("日志,方法:add 参数:"+i+","+j);//前置代理
result = target.add(i, j);// 逻辑代码
System.out.println("日志,方法:add 结果:"+result);//后置代理
}catch (Exception e){
System.out.println("e = " + e);//异常代理
}finally {
return result;
}
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub 参数:"+i+","+j);
int result = target.sub(i, j);
System.out.println("日志,方法:sub 结果:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul 参数:"+i+","+j);
int result = target.mul(i, j);
System.out.println("日志,方法:mul 结果:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div 参数:"+i+","+j);
int result = target.div(i, j);
System.out.println("日志,方法:div 结果:"+result);
return result;
}
}
静态代理确实实现了解耦,但是由于代码写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复代码,日志功能还是分散的,没有统一管理
提出进一步的需求:将日志功能集中到一个代理类中将来有任何日志需求都通过这个代理类来实现。这就需要使用代理技术了。
动态代理
package com.test.spring.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 动态代理
*/
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy(){
/**
* ClassLoader loader:指定加载动态生成的代理类加载器
* Class<?>[] interfaces:获取目标对象实现的所以接口的class对象的数组
* InvocationHandler h:设置代理类中的抽象方法如何重写
* */
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("日志,方法:"+method.getName()+",参数:"+ Arrays.toString(args));
//proxy表示代理,method表示要执行表示要执行的方法args表示要执行的方法到参数列表
result = method.invoke(target, args);
System.out.println("日志,方法:"+method.getName()+",结果:"+ result);
} catch (Exception e){
System.out.println("日志,方法:"+method.getName()+",发生异常:"+ e);
}finally {
System.out.println("日志,方法:"+method.getName()+",方法执行完毕");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,h);
}
}
测试
package com.test.spring.proxy;
import com.test.spring.proxy.impl.CalculatorImpl;
import com.test.spring.proxy.impl.staticProxy.CalculatorStaticProxy;
import com.test.spring.util.ProxyFactory;
import org.junit.Test;
public class ProxyTest {
/**
* 静态代理测试
*/
@Test
public void test(){
CalculatorStaticProxy proxy = new CalculatorStaticProxy(new CalculatorImpl());
int add = proxy.add(1, 1);
}
/**
* 动态代理有两种
* 1、jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同接口在com.sum.proxy包下,类名为$proxy2
* 2、cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下
*/
@Test
public void testProxy(){
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator calculator = (Calculator) proxyFactory.getProxy();
calculator.add(1,1);
}
}
AOP概念及相关术语
概述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域种的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的基础上给程序动态统一添加额外功能的一种技术。
1.横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我门可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层天然存在的,而是根据附加功能的逻辑的需要:有十个附加功能,就有是个横切关注点。
2.通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法
- 前置通知:在代理的目标方法前执行
- 返回通知:在被代理目标方法成功结束后通知(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(开馆定论)
- 环绕通知:使用try…catch…finally结构围绕被代理的目标方法,包括上面四个通知对应的所以位置
3.切面
封装通知方法的类
4.目标
被代理的目标对象
5.代理
向目标对象应用通知之后创建的代理对象
6.连接点
这也是一个纯逻辑概念,不是语法定义的
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序成y轴,x轴和y轴的交叉点就是连接点
7.切入点
定位连接点的方式
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事务(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。
Spring的AOP技术可以通过切入点定位到特定的连接点。
切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
- 代码增强:把特定的功能装到切面类中,看哪里有需要,就往哪里套,被套用了切面逻辑的方法就被切面给增强了。
基于注解的AOP
技术说明
- 动态代理(InvocationHandler):jDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口
- AspectJ:本质上是静态代理,将代理逻辑”织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
准备工作
1、添加依赖
在IOC所需依赖基础上再加入下面依赖即可:
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!--Spring AOP,spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
2、配置
配置Spring配置文件,使用
<context:component-scan>
对组件进行扫描,使用<aop:aspectj-autoproxy/>
开启基于注解的Aop功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
Aop的注意事项:
切面类和目标类都需要交给IOC容器管理。
切面类必须通过@Aspect注解标识为一个切面
在Spring的配置文件中设置<aop:aspectj-autoproxy/>开启基于注解的AOP
-->
<context:component-scan base-package="com.test.aop.annotation"></context:component-scan>
<!--开启基于注解的Aop功能-->
<aop:aspectj-autoproxy/>
</beans>
2、接口
package com.test.aop.annotation;
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);
}
3、实现类
package com.test.aop.annotation.service.impl;
import com.test.aop.annotation.service.Calculator;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i+j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i-j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i*j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i/j;
System.out.println("结算结果 = " + result);
return result;
}
}
4、切面
1、在切面中,需要通过指定的注解将方法标识为通知方法
@Before:前置通知,在目标对象方法执行前执行
@After:后置通知,在目标对象方法的finally字句中执行
@AfterReturning:返回通知,在目标对象方法返回值之后执行
@AfterThrowing:异常通知,在目标方法的catch子句中执行
@Around:环绕通知,在目标对象的前置、后置、返回、异常都可进行自定义执行
2、切入点表达式:设置在标识通知的注解的value属性中
execution(public int com.test.aop.annotation.service.impl.CalculatorImpl.add(int,int)
execution(* com.test.aop.annotation.service.impl..(… )
第一个*表示任意的访问修饰符和返回值类型
第二个*表示类中任意的方法
…表示任意的参数列表
类的地方也可以使用*,表示包下的所有的类
3、重用切入点表达式
//@Pointcut 声明一个公共的切入点表达式
@Pointcut(“execution(* com.test.aop.annotation.service.impl..(… ))”)
public void pointCut(){}
使用方式
@Before(“pointCut()”)
4、获取连接点的信息
在通知党法的参数位置,设置JoinPoint类型的参数,就可以获取连接点对应的信息
获取连接点有对应方法签名信息
Signature signature = joinPoint.getSignature();
获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
5、切面的优先级
可以通过@Order注解的value属性设置优先级,默认值Integer的最大值
@Order注解的value属性值越小优先级越高
package com.test.aop.annotation.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 1、在切面中,需要通过指定的注解将方法标识为通知方法
* @Before:前置通知,在目标对象方法执行前执行
* @After:后置通知,在目标对象方法的finally字句中执行
* @AfterReturning:返回通知,在目标对象方法返回值之后执行
* @AfterThrowing:异常通知,在目标方法的catch子句中执行
* 2、切入点表达式:设置在标识通知的注解的value属性中
* execution(public int com.test.aop.annotation.service.impl.CalculatorImpl.add(int,int)
* execution(* com.test.aop.annotation.service.impl.*.*(.. )
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示类中任意的方法
* ..表示任意的参数列表
* 类的地方也可以使用*,表示包下的所有的类
*
* 3、重用切入点表达式
* //@Pointcut 声明一个公共的切入点表达式
* @Pointcut("execution(* com.test.aop.annotation.service.impl.*.*(.. ))")
* public void pointCut(){}
* 使用方式
* @Before("pointCut()")
*4、获取连接点的信息
* 在通知党法的参数位置,设置JoinPoint类型的参数,就可以获取连接点对应的信息
*
*/
@Component
@Aspect //将当前组件标识为切面
public class LoggerAspect {
@Pointcut("execution(* com.test.aop.annotation.service.impl.*.*(.. ))")
public void pointCut(){}
/**
* 前置通知
*/
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
/**
* 后置通知
*/
@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("后置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
/**
* 返回通知,在返回通知中若要获取目标对象的返回值,只需要通过@AfterReturning注解的returning属性
* 就可以通知方法的某个参数指定为接收目标对象的返回值
*/
@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("返回通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args)+",结果:"+result);
}
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常参数
*
*/
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception exception){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("异常通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args)+",异常:"+exception);
}
/**
* 环绕通知
* 环绕通知的方法一定要和目标对象方法的返回值一致
*/
@Around("pointCut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕前置通知");
//表示目标方法的执行
result = joinPoint.proceed();
System.out.println("环绕返回通知");
} catch (Throwable e) {
System.out.println("环绕异常通知");
throw new RuntimeException(e);
}finally {
System.out.println("环绕后置通知");
}
return result;
}
}
5、测试
package com.test.aop.annotation;
import com.test.aop.annotation.service.Calculator;
import com.test.aop.annotation.service.impl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,1);
}
}
切入点使用示例
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
一、execution:使用“execution(方法表达式)”匹配方法执行;
表达式 | 描述 |
---|---|
public * *(…) | 任何公共方法的执行 |
* cn.javass…IPointcutService.*() | cn.javass包及所有子包下IPointcutService接口中的任何无参方法 |
* cn.javass….(…) | cn.javass包及所有子包下任何类的任何方法 |
* cn.javass…IPointcutService.() | cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法 |
* (!cn.javass…IPointcutService+).*(…) | 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法 |
* cn.javass…IPointcutService+.*() | cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法 |
* cn.javass…IPointcut*.test*(java.util.Date) | cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的.如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
* cn.javass…IPointcut*.test*(…) throws IllegalArgumentException, ArrayIndexOutOfBoundsException | cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常 |
* (cn.javass…IPointcutService+&& java.io.Serializable+).*(…) | 任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法 |
@java.lang.Deprecated * *(…) | 任何持有@java.lang.Deprecated注解的方法 |
@java.lang.Deprecated @cn.javass…Secure * *(…) | 任何持有@java.lang.Deprecated和@cn.javass…Secure注解的方法 |
@(java.lang.Deprecated | |
(@cn.javass…Secure *) *(…) | 任何返回值类型持有@cn.javass…Secure的方法 |
* (@cn.javass…Secure ).(…) | 任何定义方法的类型持有@cn.javass…Secure的方法 |
* (@cn.javass…Secure () , @cn.javass…Secure (*)) | 任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,如public void test(@Secure String str1, @Secure String str1); |
* *((@ cn.javass…Secure ))或 *(@ cn.javass…Secure *) | 任何带有一个参数的方法,且该参数类型持有@ cn.javass…Secure;如public void test(Model model);且Model类上持有@Secure注解 |
* *(@cn.javass…Secure (@cn.javass…Secure *) ,@ cn.javass…Secure (@cn.javass…Secure *)) | 任何带有两个参数的方法,且这两个参数都被@ cn.javass…Secure标记了;且这两个参数的类型上都持有@ cn.javass…Secure; |
* *(java.util.Map<cn.javass…Model, cn.javass…Model>, …) | 任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass…Model, cn.javass…Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(java.util.HashMap<cn.javass…Model,cn.javass…Model>, …)”进行匹配;而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配 |
* *(java.util.Collection<@cn.javass…Secure *>) | 任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass…Secure注解;如public void test(Collection ollection);Model类型上持有@cn.javass…Secure |
* *(java.util.Set<? extends HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap;Spring AOP目前测试不能正常工作 |
* *(java.util.List<? super HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map);Spring AOP目前测试不能正常工作 |
* (<@cn.javass…Secure *>) | 任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass…Secure注解;Spring AOP目前测试不能正常工作 |
二、within:使用“within(类型表达式)”匹配指定类型内的方法执行;
表达式 | 描述 |
---|---|
within(cn.javass…*) | cn.javass包及子包下的任何方法执行 |
within(cn.javass…IPointcutService+) | cn.javass包或所有子包下IPointcutService类型及子类型的任何方法 |
within(@cn.javass…Secure *) | 持有cn.javass…Secure注解的任何类型的任何方法,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
三、this:使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;
注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;
表达式 | 描述 |
---|---|
this(cn.javass.spring.chapter6.service.IPointcutService) | 当前AOP对象实现了 IPointcutService接口的任何方法 |
this(cn.javass.spring.chapter6.service.IIntroductionService) | 当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口 |
四、target:使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;
注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;
表达式 | 描述 |
---|---|
target(cn.javass.spring.chapter6.service.IPointcutService) | 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法 |
target(cn.javass.spring.chapter6.service.IIntroductionService) | 当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口 |
五、args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;
注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;
表达式 | 描述 |
---|---|
args (java.io.Serializable,…) | 任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的 |
六、@within:使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;
注解类型也必须是全限定类型名;
表达式 | 描述 |
---|---|
@within cn.javass.spring.chapter6.Secure) | 任何目标对象对应的类型持有Secure注解的类方法; 必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
七、@target:使用“@target(注解类型)”匹配当前目标对象类型的执行方法
其中目标对象持有指定的注解;注解类型也必须是全限定类型名;
表达式 | 描述 |
---|---|
@target (cn.javass.spring.chapter6.Secure) | 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
八、@args:使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;
注解类型也必须是全限定类型名;
表达式 | 描述 |
---|---|
@args (cn.javass.spring.chapter6.Secure) | 任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符; |
九、@annotation:使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;
注解类型也必须是全限定类型名;
表达式 | 描述 |
---|---|
@annotation(cn.javass.spring.chapter6.Secure ) | 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配 |
十、bean:使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;
Spring ASP扩展的,在AspectJ中无相应概念;
表达式 | 描述 |
---|---|
bean(*Service) | 匹配所有以Service命名(id或name)结尾的Bea |
切面优先级
如果给同一个目标方法,同时设置了两个切面,例如:A切面用于控制日志信息、B切面用于校验。此时就需要考虑A、B两个切面的优先级的问题。
@Order():可以为切面设置优先级,@Order中有一个value属性该属性为int类型,value值越小优先级越高,默认值是Integar的最大值。
A切面(控制校验)
package com.test.aop.annotation.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 校验切面
*/
@Component
@Aspect//设置切面
@Order(1) //控制切面优先级
public class ValidateAspect {
@Pointcut("execution(* com.test.aop.annotation.service.impl.CalculatorImpl.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("ValidateAspect前置通知");
}
}
B切面
package com.test.aop.annotation.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 日志切面
*/
@Component
@Aspect//设置切面
@Order(2) //控制切面优先级
public class LoggerAspect {
@Pointcut("execution(* com.test.aop.annotation.service.impl.CalculatorImpl.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("LoggerAspect前置通知");
}
}
测试
package com.test.aop.annotation;
import com.test.aop.annotation.service.Calculator;
import com.test.aop.annotation.service.impl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,1);
}
}
结果
ValidateAspect前置通知
LoggerAspect前置通知
基于XML的AOP
在Spring配置文件中配置Aop
1、添加依赖(略)
2、接口
package com.test.aop.xml.service;
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);
}
3、实现类
package com.test.aop.xml.service.impl;
import com.test.aop.xml.service.Calculator;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i+j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i-j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i*j;
System.out.println("结算结果 = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i/j;
System.out.println("结算结果 = " + result);
return result;
}
}
4、切面
日志切面
package com.test.aop.xml.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 日志切面
*/
@Component
public class LoggerAspect {
/**
* 前置通知
*/
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
/**
* 后置通知
*/
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("后置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
/**
* 返回通知,在返回通知中若要获取目标对象的返回值,只需要通过@AfterReturning注解的returning属性
* 就可以通知方法的某个参数指定为接收目标对象的返回值
*/
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("返回通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args)+",结果:"+result);
}
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常参数
*
*/
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception exception){
//获取连接点的签名信息(声明信息)
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("异常通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args)+",异常:"+exception);
}
/**
* 环绕通知
* 环绕通知的方法一定要和目标对象方法的返回值一致
*/
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕前置通知");
//表示目标方法的执行
result = joinPoint.proceed();
System.out.println("环绕返回通知");
} catch (Throwable e) {
System.out.println("环绕异常通知");
throw new RuntimeException(e);
}finally {
System.out.println("环绕后置通知");
}
return result;
}
}
校验切面
package com.test.aop.xml.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 校验切面
*/
@Component
public class ValidateAspect {
public void beforeMethod(JoinPoint joinPoint){
System.out.println("ValidateAspect前置通知");
}
}
5、配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.test.aop.xml"></context:component-scan>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.test.aop.xml.service.impl.CalculatorImpl.*(..))"/>
<!--将IOC容器的某个bean设置为切面-->
<aop:aspect ref="loggerAspect" > <!-- 日志切面 -->
<!--前置通知-->
<aop:before method="beforeAdviceMethod" pointcut-ref="pointcut" ></aop:before>
<!--后置通知-->
<aop:after method="afterAdviceMethod" pointcut-ref="pointcut"></aop:after>
<!--返回通知-->
<aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrowingAdviceMethod" throwing="exception" pointcut-ref="pointcut"></aop:after-throwing>
<!--环绕通知-->
<aop:around method="aroundAdviceMethod" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1"> <!-- 校验切面 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
6、测试
package com.test.aop.annotation;
import com.test.aop.xml.service.Calculator;
import com.test.aop.xml.service.impl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTestXml {
@Test
public void testXml(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-xml.xml");
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,1);
}
}
JdbcTemplate
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
准备工作
添加依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>SSM</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring_transaction</artifactId>
<packaging>war</packaging>
<name>spring_transaction Aop</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- 基于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测试依赖 Spring整合了Junit的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</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>
</dependencies>
<build>
<finalName>spring_transaction</finalName>
</build>
</project>
配置JDBC
jdbc.properties文件
jdbc.DriverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
applicationContext.xml文件(Spring配置文件)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入.properties文件 classpath:(类路径)-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.DriverClassName}"></property>
<property name="url" value="${jdbc.url}" ></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}" ></property>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
实体类
package com.test.spring.pojo;
import lombok.Data;
import java.time.LocalDate;
@Data
public class User {
private Integer id;
private String userName;
private LocalDate birthday;
}
测试
1、测试增删改
//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring测试环境的配置文件
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void testInsert(){
String sql = "INSERT INTO sys_user (username,user_password,dept_id) VALUES(?,?,?)";
int i = jdbcTemplate.update(sql, "root",123,1);
}
}
2、测试条件查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void testFind(){
String sql = "SELECT * FROM sys_user WHERE id = ?";
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(User.class),1);
System.out.println(user);
}
}
3、测试多条数据查询List集合
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void testFindUser(){
String sql = "SELECT * FROM sys_user";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
System.out.println("userList = " + userList);
}
}
4、查询单行单列值
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void testFindCount(){
String sql = "SELECT count(*) FROM sys_user";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("count = " + count);
}
}
事务
编程试事务概念
事务功能的相关操作全部通过自己编写的代码来实现
Connection conn = ...;
try{
//开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
//核心操作
...
//提交事务
conn.commit();
}catch(Exception e){
//回滚事务
conn.rollBack();
}finally{
//释放数据连接
conn.close();
}
编程试的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
声明试事务概念
介绍
既然事务控制的代码有规律可循,代码的结构基本试确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
- 好处1:提高开发效率
- 好处2:消除了冗余的代码
- 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行健壮性、性能等各个方面的优化。
所以,我们可以总结下面两个概念:
- 编程试:自己写代码实现功能
- 声明试:通过配置让框架实现功能
基于注解的声明式事务
准备工作
1、添加依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>SSM</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring_transaction</artifactId>
<packaging>war</packaging>
<name>spring_transaction Aop</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- 基于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测试依赖 Spring整合了Junit的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</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>
</dependencies>
<build>
<finalName>spring_transaction</finalName>
</build>
</project>
2、配置JDBC
jdbc.properties文件
jdbc.DriverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
applicationContext.xml文件(Spring配置文件)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.test.spring"></context:component-scan>
<!--引入.properties文件 classpath:(类路径)-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.DriverClassName}"></property>
<property name="url" value="${jdbc.url}" ></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}" ></property>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
3、创建数据库表(略)
4、Dao(接口层)或Mapper(映射层)
public interface UserDao {
/**
* 查询用户
* @param user
* @return
*/
public User queryUser(User user);
/**
* 扣除金额
* @param user
* @return
*/
public int buyMoney(User user);
/**
* 升级部门
* @param user
* @return
*/
public int updateDept(User user);
}
5、DaoImpl(接口实现)或Mapper.xml
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public User queryUser(User user) {
String sql = "SELECT * FROM sys_user WHERE username = ? AND user_password = ? ";
return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(User.class),user.getUserName(),user.getUserPassword());
}
@Override
public int buyMoney(User user) {
String sql = "update sys_user set money = money-1 WHERE id = ? ";
int update = jdbcTemplate.update(sql, user.getId());
return update;
}
@Override
public int updateDept(User user) {
String sql = "update sys_user set dept_id = dept_id+1 WHERE id = ? ";
int update = jdbcTemplate.update(sql, user.getId());
return update;
}
}
6、Service(服务层)
/**
* 用户服务接口
*/
public interface UserService {
int userUpdate(User user);
}
7、ServiceImpl(实现层)
/**
*
* 声明式事务的配置步骤:
* 1、在Spring的配置文件中配置事务管理器
* 2、开启事务的注解驱动
* 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
* @Transactional注解标识的位置
* 1、标识在方法上
* 2、标识在类上,则类中的所有方法都会被事务管理
*/
@Component
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional
@Override
public int userUpdate(User user) {
//查询用户
User userEntity = userDao.queryUser(user);
//dept_id加1
userDao.updateDept(userEntity);
//扣除金额
return userDao.buyMoney(userEntity);
}
}
8、Controller(控制层)
@Controller
public class UserController {
@Autowired
private UserService userService;
public void buyUser(User user){
userService.userUpdate(user);
}
}
9、Test(测试)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TxByAnnotationTest {
@Autowired
private UserController userController;
@Test
public void testByMoney(){
User user = new User();
user.setUserName("Nick");
user.setUserPassword("admin");
userController.buyUser(user);
}
}
声明式事务的配置步骤:
- 1、在Spring的配置文件中配置事务管理器
- 2、开启事务的注解驱动
- 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
- @Transactional注解标识的位置
- 1、标识在方法上
- 2、标识在类上,则类中的所有方法都会被事务管理
事务属性:只读
介绍
对一个查询操作来说,如果我们吧它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作(增删改)。这样数据库就能够针对查询操作来进行优化。
使用方式
@Transactional(readOnly = true)
@Override
public int userUpdate(User user) {
//查询用户
User userEntity = userDao.queryUser(user);
//dept_id加1
userDao.updateDept(userEntity);
//扣除金额
return userDao.buyMoney(userEntity);
}
注意
对增删改操作设置只读会抛出下面异常:
nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
事务属性:超时
介绍
事务在执行过程中,有可能因为到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现问题(可能是Java程序或MySql数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,吧资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
使用方式
@Transactional(timeout = 3)
@Override
public int userUpdate(User user) {
try {
//线程休眠
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//查询用户
User userEntity = userDao.queryUser(user);
//dept_id加1
userDao.updateDept(userEntity);
//扣除金额
return userDao.buyMoney(userEntity);
}
观察结果
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Oct 19 09:55:36 CST 2023
事务属性:回滚策略
介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个Class类型的对象
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
使用方式
@Transactional(noRollbackFor = ArithmeticException.class)
@Override
public int userUpdate(User user) {
//查询用户
User userEntity = userDao.queryUser(user);
//dept_id加1
userDao.updateDept(userEntity);
//扣除金额
int i = userDao.buyMoney(userEntity);
System.out.println(i/0); // 出现ArithmeticException异常,但不会回滚
return i;
}
观察结果
虽然出现了数学计算异常(ArithmeticException)但是我们设置的回滚策略时,当出现ArithmeticException不发生回滚,因此操作正常执行
事务属性:事务隔离级别
介绍
数据库系统必须具有隔离并发运行各个事务的能力,使他们不会互相影响,避免各种并发问题。一个事务与其他事务隔离的程度为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
- 读未提交:READ UNCOMMITTED
- 允许Transaction01读取Transaction02未提交的修改。
- 读已提交:READ COMMITTED
- 要求Transaction01只能读取Transaction02已提交的修改
- 可重复读:REPEATABLE READ
- 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新
- 串行化:SERIALIZABLE
- 确保Transaction01可以多次从一个表中读取到相同行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交:READ UNCOMMITTED | 有 | 有 | 有 |
读已提交:READ COMMITTED | 无 | 有 | 有 |
可重复读:REPEATABLE READ | 无 | 无 | 有 |
串行化:SERIALIZABLE | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度
隔离级别 | Oracle | MySql |
---|---|---|
读未提交:READ UNCOMMITTED | × | √ |
读已提交:READ COMMITTED | √(默认) | √ |
可重复读:REPEATABLE READ | × | √(默认) |
串行化:SERIALIZABLE | √ | √ |
使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
事务属性:事务传播行为
介绍
当事务方法被别一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
@Transactional(propagation = Propagation.REQUIRED)
基于XML的声明式事务
<!--配置事务通知-->
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<!--read-only 属性:设置只读属性 -->
<!--rollback-for 属性:设置回滚的异常 -->
<!--no-rollback-for 属性:设置不回滚的异常 -->
<!--isolation 属性:设置事务隔离级别 -->
<!--timeout 属性:设置事务的超时时间 -->
<!--propagation 属性:设置事务的传播行为 -->
<tx:method name="userUpdate" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="tx" pointcut="execution(* com.test.spring.service.impl.*.*(..))"></aop:advisor>
</aop:config>
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视图,渲染数据后最终响应给浏览器。
什么是SpringMVC
SpringMVC是Spring的一个后续产品,是Spring的一个子项目
SpringMvc是Spring为表述层开发提供的一整套完备的解决方案。在表述层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选项了SpringMVC作为JavaEE项目表述层开发的首选方案
注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet
SpringMVC的特点
- Spring 家族原生产品,与IOC容器等基础设施无缝对接
- 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
- 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,可插拔式组件即插即用,想用什么功能配置相应组件即可
- 性能卓著,尤其适合现代大型、超大型互联网项目要求
入门案例
Spring版本:5.3.1
创建Maven工程
1、添加web模块
2、打包方式:war
3、引入依赖
<dependencies>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!--日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--ServletAPI-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--Spring5和Thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
添加web模块
配置web.xml注册
注册SpringMVC的前端控制器DispatcherServlet
默认配置方式
此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为<servlet-name>-servlet.xml,例如以下配置所对应SpringMVC的配置文件位于WEB-INF,文件名为SpringMVC-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--配置SpringMVC的前端控制器DispatcherServlet-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<!--
url-pattern中/和/*的区别:
/:匹配浏览器向服务器发送的所有请求(不包括.jsp)
/*:匹配浏览器向服务器发送的所有请求(包括.jsp)
-->
<servlet-mapping><!--请求模型-->
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
url-pattern中/和/的区别:/:匹配浏览器向服务器发送的所有请求(不包括.jsp)/:匹配浏览器向服务器发送的所有请求(包括.jsp)
创建请求控制器
由于前端控制器(DispatcherServlet)对浏览器发送的请求进行统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器。
请求控制器中每一个处理请求的方式成为控制器方法
因为SpringMVC的控制器由一个POJO(普通的Java类)担任,因此需要通过@Controller注解将其标识为一个控制层组件,交给Spring的IOC容器管理,此时SpringMvc才能够识别控制器的存在
@Controller
public class HelloController {
}
创建SpringMvc配置文件
SpringMVC-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.test.controller"></context:component-scan>
<!--配置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>
</beans>
目录结构
测试HelloWorld
实现对首页的访问
在请求控制器中创建处理请求的方法
//@RequestMapping注解:处理请求喝控制器方法之间的映射关系
//@RequestMapping注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
//localhost:8080/springMVC/
@RequestMapping("/")
public String protal(){
//将逻辑视图返回
return "index";
}
通过超链接调整到指定页面
在主页index.html中设置超链接
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
Hello World
</h1>
<a th:href="@{/hello}" >测试绝对路径</a>
</body>
再请求控制器中创建处理请求方法
@RequestMapping("/hello")
public String hello(){
return "success";
}
总结
浏览器发送请求,若请求地址符合前端控制器url-pattern,该请求就会被前端控制器DispatcherServlet处理。亲的控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中。
@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面。
@RequestMapping注解
1、@RequestMapping注解的功能
从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求喝处理请求的控制器方法关联起来的,建立映射关系。
SpringMvc 接收到指定的请求,就会来找到在映射关系中对应用的控制器方法来处理这个请求。
2、@RequestMapping注解的位置
@RequestMapping标识一个类:设置映射请求的请求路径的初始信息。
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息。
package com.test.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class SuccessRequestMappingController {
//此时控制器方法所匹配的请求的请求路径为/test/success
@RequestMapping("/success")
public String success(){
return "success";
}
}
3、@RequestMapping注解的value属性
作用:通过请求的请求路径匹配请求
- value属性时数组类型,即当前浏览器所发送请求的请求路径匹配value属性中的任何一个值
- 则当前请求就会被注解所标识的方法进行处理
4、@RequestMapping注解的Method
作用:通过请求的请求方式匹配请求
- method属性时requestMethod类型的数组,即当前浏览器所发送请求的请求方式匹配method属性的任何一种请求方式
- 则当前请求就会被注解所标识的方法进行处理
- 若浏览器所发送的请求的请求路径和@RequestMapping注解value属性匹配,但是请求方式不匹配
- 此时页面报错:405 - Request method ’xxx‘ not supported
- 在@RequestMapping的基础上,结合请求方式的一些派生注解
- @GetMapping@PostMapping@PutMapping@DeleteMapping
5、@RequestMapping注解的params属性
作用:通过请求的请求参数匹配请求,即浏览器发送的请求的请求参数必须满足params属性到的设置
- params可以使用四种表达式:
- “param”:表示当前所匹配请求的请求参数中必须携带param参数
- “!param”:表示当前所匹配请求的请求参数中一定不能携带param参数
- “param=value”:表示当前所匹配请求的请求参数中必须携带param参数且值必须为value
- “param!=value”:表示当前所匹配请求的请求参数中可以不携带param,若携带值一定不能是value
package com.test.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping({"/test","/demo"})
public class SuccessRequestMappingController {
@PostMapping(value = "/param",params = {"username","!password","age=20","gender!=女"})
public String paramSuccess(){
return "success";
}
}
6、@RequestMapping注解的headers属性(了解)
@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射
@RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
- ”header“:要求请求映射所匹配的请求必须携带header请求头信息
- “!header”:要求请求映射所匹配的请求必须不能携带header请求头信息
- “header=value”:要求请求映射所匹配的请求必须携带header请求头信息且header=value
- “header!=value”:要求请求映射所匹配的请求必须携带header请求头信息且header!=value
若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到。
7、SpringMvc支持ant风格的路径
- ?:表示任意的单个字符(不包括?本身)
- *:表示任意的0个或多个字符
- **:表示任意层数的任意目录
注意:在使用**
时,只能使用/**/xxx
的方式
@RequestMapping("/a?t/test")
public String testAnt(){
return "success";
}
<a th:href="@{test/aat/test}">测试Ant风格路径</a>
<br/>
<a th:href="@{test/abt/test}">测试Ant风格路径</a>
8、SpringMvc支持路径中的占位符(重点)
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
SpringMvc路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在通过@PathVariable注解,将占位符表示的数据赋值给控制器方法的形参
SpringMVC获取请求参数
1、通过ServletAPI获取
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象。
- 只需要在控制器方法的形参位置设置HttpServletRequest类型的形参
- 就可以在控制器方法中使用request对象获取请求参数
<form th:action="@{param/servletAPI}" method="post" >
<input name="username" value="李佳伟">
<input name="password" value="admin">
<input name="age" value="20">
<input type="submit" value="param/servletAPI提交">
</form>
@RequestMapping("/servletAPI")
public String getParamByServletAPI(HttpServletRequest request,HttpServletResponse response){
HttpSession session = request.getSession();
String username = request.getParameter("username");
String password = request.getParameter("password");
String age = request.getParameter("age");
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println("age = " + age);
return "success";
}
2、通过控制器方法的形参获取
- 只需要在控制器方法的形参位置,设置一个形参,形参的名字和请求参数的名字一致即可
3、@RequestParam:将请求参数和控制器方法的形参绑定。
- @RequestParam注解的三个属性:value、required、defaultValue
- value:设置和形参绑定的请求参数的名字
- required:设置是否必须传输value所对应的请求参数
- 默认值为true,表示value所对应的请求参数必须传输,否则页面报错:
- 400 - Required String parameter ‘xxx’ is not present
- 若设置false,则表示value所对应的请求参数不是必须传输,若为传输,则形参值为NULL
- defaultValue:设置当没有传输参数value所对应的请求参数时,为形参设置的默认值,此时和required属性值
4、@RequestHeader:请求头信息和控制器方法的参数绑定
5、CookieValue:将CookieValue数据和控制器方法的参数绑定
<form th:action="@{param/servletAPI/param}" method="post" >
<input name="username" value="李佳伟">
<input name="password" value="admin">
<input name="age" value="20">
<input type="submit" value="param/servletAPI/param提交">
</form>
@RequestMapping("/servletAPI/param")
public String getParamByServletAPI
(@RequestParam(value = "username",required = true,defaultValue ="admin" ) String username,
String password, String age,
@RequestHeader("referer") String referer,
@CookieValue("workSessionId") String workSessionId){
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println("age = " + age);
System.out.println("referer = " + referer);
System.out.println("workSessionId = " + workSessionId);
return "success";
}
6、通过POJO获取请求参数
需要在控制器方法的形参位置设置实体类型的形参,要保证实体类中的属性的属性名和请求参数的名字一致,可以通过实体类类型的形参获取请求参数
<form th:action="@{param/pojo}" method="post">
<input name="username" value="李佳伟">
<br/>
<input name="password" value="admin">
<br/>
<input name="age" value="23">
<br/>
<input type="submit" value="登录(POJO)">
</form>
@RequestMapping("/pojo")
public String paramPojo(User user){
System.out.println(user);
return "success";
}
7、解决获取请求此参数的乱码问题
在Web.xml中配置Spring的编码过滤器CharacterEncodingFilter
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring的编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--初始化参数-->
<init-param>
<param-name>encoding</param-name><!--设置请求编码UTF-8-->
<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>
<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>
</web-app>
注:
SpringMvc中处理编码的过滤器一定要配置到其他过滤器之前,否则无效。
域对象共享数据
1、使用ServletAPI向request域对象共享数据
@RequestMapping("/test")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope","hello servletAPI");
return "success";
}
2、使用ModelAndView向request域对象共享数据
@RequestMapping("/test")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope","hello servletAPI");
return "success";
}
3、使用Model向请求域共享数据
@RequestMapping("/model")
public String testModel(Model model){
model.addAttribute("testRequestScope","hello Model");
return "success";
}
4、使用ModelMap向请求域共享数据
@RequestMapping("/model/map")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope","hello ModelMap");
return "success";
}
5、使用Map向请求域共享数据
@RequestMapping("/map")
public String testMap(Map<String,Object> map){
map.put("testRequestScope","hello Map");
return "success";
}
前端访问
<a th:href="@{/scope/mav}" >测试ModelAndView向请求域共享数据</a>
<br/>
<a th:href="@{/scope/model}" >测试Model向请求域共享数据</a>
<br/>
<a th:href="@{/scope/model/map}" >测试ModelMap向请求域共享数据</a>
<br/>
<a th:href="@{/scope/map}" >测试Map向请求域共享数据</a>
6、Model和ModelMap和Map的关系
其实在底层中,这些类型的形参最终都是通过BindingAwareModelMap创建
public class BindingAwareModelMap extends ExtendedModelMap{}
public class ExtendedModelMap extends ModelMap implements Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}
7、向Session域共享数据
@RequestMapping("/session")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","hello session");
return "success";
}
8、向Application域共享数据
@RequestMapping("/application")
public String testApplication(HttpSession session){
ServletContext servletContext = session.getServletContext();
servletContext.setAttribute("testApplicationScope","hello application");
return "success";
}
SpringMvc的视图
SpringMvc中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMvc的视图种类很多,默认有转发视图和重定向视图
当工程引入jstl的依赖,在配置文件中配置InternalResourceViewResolver,转发视图会自动转换为JstlView
若使用的视图技术为Thymeleaf,在SpringMvc的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
1、ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMvc配置文件所配置的视图解析器解析,视图名称拼接视图前缀和视图。
@RequestMapping("/thymeleaf")
public String testThymeleafView(){
return "success";//没有qi
}
2、转发视图
SpringMvc中默认的转发视图是InternalResourceView
SpringMvc中创建转发视图的情况
当控制器方法中所设置的视图名称以"forward:"为前缀时,创建internalResourceView视图,此时的视图名称不会被SpringMvc配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转。
例如:“forward:/scope/session”
@RequestMapping("/forward")
public String testInternalResourceView(){
return "forward:/scope/session";
}
3、重定向视图
SpringMvc默认的重定向视图是RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMvc配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩下部分作为最终路径通过重定向的方式实现跳转
例如:“redirect:/scope/session”
@RequestMapping("/redirect")
public String testRedirectView(){
return "redirect:/scope/session";
}
4、视图控制器view-controller
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
<!--开启Mvc的注解驱动-->
<mvc:annotation-driven/>
<!--
视图控制器:为当前的请求直接设置视图名称实现页面跳转
若设置视图控制器,则只有试图控制器所设置的请求会被处理,其他的请求将全部404
此时必须在配置一个标签:<mvc:annotation-driven>
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
温馨提示
如果配置视图控制器后出现cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'mvc:view-controller' 的声明。
异常情况
解决方法:
将SpringMvc配置文件的配置头改为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启Mvc的注解驱动-->
<mvc:annotation-driven/>
<!--
视图控制器:为当前的请求直接设置视图名称实现页面跳转
若设置视图控制器,则只有试图控制器所设置的请求会被处理,其他的请求将全部404
此时必须在配置一个标签:<mvc:annotation-driven>
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<beans>
RESTful
1、RESTful简介
REST:Representational State Transfer,表现层资源状态转移。
资源
资源是一种看待服务器的方式,即将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象由多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词核心来组织的,首先关注的是名词。一个资源可以由一个或多个URL来表示。URL既是资源的名称,也是资源在Web的地址。对某个资源感兴趣的客户端应用,可以通过资源的URL与其进行交互。
资源的表述
资源的表述是一段对于资源在某个特定时刻的动态描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML、XML、JSON、纯文本、图片、视频、音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
状态转移
状态转移说的是:在客户端喝服务端之间转移(Transfer)代表资源状态的表述。通过转移喝操作资源的表述来间接实现操作资源的目的。
2、RESTful的实现
具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE
他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送服务器的数据作为URL地址的一部分,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserBy?id=1 | user/1–>get请求方式 |
保存操作 | saveUser | user–>post请求方式 |
删除操作 | deleteUser?id=1 | user/1–>delete请求方式 |
更新操作 | updateUser | user–>put请求方式 |
3、Hidden Http Method Filter
HiddenHttpMethodFilter处理Put和delete请求的条件
- 当前请求的请求方式必须为POST
- 当前请求必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter过滤器就会将当前请求的请求方式转换为请求参数**_method的值,因为此请求参数_method**的值才是最终的请求方式
在web.xml中注册HiddenHttpMethodFilter
<!--设置请求方式过滤器-->
<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中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter在web.xml中注册时,必须先注册CharacterEncodingFilter,在注册HiddenHttpMethodFilter
原因:
- 在CharacterEncodingFilter中通过request.setCharaterEncoding(encoding)方法设置字符集的
- request.setCharacterEncoding(encoding)方法要求前面不能有任何获取请求参数的操作
- 而HiddenHttpMethodFilter是有一个获取请求方式的操作:
Spring paramValue = request.getParameter(this.methodParam);
RESTful案例
准备工作
和传统CRUD一样,实现对员工信息的增删改查
- 搭建环境
- 准备实体类
package com.test.pojo;
import lombok.Data;
@Data
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
public Employee(){
}
public Employee(Integer id, String lastName, String email, Integer gender) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
}
Dao
package com.test.dao;
import com.test.pojo.Employee;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employeeMap = null;
static {
employeeMap = new HashMap<>();
employeeMap.put(1001,new Employee(1001,"E-AA","aa.@qq.com",1));
employeeMap.put(1002,new Employee(1002,"E-BB","bb.@qq.com",1));
employeeMap.put(1003,new Employee(1003,"E-CC","cc.@qq.com",0));
employeeMap.put(1004,new Employee(1004,"E-DD","dd.@qq.com",0));
employeeMap.put(1005,new Employee(1005,"E-EE","ee.@qq.com",1));
}
private static Integer initId = 1005;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employeeMap.put(employee.getId(),employee);
}
public Collection<Employee> getAll(){
return employeeMap.values();
}
public Employee getEmployee(Integer id){
return employeeMap.get(id);
}
public Employee removeEmployee(Integer id){
return employeeMap.remove(id);
}
}
Controller
package com.test.controller;
import org.springframework.stereotype.Controller;
@Controller
public class EmployeeController {
}
SpringMvc处理Ajax请求
@RequestBody
@RequestBody可以获取请求体信息,使用@RequestBody注解标识控制器方法的形参,当前请求体就会为当前注解所标识的形参赋值
@RequestBody:将请求体中的内容和控制器方法的形参进行绑定
- a>导入jackson的依赖
- b>在SpringMvc的配置文件中设置mvc:annotation-driven/
- c>在处理请求的控制器方法的形参位置,直接设置json格式的请求参数要转换的java类型的形参,使用@RequestBody注解标识即可
就可以将java对象直接转换为json字符串,并响应浏览器
@SneakyThrows
@PostMapping("/test/ajax")
public void testAjax(Integer id, String name, @RequestBody String requestBody, HttpServletResponse response){
System.out.println("id = " + id + ", name = "+ name);
System.out.println("requestBody = " + requestBody);
response.getWriter().write("hello Axios");
}
@SneakyThrows
@PostMapping("/test/requestBody/json")
public void testRequestBody(@RequestBody User user, HttpServletResponse response){
System.out.println(user);
response.getWriter().write("hello requestBody");
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--<script type="application/javascript" th:src="@{/static/js/vue.js}" ></script>-->
<h1>Hello World</h1>
<div id="app">
<h1>index.html</h1>
<input type="button" value="测试SpringMvc处理Ajax" @click="testAjax()">
<input type="button" value="测试SpringMvc处理RequestBody" @click="testRequestBody()">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
<script type="application/javascript" th:src="@{/static/js/axios.min.js}" ></script>
<script type="application/javascript">
/**
* axios({
* url:"",//请求路径
* method: "",//请求方式
* //以name=value&name=value的方式发送请求参数,
* //不管使用的请求方式是GET或者POST,请求参数都会被拼接到请求地址后
* //此种方式的请求可以通过request.getParameter()获取
* params:{},
* //以json格式发送的请求参数
* //请求参数会被保存到请求报文的请求体传输到服务器
* //此种方式的请求参数不能通过request.getParameter()获取
* data:{}
* }).then(response=>{
* console.log(response)
* });
*/
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
testAjax(){
axios.post("/SpringMvc/test/ajax?id=1001&name=李佳伟",
{id:1002,name:"张三"}).then(response=>{
console.log(response);
})
},
testRequestBody(){
axios.post("/SpringMvc/test/requestBody/json",
{id:1002,name:"张三"}).then(response=>{
console.log(response);
})
}
}
})
</script>
</body>
</html>
@ResponseBody
将所标识的控制器方法的返回值作为响应到浏览器的响应报文的响应体返回到页面。
使用@ResponseBody注解响应浏览器json格式的数据
- a>导入jackson的依赖
- b>在SpringMvc的配置文件中设置mvc:annotation-driven/
- c>将需要转换为json字符串的java对象直接作为控制器方法返回值,使用@ResponseBody注解标识控制器方法
- 就可以将java对象直接转换为json字符串,并响应浏览器
@ResponseBody
@GetMapping("/test/responseBody")
public String testResponseBody(){
return "success";
}
@ResponseBody
@PostMapping("/test/responseBody/User")
public User testResponseBodyGetUser(){
return new User(1001,"张三");
}
@RestController
@RestController注解是SpringMvc提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中每一个方法添加了@ResponseBody注解
文件上传和下载
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文使用ResponseEntity实现下载文件的功能。
文件下载
@RequestMapping("/test/down")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中的文件真实路径
String realPath = servletContext.getRealPath("static");
// File.separator:识别系统的”/“分隔符
realPath = realPath + File.separator + "img" + File.separator + "1.jpg";
System.out.println("realPath = " + realPath);
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组,si.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;
}
<a th:href="@{/test/down}">下载图片</a>
文件上传
文件上传的要求:
- 1、form表单的请求方式必须为post
- 2、form表单必须设置属性multipart/form-data
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
springMvc.xml配置文件
<!--
配置文件上传解析器
注意:Spring获取CommonsMultipartResolver的方式是ID获取
ID名称必须是:multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
@PostMapping("/test/up")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//photo.getOriginalFilename():获取上传文件名
String filename = photo.getOriginalFilename();
//获取文件后缀
String hzName = filename.substring(filename.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString() + hzName;
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
//创建photoPath
File file = new File(photoPath);
//判断file所对应目录是否存在
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + newFileName;
//文件上传
photo.transferTo(new File(finalPath));
return "success";
}
<form th:action="@{/test/up}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo">
<input type="submit" value="上传">
</form>
拦截器
拦截器的配置
- SpringMvc中的拦截器用于拦截控制器方法的执行
- SpringMvc中的拦截器需要实现HandlerInterceptor
- SpringMvc的拦截器必须在SpringMvc的配置文件中进行配置;
package com.test.Interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* preHandle():在控制器方法执行之前执行,其返回值表示对控制器方法的拦截(false)或放行(true)
* postHandle():在控制器方法执行之后
* afterCompletion():在控制器方法执行之后,且渲染视图完毕之后执行
*
* 多个拦截器的执行顺序和在SpringMvc的配置文件的顺序有关
* parHandle()按照配置的属性执行,而postHandle()和afterCompletion()会被配置的反序执行
*
* 若拦截器中的某个拦截器的preHandle()返回false
* 拦截器的preHandle()返回false和它之前的拦截器的preHandle()都会执行
* 拦截器的preHandle返回false之前的拦截器的afterCompletion()会执行
*/
@Component
public class FirstInterceptor implements HandlerInterceptor {
/**
* 拦截或放行
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return true:方向 ,false:拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-->preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor-->postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor-->afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<!--bean和ref标签所配置的拦截器默认对DispatcherServlet处理的所有的请求进行拦截-->
<!--<bean class="com.test.Interceptor.FirstInterceptor"/>-->
<!--<ref bean="firstInterceptor"/>-->
<!--
精确拦截
设置拦截规则:
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/abc/"/>排除的
设置拦截器:
<ref bean="firstInterceptor"/>
-->
<mvc:interceptor>
<!--拦截的请求路径,/**表示所有请求-->
<mvc:mapping path="/**"/>
<!--排除的请求路径-->
<mvc:exclude-mapping path="/abc/"/>
<ref bean="firstInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
多个拦截器执行顺序
1、若每一个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMvc的配置文件的配置顺序有关。
preHandle()会按照配置的顺序执行,而postHandle和afterCompletion()会按照配置的反序执行。
2、若某一个拦截器的parHandle()返回了fasle
preHandle()返回false和它之前的拦截器的preHandle都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterCompletion()会执行
异常处理器
SpringMvc提供了一个处理控制器方法执行过程中所出现的异常的接口:HandleExceptionResolver
HandleExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMvc提供了自定义的异常处理器SimpleMappingExceptionResovler
SpringMvc.xml配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key设置要处理的异常,value设置出现该异常时要跳转的页面所对应的逻辑视图-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--设置共享在请求域中的异常信息的属性名-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
SpringMvc注解配置异常处理器
package com.test.controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
//将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
/**
* 设置要处理的异常信息
* @param ex 表示控制器方法所出现的异常
* @param model
* @return
*/
@ExceptionHandler(ArithmeticException.class)
public String handleException(Throwable ex, Model model){
model.addAttribute("ex",ex);
return "error";
}
}
注解配置SpringMvc
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitlalizer接口类,如果找到的话就用它来配置Servlet容器。
Spring提供了这个接口的实现,名为SpringServletContainerInitalizer,这个类反过来又会查找实现。
WebApplicationInitalizer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的
WebApplicationInitlalizer基础实现,名为AbstractAnnotationConfigDispatcherServletInitalizer,当我们的类扩展了AbstracAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
添加依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>SSM</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>Spring_mvc_annotation</artifactId>
<packaging>war</packaging>
<name>Spring_mvc_annotation Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!--日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--ServletAPI-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--Spring5和Thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<build>
<finalName>Spring_mvc_annotation</finalName>
</build>
</project>
添加配置
-
创建WebInit.java,继承AbstractAnnotationConfigDispatcherServletInitializer;
-
此时WebInit就代替了web.xml在项目启动时护会自动识别WebInit类
其中可以配置:
- Spring的配置文件(applicationContext.xml)
- SpringMvc的配置文件(SpringMvc.xml)
- SpringMvc的前端控制器DispatcherServlet的url-pattern(Web.xml)
- 设置过滤器
package com.test.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
/**
* 代替web.xml
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 设置一个配置类代替Spring的配置文件
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 设置一个配置类代替SpringMvc的配置文件
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 设置SpringMvc的前端控制器DispatcherServlet的url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 设置当前的过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
//设置编码过滤器
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
//设置请求方式过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter,hiddenHttpMethodFilter};
}
}
创建Spring配置
在WebInit配置了SpringConfig可替代Spring的xml配置
package com.test.config;
import org.springframework.context.annotation.Configuration;
/**
* 代替Spring配置文件
*/
@Configuration //将类表示为配置类
public class SpringConfig {
}
创建SpringMvc配置
配置内容:扫描组件、视图解析器、默认的servlet处理静态资源、mvc的注解驱动、配置视图控制器、配置文件上传解析器、配置拦截器、异常解析器
扫描组件
添加@Component注解的类将会被扫描,项目启动添加到容器中
@ComponentScan("com.test.controller")
视图解析器
配置默认开启的视图
/**
* 配置视图解析器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
Servlet处理静态资源
但是DispatcherServlet无法处理静态资源,需要配置处理访问静态资源
@Override
//默认的servlet处理静态资源
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
mvc的注解驱动
在Spring MVC中,MVC(Model-View-Controller)的注解驱动是一种使用注解来配置和管理控制器、请求映射以及其他相关功能的方式。通过使用注解,开发者可以更方便地定义和配置控制器,而不必依赖于传统的XML配置方式
//开启mvc的注解驱动
@EnableWebMvc
配置视图控制器
此配置使用视图控制器配置首次访问主页面index
/**
* 配置视图解析器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
配置文件上传解析器
/**
* 配置文件上传解析器
* @return
*/
//@Bean注解可以将标识的方法的返回值作为bean进行管理
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
配置拦截器
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirsInterceptor firsInterceptor = new FirsInterceptor();
registry.addInterceptor(firsInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/abc");
}
异常解析器
/**
* 配置异常解析器
* @param resolvers initially an empty list
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException","error");
exceptionResolver.setExceptionMappings(prop);
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
配置Thymeleaf视图解析器
/**
* 配置Thymeleaf视图解析器
* @return
*/
@Bean
public ITemplateResolver templateResolver(){
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver){
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并为解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
SpringMvc综合配置
package com.test.config;
import com.test.interceptor.FirsInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import java.util.List;
import java.util.Properties;
/**
* 代替SpringMvc的配置文件
* 配置内容:扫描组件、视图解析器、默认的servlet处理静态资源、mvc的注解驱动
* 配置视图控制器、配置文件上传解析器、配置拦截器、异常解析器
*/
//将类标识为配置类
@Configuration
//扫描组件
@ComponentScan("com.test.controller")
//开启mvc的注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
//默认的servlet处理静态资源
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 配置视图解析器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
/**
* 配置文件上传解析器
* @return
*/
//@Bean注解可以将标识的方法的返回值作为bean进行管理
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirsInterceptor firsInterceptor = new FirsInterceptor();
registry.addInterceptor(firsInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/abc");
}
/**
* 配置异常解析器
* @param resolvers initially an empty list
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException","error");
exceptionResolver.setExceptionMappings(prop);
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
/**
* 配置Thymeleaf视图解析器
* @return
*/
@Bean
public ITemplateResolver templateResolver(){
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver){
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并为解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
SpringMvc执行流程
- DispatcherServlet:前端控制器,不需要工程师开发,由框架提供。
作用:统一处理请求和响应,整个流程控制中心,由它调用其它组件处理用户请求。
- HandlerMapping:处理器映射器,不需要工程师开发,由框架提供。
作用:根据请求的url、method等信息查找Handler,即控制器方法。
- Handler:处理器,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
- HandlerAdapter:对处理器(控制器方法)进行执行
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
- ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
- View:视图
作用:将模型数据通过页面展示给用户
DispatcherServlet初始化过程
DispatcherServlet本质上是一个Servlet,所以天然的遵循Servlet的生命周期。所以宏观上是Servlet生命周期来进行调度
①初始化WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
//创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
//刷新WebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//将IOC容器在应用领域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
②创建WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通过反射创建IOC容器对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//设置父容器(Spring容器)
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
③DispatcherServlet初始化策略
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
所在类:org.springframework.web.servlet.DispatcherServlet
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
DispatcherServlet调用组件处理请求
①processRequest()
FrameworkServlet重写HttpServlet中的service和doXxx(),这些方法中调用了processRequest(request,response)
所在类:org.springframework.web.servlet.FrameworkServlet
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
//执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
②doService
所在类:org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
//处理请求和响应
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
③doDispatch
所在类:org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//由处理器适配器调用具体的控制器方法,最终获取ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
④processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
SpringMvc的执行流程
- 用户向服务器发送请求,请求被SpringMvc前端控制器DispatcherServlet捕获。
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URL),判断请求URL对应的映射;
- 不存在
- 在判断是否配置了mvc:default:servlet:handler
- 如果没配置,则控制台报映射找不到,客户端展示404错误
- 如果有配置,则访问目标资源(一般为静态资源,如:js、CSS、HTML),找不到客户端会展示404错误
- 存在则执行下面的流程
- 根据该URL,调用HandlerMapping获取该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
- DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。
- 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置Spring将帮你做一些额外的工作;
- HttpMessageConveter:将请求消息(如json、xml等数据)转换成一个对象,将对象转换为指定的相应信息
- 数据转换:对请求消息进行数据转换。如Spring转换成In teger、Double等
- 数据格式化:对请求消息进行数据格式化。如将字符串换成格式化数字或格式化日期等。
- 数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象
SSM整合
ContextLoaderListener(监听器)
Spring提供了监听器ContextLoaderListener,实现ServletContextListener接口,可监听ServletContext的状态,在Web服务器的启动,读取Spring的配置文件,创建Spring的IOC容器。web应用中必须在web.xml中配置
<listener>
<!--
配置Spring的监听器,在服务器时加载Spring的配置文件
Spring配置文件默认位置和名称:/WEB-INF/applicationContext.xml
可通过上下文参数自定义Spring配置文件的位置和名称
-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 自定义Spring配置文件的位置名称 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
①创建Maven Module
②导入依赖
<!--web工程-->
<packaging>war</packaging>
<name>SSM_Demo</name>
<!--自定义属性-->
<properties>
<spring.version>5.3.1</spring.version>
</properties>
<dependencies>
<!--SpringMvc、Spring相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId><!--事务管理器-->
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId><!--管理切面-->
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--MyBatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId><!--spring整合mybatis-->
<version>2.0.6</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--ServletAPI-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.2</version>
</dependency>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--Spring5和Thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
③创建表
CREATE TABLE `t_emp` (
`emp_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`emp_name` varchar(20) DEFAULT NULL COMMENT '员工姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`email` varchar(50) DEFAULT NULL COMMENT 'Email',
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB AUTO_INCREMENT=163 DEFAULT CHARSET=utf8 COMMENT='员工表'
④配置web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--设置Spring配置文件自定义的位置名称-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!--配置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>
<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>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
配置Spring的监听器,在服务器时加载Spring的配置文件
Spring配置文件默认位置和名称:/WEB-INF/applicationContext.xml
可通过上下文参数自定义Spring配置文件的位置和名称
-->
<listener>
<!--在服务器启动时加载Spring的配置文件-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--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>
<!--将DispatcherServlet的初始化时间提前到服务器启动时-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
⑤配置springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:comtext="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--扫描控制层组件-->
<comtext:component-scan base-package="com.ssm.controller"></comtext:component-scan>
<!--视图解析器-->
<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>
<!--配置默认的servlet处理静态资源-->
<mvc:default-servlet-handler/>
<!--开启Mvc注解驱动-->
<mvc:annotation-driven/>
<!--配置视图控制器-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
</beans>
⑥配置spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:comtext="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描控制层组件(排除Controller层)-->
<comtext:component-scan base-package="com.ssm">
<!-- 排除控制层 -->
<comtext:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</comtext:component-scan>
<!-- 引入jdbc.properties -->
<comtext:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<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>
<!--配置sqlSessionFactoryBean,可以直接在Spring的IOC中获取sqlSessionFactory-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置Mybatis的核心配置文件的路径-->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--设置数据源-->
<property name="dataSource" ref="dataSource"></property>
<!--设置类型别名所对应的包-->
<property name="typeAliasesPackage" value="com.ssm.pojo"></property>
<!--设置映射文件的路径,只有映射文件的包和mapper接口的包不一致时需要设置-->
<!--<property name="mapperLocations" value="classpath:com/ssm/mapper/*.xml"></property>-->
<!--配置分页插件-->
<!--<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor"></bean>
</array>
</property>-->
<!-- 使用configuration配置 -->
<!--<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>-->
</bean>
<!--
配置mapper接口的扫描,可以将指定包下所有的mapper接口,
通过sqlSession创建代理实现类对象,并将这些对象交给IOC容器管理
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ssm.mapper"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动
将使用注解@Transactional标识的方法或类中所有的方法进行事务管理
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
⑦配置mybatis-config.xml
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- settings(设置) -->
<settings>
<!-- 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<plugins>
<!-- 配置分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
</configuration>
⑧配置log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "file:///path/to/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/>
</layout>
</appender>
<logger name="java.sql">
<level value="debug"/>
</logger>
<logger name="org.apache.ibatis">
<level value="info"/>
</logger>
<root>
<level value="debug"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
pojo(Employ)
package com.ssm.pojo;
import lombok.Data;
@Data
public class Employ {
/**职工编号*/
private Integer empId;
/**职工名称*/
private String empName;
/**职工年龄*/
private Integer age;
/**职工性别*/
private String sex;
/**职工邮箱*/
private String email;
}
controller(EmployController)
package com.ssm.controller;
import com.github.pagehelper.PageInfo;
import com.ssm.pojo.Employ;
import com.ssm.service.EmployService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* 查询所以的员工信息-->/employee-->GET
* 查询所以的员工信息-->/employee/page/1-->GET
* 跳转添加页面-->/to/add/employee-->GET
* 添加员工信息-->//employee-->POST
* 跳转修改页面-->/to/update/employee/1-->GET
* 修改员工信息-->/employee-->PUT
* 删除员工信息-->/employee/1-->DELETE
*/
@Controller
public class EmployController {
@Autowired
private EmployService employService;
@GetMapping("/Employ/page/{pageNum}")
public String getEmployPage(@PathVariable("pageNum") Integer pageNum,Model model){
PageInfo<Employ> list = employService.getEmployPageAll(pageNum);
System.out.println("pageInfo = " + list);
model.addAttribute("page",list);
return "employ_list_page";
}
@GetMapping("/Employ")
public String getEmploy(Model model){
List<Employ> list = employService.getEmployAll();
model.addAttribute("employList",list);
return "employ_list";
}
}
service(EmployService)
package com.ssm.service;
import com.github.pagehelper.PageInfo;
import com.ssm.pojo.Employ;
import java.util.List;
public interface EmployService {
/**
* 查询所有员工信息
* @return
*/
List<Employ> getEmployAll();
/**
* 分页查询员工信息
* @param pageNum 当前页数
* @return
*/
PageInfo<Employ> getEmployPageAll(Integer pageNum);
}
serviceImpl(EmployServiceImpl)
package com.ssm.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssm.mapper.EmployMapper;
import com.ssm.pojo.Employ;
import com.ssm.service.EmployService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class EmployServiceImpl implements EmployService {
@Autowired
private EmployMapper employMapper;
@Override
public List<Employ> getEmployAll() {
return employMapper.selectEmploy();
}
@Override
public PageInfo<Employ> getEmployPageAll(Integer pageNum) {
PageHelper.startPage(pageNum,10);
List<Employ> list = employMapper.selectEmploy();
PageInfo<Employ> employPageInfo = new PageInfo<>(list, 5);
return employPageInfo;
}
}
mapper(EmployMapper)
package com.ssm.mapper;
import com.ssm.pojo.Employ;
import java.util.List;
public interface EmployMapper {
public List<Employ> selectEmploy();
}
mapper.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.ssm.mapper.EmployMapper">
<resultMap id="EmployResultMap" type="com.ssm.pojo.Employ">
<id property="empId" column="emp_id"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
<sql id="employColumn" >
emp_id,emp_name,age,sex,email
</sql>
<select id="selectEmploy" resultMap="EmployResultMap" >
SELECT <include refid="employColumn"></include> FROM t_emp
</select>
</mapper>
html(employ_list_page.html)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" bordercolor="#333" width="80%" cellpadding="5" cellspacing="0" align="center" valign="top" bgcolor="#f0f0f0" summary="This table contains sales data for the current year.">
<tr>
<th colspan="7">
EmployeeInfo
</th>
</tr>
<tr>
<th>Number</th>
<th>EmpId</th>
<th>EmpName</th>
<th>Age</th>
<th>Sex</th>
<th>Email</th>
<th>CRUD</th>
</tr>
<tr th:each="item,status:${page.list}">
<td th:text="${status.count}"></td>
<td th:text="${item.empId}"></td>
<td th:text="${item.empName}"></td>
<td th:text="${item.age}"></td>
<td th:text="${item.sex}"></td>
<td th:text="${item.email}"></td>
<td>
<a th:href="@{employee/${item.id}}" >delete</a>
<a th:href="@{employee/${item.id}}" >update</a>
</td>
</tr>
</table>
<div style="text-align: center; margin-top: 10px " >
<a style="color: black; text-decoration: none"
th:if="${page.hasPreviousPage}" th:href="@{/Employ/page/1}" >
首页
</a>
<a style="color: black; text-decoration: none"
th:if="${page.hasPreviousPage}" th:href="@{'/Employ/page/'+${page.prePage}}" >
上一页
</a>
<span th:each="pageNumber : ${page.navigatepageNums}" >
<a th:if="${pageNumber == page.pageNum}" style="padding:0px 5px; margin: 0px 5px; background-color:#333;
color: black; text-decoration: none;border: 1px black solid"
th:href="@{'/Employ/page/'+${pageNumber}}"
th:text="${pageNumber}"></a>
<a th:if="${pageNumber != page.pageNum}" style="padding:0px 5px; margin: 0px 5px; color: black; text-decoration: none;border: 1px black solid"![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/f41915f1d8fe4dc9a5d12ae1fbbdaa9d.png#pic_center)
th:href="@{'/Employ/page/'+${pageNumber}}"
th:text="${pageNumber}"></a>
</span>
<a style="color: black; text-decoration: none"
th:if="${page.hasNextPage}" th:href="@{'/Employ/page/'+${page.nextPage}}" >
下一页
</a>
<a style="color: black; text-decoration: none"
th:if="${page.hasNextPage}" th:href="@{'/Employ/page/'+${page.pages}}" >
末页
</a>
</div>
</body>
</html>