基本案例
流程分析
1.创建工程
- 创建一个Maven的web工程
- pom.xml添加SSM需要的依赖jar包
- 编写Web项目的入口配置类,实现AbstractAnnotationConfigDispatcherServletInitializer重写以下方法
- getRootConfigClasses() :返回Spring的配置类->需要SpringConfig配置类
- getServletConfigClasses() :返回SpringMVC的配置类->需要SpringMvcConfig配置类
- getRootConfigClasses() :getServletMappings() : 设置SpringMVC请求拦截路径规则
- getRootConfigClasses() :getServletFilters() :设置过滤器,解决POST请求中文乱码问题
2.SSM整合
-
SpringConfig
- 标识该类为配置类 @Configuration
- 扫描Service所在的包 @ComponentScan
- 在Service层要管理事务 @EnableTransactionManagement
- 读取外部的properties配置文件 @PropertySource
- 整合Mybatis需要引入Mybatis相关配置类 @Import
- 第三方数据源配置类 JdbcConfig
- 构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素, @Bean @Value
- 构建平台事务管理器,DataSourceTransactionManager,@Bean
- Mybatis配置类 MybatisConfig
- 构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
- 构建MapperScannerConfigurer并设置DAO层的包扫描
- 第三方数据源配置类 JdbcConfig
-
SpringMvcConfig
- 标识该类为配置类 @Configuration
- 扫描Controller所在的包 @ComponentScan
- 开启SpringMVC注解支持 @EnableWebMvc
3.功能模块
- 创建数据库表
- 根据数据库表创建对应的模型类
- 通过Dao层完成数据库表的增删改查(接口+自动代理)
- 编写Service层[Service接口+实现类]
- @Service
- @Transactional
- 整合Junit对业务层进行单元测试
- @RunWith
- @ContextConfiguration
- @Test
- 编写Controller层
- 接收请求 @RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping
- 接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
- @RequestParam
- @PathVariable
- @RequestBody
- 转发业务层
- @Autowired
- 响应结果
- @ResponseBody
环境准备
1.添加依赖
pom.xml添加SSM所需要的依赖jar包:
- SpringMVC相关:javax.servlet-api,spring-webmvc(包括了spring-context),javkson-databind
- Spring相关:spring-jdbc,spring-test,junit
- 数据库相关:mybatis,mybatis-spring,mysql,druid
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itnkb</groupId>
<artifactId>day02_1_ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>day02_1_ssm Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</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.1.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<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.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.创建SpringConfig配置类
@Configuration
@ComponentScan({"com.itnkb.dao","com.itnkb.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
2.创建JdbcConfig配置类
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
}
3.创建MybatisConfig配置类
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.itnkb.domain");
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itnkb.dao");
return msc;
}
}
4.创建jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=root
5.加载SpringMvcConfig配置类
@Configuration
@ComponentScan("com.itnkb.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
6.创建Servlet配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter =new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
代码实现
1.创建数据库及表
create database ssm_db character set utf8;
use ssm_db;
create table tbl_book(
id int primary key auto_increment,
type varchar(20),
name varchar(50),
description varchar(255)
)
insert into `tbl_book`(`id`,`type`,`name`,`description`) values (1,'计算机理论','Spring实战 第五版','Spring入门经典教程,深入理解Spring原理技术内幕'),(2,'计算机理论','Spring 5核心原理与30个类手写实践','十年沉淀之作,手写Spring精华思想'),(3,'计算机理论','Spring 5设计模式','深入Spring源码刨析Spring源码中蕴含的10大设计模式'),(4,'计算机理论','Spring MVC+Mybatis开发从入门到项目实战','全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'),(5,'计算机理论','轻量级Java Web企业应用实战','源码级刨析Spring框架,适合已掌握Java基础的读者'),(6,'计算机理论','Java核心技术 卷Ⅰ 基础知识(原书第11版)','Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新'),(7,'计算机理论','深入理解Java虚拟机','5个纬度全面刨析JVM,大厂面试知识点全覆盖'),(8,'计算机理论','Java编程思想(第4版)','Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉'),(9,'计算机理论','零基础学Java(全彩版)','零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术'),(10,'市场营销','直播就这么做:主播高效沟通实战指南','张三李四王五成长为网红的秘密都在书中'),(11,'市场营销','直播销讲实战一本通','和小明一起学系列网络营销书籍'),(12,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
2.编写Book模型类
package com.itnkb.domain;
public class Book {
private Integer id;
private String type;
private String name;
private String description;
//setter,getter方法省略
}
3.编写Dao接口
public interface BookDao {
@Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
public void save(Book book);
@Update("update tbl_book set type=#{type}, name=#{name}, description=#{description} where id=#{id}")
public void update(Book book);
@Delete("delete from tbl_book where id=#{id}")
public void delete(Integer id);
@Select("select * from tbl_book where id=#{id}")
public Book getById(Integer id);
@Select("select * from tbl_book")
public List<Book> getAll();
}
4.编写Service接口和实现类
//业务注解,搭配SpringConfig的@EnableTransactionManagement和JdbcConfig里的PlatformTransactionManager的bean对象
@Transactional
public interface BookService {
/**
* 保存操作
* @param book
* @return
*/
public boolean save(Book book);
/**
* 修改操作
* @param book
* @return
*/
public boolean update(Book book);
/**
* 根据ID删除操作
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 根据ID查询操作
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 查询所有操作
* @return
*/
public List<Book> getAll();
}
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
bookDao.save(book);
return true;
}
@Override
public boolean update(Book book) {
bookDao.update(book);
return true; }
@Override
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
5.编写Controller类
@RestController
@RequestMapping("/books")
@EnableWebMvc
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return bookService.delete(id);
}
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
@GetMapping
public List<Book> getAll() {
return bookService.getAll();
}
}
单元测试
编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
public void testGetById(){
Book book = bookService.getById(1);
System.out.println(book);
}
@Test
public void testAll(){
List<Book> books = bookService.getAll();
System.out.println(books);
}
}
查询单个 功能测试
查询所有 功能测试
PostMan测试表现层
新增
修改
删除
查询单个
查询所有
统一结果封装,前后端数据传输协议
在后台向前端传输数据时,希望后台返回统一的数据结果,前端按照一种方式解析,开发就会变得更加简单。
- 通过创建结果模型类来实现
- data:封装返回的数据
- code:封装返回状态码(操作结果),即何种操作及是否操作成功
- msg:封装特殊消息,如操作失败后为了封装返回的错误信息
代码实现
对于结果封装,我们应该是在表现层进行处理,所以我们把Result类和Code类放在controller包下,当然也可以放在domain包
Result类
public class Result {
private Object data;
private Integer code;
private String message;
public Result() {
}
public Result(Integer code , Object data) {
this.data = data;
this.code = code;
}
public Result( Integer code, Object data , String message) {
this.data = data;
this.code = code;
this.message = message;
}
//setter、getter方法已省略
}
Code类
public class Code {
public static final int SAVE_OK = 20011;
public static final int DELETE_OK = 20021;
public static final int UPDATE_OK = 20031;
public static final int GET_OK = 20041;
public static final int SAVE_ERR = 20010;
public static final int DELETE_ERR = 20020;
public static final int UPDATE_ERR = 20030;
public static final int GET_ERR = 20040;
}
修改Controller类的代码
@RestController
@RequestMapping("/books")
@EnableWebMvc
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book) {
Boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);
}
@PutMapping
public Result update(@RequestBody Book book) {
Boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
Boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败!";
return new Result(code,book,msg);
}
@GetMapping
public Result getAll() {
List<Book> books = bookService.getAll();
Integer code = books != null ? Code.GET_OK : Code.GET_ERR;
String msg = books != null ? "" : "数据查询失败!";
return new Result(code,books,msg);
}
}
统一异常处理
在运行项目的过程中,各层中都有可能出现问题,而且这些异常是不能避免的。所以我们就得将异常进行处理。
1.各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
2.异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类
3.表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP思想
代码实现
创建异常处理类
/**
* @RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
*/
@RestControllerAdvice
public class ProjectExceptionAdvice {
/**
* 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
* @ExceptionHandler异常处理器
*/
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("捕获异常!")
return new Result(666,null,"捕获异常!");
}
}
异常处理方案
异常分类
- 业务异常(BusinessException)是用户问题:
- 规范的用户行为产生的异常
- 用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
- 不规范的用户行为操作产生的异常
- 如用户故意传递错误数据
- 规范的用户行为产生的异常
- 系统异常(SystemException)是系统问题:
- 项目运行过程中可预计但无法避免的异常
- 比如数据库或服务器宕机
- 项目运行过程中可预计但无法避免的异常
- 其他异常(Exception)是程序员问题:
- 编程人员未预期到的异常,如:用到的文件不存在
异常解决方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
拦截器
概念
拦截器概念
拦截器:
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前、后执行预先设定的代码
- 阻止原始方法的执行
- 总结:拦截器就是用来做增强,在不改变原始方法基础上,对其前中后进行拦截、放行、运行相关代码
拦截器和过滤器之间的区别:
- 归属不同:Filter属于Servlet技术,依赖于Servlet容器;Interceptor属于SpringMVC技术,不依赖于servlet容器。
- 拦截内容不同:Filter对所有访问进行增强,几乎对所有请求起作用;Interceptor仅针对SpringMVC的访问进行增强,只能对action请求起作用。
- 调用次数不同:在action的生命周期中,过滤器只能在容器初始化时被调用一次,而拦截器可以多次被调用。
- 获取bean的权限不同:过滤器不能获取IOC容器中的各个bean;拦截器就可以,因为拦截器本身是个bean。这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
- 底层机制不同:过滤器是基于函数回调,拦截器是基于java的反射机制的
代码实现
方法一(底层麻烦): SpringMvcSupport 在config目录下
@Component
/**
* 定义拦截器类 --实现HandlerInterceptor接口
* 注意当前类必须受Spring容器控制
*/
public class ProjectInterceptor implements HandlerInterceptor {
@Override
//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
HandlerMethod hm = (HandlerMethod)handler;
System.out.println("preHandle..."+contentType);
return true;
}
@Override
//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
//原始方法调用完成后执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
方法二(简单快速):在SpringMvcConfig中添加拦截器,淘汰SpringMvcSupport
@Configuration
//package1.config主要为了扫描项目拦截器类、ServletContainersInitConfig类
@ComponentScan({"package1.controller","package1.config"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterception projectInterception;
//添加拦截器,配置本地资源映射路径,在访问A(虚拟的)的时候,需要到B(实际的)的位置去访问。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterception).addPathPatterns("/books","/books/*");
//如果只拦截/books,发送http://localhost/books/100后会发现拦截器没有被执行
//registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
}
//添加资源处理器
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
拦截链
代码实现
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...222");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...222");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...222");
}
}
顺序
拦截器执行的顺序是和配置顺序有关,先进后出
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行。