SpringMVC高级
一、SSM整合
整合思路及流程:
- 创建SpringConfig类---设置配置类注解,设置注解扫描包,导入其他配置类,开启事务,导入配置文件
- 整合MyBatis---需要获取SqlSessionFactoryBean来创建SqlSessionFactory ,进而来创建SqlSession和创建MapperScannerConfigurer,用来扫描注册dao。创建配置SqlSessionFactoryBean需要DataSource
- 创建JdbcConfig类创建DataSource---根据jdbc.properties信息通过数据连接池中通过获取数据源DataSource和编写spring事务管理器,需要在SpirngConfig中开启事务@EnableTransactionManagement
- 配置SpringMvc:添加@Configuration注解,设置Controller包扫描,还需要WebApplicationInitializer来配置DispatcherServlet,而创建DispatcherServlet可以通过配置Servlet容器配置类进行继承AbstractDispatcherServletInitializer类或者AbstractAnnotationConfigDispatcherServletInitializer类
- 配置Servlet容器配置类:继承继承AbstractDispatcherServletInitializer类或者AbstractAnnotationConfigDispatcherServletInitializer类重写三个方法
SSM相关Maven依赖整理:
<!--依赖-->
<dependencies>
<!--SpringMVC相关依赖-->
<!--spring-webmvc包含了spring-context包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided<scop>
</dependency>
<!--JSP-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!-- JSP中JSTL标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--Json相关依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!--Mybatis相关依赖--------------------------- -->
<!--Spring的jdbc模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--Mybatis整合-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<!--测试相关依赖包 ------------------------------ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
<scope>test</scope>
</dependency>
<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
1.1 Spring
1.1.1 SpringConfig
@Configuration
@ComponentScan("com.sys")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig{
}
1.2 MyBatis
1.2.1 MybatisConfig
使用 SqlSessionFactoryBuilder创建SqlSessionFactory ,进而来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射语句,提交或回滚连接,最后,当不再需要它的时候, 你可以关闭 session。
public class MybatisConfif(){
//创建SqlSessionFactroyBean,设置数据源和别名包路径
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactroyBean();
SqlSessionFactoryBean.setDataSource(dataSource);
SqlSessionFactoryBean.setTypeAliasesPackage("com.sys.po");
return sqlSessionFactroyBean;
}
//创建MapperScannerConfigurer,用来扫描注册dao
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.sys.dao");
return mapperScannerConfigurer;
}
}
1.2.2 JdbcConfig
整合MyBatis:需要创建JdbcConfig类来根据jdbc.properties信息通过数据连接池中通过获取数据源DataSource
public class JdbcConfig{
@Value(${jdbc.driver})
private String drvier;
@Value(${jdbc.url})
private String url;
@Value(${jdbc.username})
private String username;
@Value(${jdbc.password})
private String password;
//配置获取数据源DataSource
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(drvier);
druidDataSource。setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//编写spring事务管理器,需要在SpirngConfig中开启事务@EnableTransactionManagement
@Bean
public PlatformTransactionManager transactionManager(DataSource data Source){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSourceTransactionManager);
return dataSourceTransactionManager;
}
}
当spring容器启动的时候,发现有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描看一下bean上是否有@Transaction注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring会通过aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务
1.2.3 jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://主机号:端口/数据库名称
jdbc.username=账号
jdbc.password=密码
1.3 SpringMVC
1.3.1 ServletConfig
继承AbstractDispatcherServletInitializer类实现它的三个方法:
AbstractDispatcherServletInitializer类的继承关系
tomcat 容器启动时,ServletContainerInitializer 实例化,然后把一系列标有HandlesTypes 注解的类,这里就是 SpringServletContainerInitializer 进行实例化,并被tomcat调用他的 onStartup()方法 。Set参数就是一系列 实现了 WebApplicationInitializer 接口的类,Set集合中的类统一进行 调用 initializer.onStartup(servletContext); 最终实现 tomcat 容器启动,将 DispatcherServlet 实例化,初始化,注册到tomcat中。
Public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer{
//创建一个SpringMVC容器--表现层
protected WebApplicationContext createrServletApplicationContext(){
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置哪些请求交给SpringMVC处理
protected String[] getServletMappings(){
retrn new String[]{"/"};
}
//创建spring容器 - service ,dao
protected WebApplicationContext createRootApplicationContext(){
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
继承AbstractAnnotationConfigDispatcherServletInitializer类实现它的三个方法:
Spring3.2中引入AbstractAnnotationConfigDispatcherServletInitializer就是WebApplicationInitializer的基础实现,所以当部署到servlet3.0容器中时,容器会发现它的子类,并用子类来配置Servlet上下文。
- getServletMappings():将一个或多个路径映射到DispatcherServlet上;
- getServletConfigClasses():返回的带有@Configuration注解的类用来配置DispatcherServlet;
- getRootConfigClasses():返回的带有@Configuration注解的类用来配置ContextLoaderListener;
public class ServletContainerInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
protected String[] getServletMappings(){
return new String[]{"/"};
}
protected Class<?>[] getServletConfigClasses(){
return new Class[]{SpringMvcConfig.class};
}
protected Class<?>[] getRootConfigClasses(){
return new Class[]{SpringConfig.class};
}
}
1.32 SpringMvcConfig
@Configuration
@ComponentScan("com.sys.controller")
@EnableWebMvc
public class SpringMvcConfig(){
}
二、功能模块
数据库表——>实体层——>DAO层——>Mapper层——>Service层——>ServiceImpl——>Controller层
- 根据数据库表来新建相对应实体类
2.1表与实体类
public class Book{
private Integer id;
private String type;
private String name;
priavte Stirng description;
set,get,构造函数等...
}
2.2 dao(接口+自动代理)
数据访问层、持久层
public interface BookDao {
//插入
@Insert("insert into tbl_book (type,name,description) values (#{type},#{name},#{description})")
Integer save(Book book);
//查询全部
@Select("select * from tbl_book")
List<Book> getAll();
//根据id查询
@Select("select * from tbl_book where id = #{id}"")
Book getById(Integer id);
}
2.3 service(接口+实现类)
业务逻辑层
接口类:
public class BookService {
Boolean save(Book book);
List<Book> getAll();
Book getById(Integer id);
}
实现类
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
public Boolean save(Book book){
Integer save = bookDao.save(book);
return save > 0;
}
public List<Book> getAll(){
return bookDao.getAll();
}
public Book getById(Integer id){
return bookDao.getById(id);
}
}
2.3.1 业务层接口测试(整合JUnit)
- 在test文件夹下面新建包结构:com.sys.service.BookServiceTest
- 使用@RunWith注解启用JUnit4来运行
@RunWith(JUnit4.class)就是指用JUnit4来运行
@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境
@RunWith(Suite.class)的话就是一套测试集合,
@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试
当一个类添加了注解@Component,那么他就自动变成了一个bean,就不需要再Spring配置文件中显示的配置了。把这些bean收集起来通常有两种方式,Java的方式和XML的方式。当这些bean收集起来之后,当我们想要在某个测试类使用@Autowired注解来引入这些收集起来的bean时,只需要给这个测试类添加@ContextConfiguration注解来标注我们想要导入这个测试类的某些bean。
package com.sys.service;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class) //因为是测试service所以SpringConfig.class
public class BokkServiceTest {
@Autowired
private BookService bookService;
@Test
public void testGetAll(){
List<Book> bookList = bookService.getAll();
System.outprintln(bookList);
}
@Test
public void getAll(){
Book book = new Book();
book.setName();
.....
Boolean save = bookService.save(book);
System.outprintln(save);
}
@Test
public void getById(){
Book book = bookService.getById(3);
System.outprintln(book);
}
}
2.4 controller
表现层
@RestController
@RequestMapping("/books")
public class BookController{
@Autowired
priavte BookService bookService;
@PostMapping
Boolean save(@RequestBody Book book){
Boolean isSave = bookService.save(book)
if(isSave){
return new Result(Code.SAVE_OK,"插入成功",isSave);
}else{
}
return ;
}
@GetMapping
List<Book> getAll(){
return bookService.getAll();
}
@GetMapping("/{id}")
Book getById(@PathVariable Integer id){
return bookService.getById(id);
}
}
三、项目中数据封装
3.1表现层数据封装
- 设置统一数据返回类,比如下面,可以根据项目需要进行更改和提供构造方法,方便操作
- 一般兴建一个vo包来存放VO代表值对象,它用于封装业务逻辑中的数据,并且在应用程序的不同层之间进行传递
public class Result {
private Object data;
private Interger code;
private String msg;
}
还可以封装一下Code字段,新建一个Code类
public calss Code{
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer SELECT_Ok = 20041;
public static final Integer SAVE_ERR = 20010;
.....
}
3.2 异常处理器
出现异常现象的常见位置与常见诱因如下:
异常常见位置 | 异常常见诱因 |
---|---|
框架内部抛出的异常 | 因使用不合规导致 |
数据层抛出的异常 | 因外部服务器故障导致(例如:服务器访问超时) |
业务层抛出的异常 | 因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常) |
工具类抛出的异常 | 因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等) |
在系统当中, Dao、Service、Controller层代码出现都可能抛出异常。如果哪里产生异常就在哪里处理,则会降低开发效率。所以一般情况下我们会让异常向上抛出,最终到达DispatcherServlet中,此时SpringMVC提供了异常处理器进行异常处理,这样可以提高开发效率
3.2.1 单个控制器异常处理
使用实现单个控制器异常处理@ExceptionHandler注解
@Controller
public class MyController {
// 处理单个Controller异常
@RequestMapping("/t1")
public String t1(){
String str = null;
// str.length();
// int flag = 1/0;
int []arr = new int[1];
arr[2] = 10;
return "index";
}
/**
* 异常处理方法
* @param ex 异常对象
* @param model 模型对象
* @return
*/
// 添加@ExceptionHandler,表示该方法是处理异常的方法,属性为处理的异常类
@ExceptionHandler({java.lang.NullPointerException.class,java.lang.ArithmeticException.class})
public String exceptionHandler1(Exception ex, Model model){
// 向模型中添加异常对象
model.addAttribute("msg",ex);
// 跳转到异常页面
return "error";
}
// 方法一不能处理的异常交给方法二处理
@ExceptionHandler({java.lang.Exception.class})
public String exceptionHandler2(Exception ex,Model model){
model.addAttribute("msg",ex);
return "error2";
}
3.2.2 全局异常处理
- 在控制器中定义异常处理方法只能处理该控制器类的异常,要想处理所有控制器的异常,需要定义全局异常处理类。主要是使用@ControllerAdvice或者@RestControllerAdvice注解来创建一个异常处理器
- 在项目中创建给exception包来创建一个异常处理器类ProjectExceptionAdvice,不要忘了在SpringConfig包扫描中标注此包
- 创建自定义异常,继承RuntimeException类重载器构造函数
- 在全局异常处理器中书写相对应异常捕获方法,用@ExceptionHandler注解后面选择捕获异常类型,传入书写的自定义异常类
- 使用异常:可能出现异常的代码使用try catch来捕获抛出异常到new一个自定义异常类中,
- 我们的全局异常处理器就会捕获到,进入处理器相对应的方法,执行一些横向开发的工作,比如通知运维人员报错了啊等等
@ControllerAdvice结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到全局不同类型的异常区别处理的目的。
- @ExceptionHandler这个注解表示Controller中任何一个方法发生异常,则会被注解了@ExceptionHandler的方法拦截到。对应的异常类执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方法
@RestControllerAdvice
public class ProjectExceptionAdvice{
@ExceptionHandler(Exception.class)
public Result doException(自定义异常类 e){
return new Result(e.getCode(),e.getMessage(),null);
}
}
3.2.3 异常处理涉及注释
- @ControllerAdvice注解作用原理
Spring首先前端控制器DispatcherServlet对象在创建时会初始化一系列的对象,里面包含了initHandlerAdapters(context)和initHandlerExceptionResolvers(context)这两个方法,是我们重点关注的。
initHandlerAdapters(context)方法会取得所有实现了HandlerAdapter接口的bean并保存起来,其中就有一个类型为RequestMappingHandlerAdapter的bean,这个bean就是@RequestMapping注解能起作用的关键,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理:找到所有ModelAttribute标注的方法并缓存起来,找到所有InitBinder标注的方法并缓存起来。经过处理之后,@ModelAttribute和@InitBinder就能起作用了。
DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,七张就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理
- @RestControllerAdvice和@ControllerAdvice
@RestControllerAdvice是一个组合注解,是@ResponseBody和@ControllerAdvice的组合,而函数体中用到了@AliasFor注解,因此可以通过@RestControllerAdvice的属性传递将属性值传给@ControllerAdvice。
3.2.4 异常处理方案
- 项目异常分类
- 业务异常(BusinessExceptoin)
- 规范的用户行为产生异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期的异常
- 业务异常(BusinessExceptoin)
我们可以在相应的exception包下书写相对应异常的处理器,利用继承RuntimeException类,实现构造器,为什么要继承这个类?
- 不需要在方法签名中声明:由于运行时异常不需要在方法上显式声明或捕获,所以继承 RuntimeException 的自定义异常可以在方法中抛出而无需修改方法签名。
- 异常不需要被强制捕获:运行时异常不需要被强制捕获,开发者可以选择是否捕获并处理这些异常。这样可以减少代码的冗余性,简化异常处理逻辑。
- 适合表示程序错误或逻辑问题:运行时异常通常用于表示程序错误、逻辑问题或不可恢复的错误情况。当出现这些异常时,通常意味着程序处于一个不可恢复的状态,需要进行修复或终止程序的执行。
public class BusinessException extends RuntimeException{
private Integer code;
public BusinessException(Integer code , String message){
super(message);
this.code = code;
}
public BusinessException(Integer code,Stirng message, Throwable cause){
super(message,cause);
this.code = code;
}
code,set,get方法
}
四、拦截器
4.1 过滤器和拦截器区别
Filter(过滤器):过滤器工作在 Servlet 容器中,它拦截客户端的请求和服务器的响应。过滤器链(Filter Chain)是多个过滤器按照一定的顺序执行的集合,一个请求可以依次通过多个过滤器,然后到达目标 Servlet,响应也会按相反的顺序经过这些过滤器返回给客户端。
Interceptor(拦截器):拦截器工作在 Spring 的 DispatcherServlet 和具体的 Controller 之间。当一个请求被发送到 Spring MVC 应用时,DispatcherServlet 首先接收到这个请求,然后根据配置的拦截器链对请求进行预处理,最后将请求转发到相应的 Controller 进行处理。
4.2 拦截器实现
- 拦截器是与 Spring MVC 的 DispatcherServlet 集成的。当一个请求被 DispatcherServlet 接收后,它会处理和调度这个请求到相应的处理器(Controller)。
- 在请求到达 Controller 之前,Spring 会根据请求的 URL 查找对应的 Handler Mapping,然后确定相应的 Controller。
- 在请求达到 Controller 之前,以及响应返回给客户端之前,拦截器链中的拦截器会被依次调用。
- 开发者可以自定义拦截器,并通过实现 WebMvcConfigurer 接口的 addInterceptors 方法将它们添加到应用中。
实现步骤:
- 创建拦截器类:
- 实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter 类。
- 重写 preHandle、postHandle 和 afterCompletion 方法。
- 注册拦截器:
- 创建一个配置类,实现 WebMvcConfigurer 接口或者继承WebMVcConfigurationSupport类。
- 重写 addInterceptors 方法来添加拦截器。
- 编写拦截逻辑:
- 在 前置处理preHandle、后置处理postHandle 和完成后处理 afterCompletion 方法中实现具体的拦截逻辑。
4.3 处理器链
pre1 --> pre2 --> pre3 -->controller --> post3 --> post2 --> post1 --> after3 --> after2 --> after1
如果前置pre3 返回false 则也跳过 controller 和全部post环节 还有自己的 after环节,执行after2,after1
- 在请求达到 Controller 之前,以及响应返回给客户端之前,拦截器链中的拦截器会被依次调用。
- 开发者可以自定义拦截器,并通过实现 WebMvcConfigurer 接口的 addInterceptors 方法将它们添加到应用中。
- 实现:书写多个处理器,然后在注册的时候全部写入,执行拦截器按照addInterceptor的代码位置
//注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor1;
@Autowired
private MyInterceptor myInterceptor2;
@Autowired
private MyInterceptor myInterceptor3;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor1).addPathPatterns("/**");
registry.addInterceptor(myInterceptor2).addPathPatterns("/books","/books/*");
registry.addInterceptor(myInterceptor3).addPathPatterns("/books");
}
}
4.4代码演示
创建一个interceptor包来书写拦截器类
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 请求处理前的逻辑
return true; // 返回 true 继续流程,返回 false 中断流程
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// 请求处理后的逻辑,但在视图渲染前
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 请求处理完毕后的逻辑
}
}
注册拦截器:实现WebMvcConfigurer接口,重写 addInterceptors 方法来添加拦截器,重写addResourceHandlers可以设置静态资源访问路径
我们这里把拦截器剔出到一个SpirngWebSupport配置类,然后在SpringMvcConfig类中用注解@Improt导入即可
@Configuration
public class SpringWebSupport implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
//设置静态资源访问路径
puablic void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/pages/**").addResourceLocation("/pages/")
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 应用于所有路径(可以注入自定义拦截器类就不用new了)
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
}
五、注意事项
5.1 注释包扫描范围设置
- 精准扫描
在包扫描注解利用数组写下多个需要扫描的路径
@ComponentScan({"com.sys.service","com.sys.dao"})
- 排除扫描
利用包扫描注解中Value和excludeFilters参数来排除子包
@ComponentScan(value = "com.sys",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class))