SpringMVC
可以用于替代Servlet,也就是Servlet能做的SpringMVC都能做
参数注解(重要)
最新的spring控制层传参的时候如果参数是从url获取的,都要用参数注解注明参数名便于获取,区分各种类型的参数注解
@RequestBody
从json数据获取,也就是从请求体中获取的,参数前加@RequestBody注解
@RequestParam("参数名")
从url中获取(GET请求),需要指定参数名
@PathVariable("参数名")
从url中获取但是是REST风格的url
简介
概述
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架,优点在于使用简单,开发便捷(相对于Servlet),且灵活性强
SpringMVC是一种表现层框架技术
SpringMVC用于进行表现层功能开发
入门案例
导入坐标
先导入servlet的坐标和springMVC的坐标
xml
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.7</version>
</dependency>
</dependencies>
创建SpringMVC控制器类(等同Servlet功能)
先用@Controller注解把类定义为一个控制类Bean
然后用@RequestMapping("/调用名称")来给控制类的方法设置调用名称,调用名称一般在类上也用@RequestMapping注解加上前缀名,tomcat运行的时候完整的调用名称是前缀名加方法调用名(比如/user/save),因为BookController和UserController可能都有save方法,需要用前缀名加以区别
然后用@ResponseBody注解(这个注解相当于告诉SpringMVC该方法的返回值无需找视图,只需要转换成数据直接返回给客户端)
UserControllerImpl
java
@Controller
@RequestMapping("/user")
public class UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
public String save(){
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
}
创建SpringMVC配置类
创建springmvc配置类,跟spring配置类一样用@ComponentScan扫描Bean,用@Configuration声明配置身份
SpringMVCConfig
java
@Configuration
@ComponentScan("com.morgan.controller")
public class SpringMVCConfig {
}
创建servlet启动容器的配置类,在里面加载spring的配置
创建一个servlet容器启动的配置类,在里面加载spring的配置,继承AbstractDispatcherServletInitializer类,实现父类的三个方法(加载springmvc容器配置,设置归属springmvc处理的请求,加载spring配置)
加载springmvc容器配置的方法createServletApplicationContext的内容跟之前学spring-framework的时候加载spring差不多,但要注意之前的AnnotationConfigApplicationContext不适用web环境,需要换成AnnotationConfigWebApplicationContext,然后加载SpringMVC配置类
设置归属springmvc处理的请求把return的String[0]改成String[]{"/"}表示所有的请求都归springmvc处理
spring配置的加载暂时不需要动,跟原来一样return null就行
ServletContainerInitConfig
java
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置(现在暂时return null就行后面会再写Spring配置)
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
静态页面的放行
因为后续会涉及到web的静态页面的放行问题,比如html应该由tomcat来管理但是由于在servlet启动容器配置类中把所有请求都归springmvc处理的,就会出问题,这时候就需要把这些静态资源放行
比如如果所有资源都是springmvc管理,那在controller类中定义方法跳转到jsp页面可以跳转,但在浏览器直接输jsp页面就进不去,因为这些本来应该由tomcat管理,但现在都交给springmvc管理了就导致直接在地址栏输入用不了
创建SpringMVCSurpport类,打上@Configuration注解,在里面配置不交由springmvc管理的模块
然后在SpringMVC配置类中@ComponentScan({"com.morgan.controller","com.morgan.config"}),加上config包来扫描到SpringMVCSupport类
SpringMVCConfig
java
@Configuration
@ComponentScan({"com.morgan.controller","com.morgan.config"})
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
}
SpringMVCSupport
java
@Configuration
public class SpringMVCSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//表示检测到前面形式的url的时候访问后面这个包,下面还可以以相同类型继续添加包
registry.addResourceHandler("/page/**").addResourceLocations("/page/");
}
运行
配置tomcat,启动,转到/save
bean的加载和控制
SpringMVC要加载的Bean只有controller层的(因为是servlet功能负责的前后端对接),其他的Bean仍由Spring管理,那就需要避免Spring加载到SpringMVC已经加载的Bean
SpringMVC只需要正常加载com.morgan.controller包就行
而Spring要想避免加载到SpringMVC已经加载的包有两种方式,精确的加载到service包,dao包等或者加载到com.morgan但排除掉controller包
先创建Spring配置类
然后在servlet启动容器配置类中加载Spring配置类
ServletContainerInitConfig
java
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
然后在spring中加载包
精确加载(常用)
java
@Configuration
@ComponentScan({"com.morgan.dao","com.morgan.service"})
public class SpringConfig {
}
排除加载
java
@Configuration
@ComponentScan({"com.morgan.dao","com.morgan.service"})
/*@ComponentScan(value = "com.morgan",
excludeFilters = @ComponentScan.Filter(
//意思是按注解排除
type = FilterType.ANNOTATION,
//意思是排除注解为@Contoller的Bean
classes = Controller.class
)
)*/
public class SpringConfig {
}
请求与响应
请求映射路径
用@RequestMapper()注解来写请求映射路径
写在类上的是给本类所有方法设置的前缀名\
写在方法上的是检索名
java
@Controller
@RequestMapping("/user")
public class UserControllerImpl implements UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
@Override
public String save() {
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
@RequestMapping("/delete")
@ResponseBody
@Override
public String delete() {
return "{'module':'springmvc-delete'}";
}
}
用请求发送参数
普通参数
GET请求
在url后面加上?参数1名=参数1值&参数2名=参数2值
然后在需要接收参数的方法中以方法参数的形式接收参数,注意在每个参数前面要加上@RequestParam("参数名")注解来注明要接收的参数名
java
@Controller
@RequestMapping("/user")
public class UserControllerImpl implements UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
@Override
public String save() {
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
@RequestMapping("/delete")
@ResponseBody
@Override
public String delete(@RequestParam("name") String name,@RequestParam("age") int age) {
System.out.println(name);
System.out.println(age);
return "{'module':'springmvc-delete'}";
}
}
url为http://localhost/mvc_example1_war_exploded/user/delete?name=sad\&age=15
POST请求
POST请求需要在表单里提交,这里我们用Postman进行测试,提交数据,后端程序跟GET一样不用动
但POST需要处理中文乱码问题需要在servlet启动容器配置类中再实现一个方法getServletFilters(SpringMVC自己的方法不是自己写的),在方法里创建CharacterEncodingFilter对象filter作为过滤器,filter.setEncoding("UTF-8")来设定过滤器,最后返回new Filter[]{filter}数组来设定过滤器,如果有多个过滤器在大括号用逗号隔开往后加就行
ServletContainerInitConfig
java
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter=new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
实体类参数
假如要传的是一个实体类User
User
java
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
因为有getter和setter方法,要获取参数的controller方法就可以直接获取参数
UserControllerImpl
java
@RequestMapping("/getEntity")
@ResponseBody
@Override
public String getEntity(User user) {
System.out.println(user);
return "{'module':'springmvc-getEn'}";
}
在url中还是正常的传name和age两个成员变量的值
如果成员变量有实体类变量,比如User的一个变量是实体类Address变量address,那传参的时候用address.province这种形式,多层实体类变量就一直点就行
数组参数
定义数组参数,比如String[] likes,然后在url用likes传多个值,比如?likes=ad&likes=ac这样,注意key必须都是likes
注意加@RequestParam注解
UserControllerImpl
java
@RequestMapping("/getArray")
@ResponseBody
@Override
public String getArray(@RequestParam("likes") String[] likes) {
System.out.println(Arrays.toString(likes));
return "{'module':'springmvc-getArr'}";
}
集合参数
集合参数跟上面数组基本一模一样
java
@RequestMapping("/getList")
@ResponseBody
@Override
public String getList(@RequestParam("likes") List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getList'}";
}
json数据参数
先导入坐标
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
在SpringMVC配置类上加上@EnableWebMvc注解开启json数据转换
SpringMVC
java
@Configuration
@ComponentScan("com.morgan.controller")
@EnableWebMvc
public class SpringMVCConfig {
}
把接收参数的地方参数前加上@RequestBody注解(相当于把@RequestParam改成这个,因为json数据是放在请求体里的而不是请求参数)
UserControllerImpl
java
@RequestMapping("/getJson")
@ResponseBody
@Override
public String getJson(@RequestBody List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getJson'}";
}
Postman里发送json数据用body里的raw的json格式发送就行
json数组
都跟上面一样,Postman里要测试json数据用中括号字符串数组就行["a","b"]
java
@RequestMapping("/getJson")
@ResponseBody
@Override
public String getJson(@RequestBody List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getJson'}";
}
json的实体类对象
也跟上面一样,Postman里要测试json数据用大括号键值对,如果有多层实体类就是多层大括号键值对
{"name":"张三",
"age":"15",
"address":{"province":"taiwan","city":"qinghai"}
}
java
@RequestMapping("/getJsonPojo")
@ResponseBody
@Override
public String getJsonPojo(@RequestBody User user) {
System.out.println(user);
return "{'module':'springmvc-getJsonPojo'}";
}
json的实体类数组
List类型的参数
就中括号里套大括号就行
{"name":"张三", "age":"15", "address":{"province":"taiwan","city":"qinghai"} }, {"name":"李四", "age":"12", "address":{"province":"taiwan","city":"qinghai"} }
java
@RequestMapping("/getJsonPojoList")
@ResponseBody
@Override
public String getJsonPojoList(@RequestBody List<User> list) {
System.out.println(list);
return "{'module':'springmvc-getJsonPojoList'}";
}
日期参数
传递日期型参数可以直接用Date参数,这样子传递的数据格式应当是yyyy/MM/dd默认格式,如果要其他格式用@DateTimeFormat(pattern="格式")注解,这样子改的格式既可以是年月日,还可以带上时间,当然@RequestParam注解也要加
java
@RequestMapping("/getDateParam")
@ResponseBody
@Override
public String getDateParam(@RequestParam("date1") Date date1,@RequestParam("date2") @DateTimeFormat(pattern="yyyy-MM-dd") Date date2,@RequestParam("date3") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date3) {
System.out.println(date1);
System.out.println(date2);
System.out.println(date3);
return "{'module':'springmvc-getDateParam'}";
}
响应
注意,如果没有@ResponseBody这个注解,SpringMVC管理controller方法默认都是返回一个字符串,然后把这个字符串解析为一个页面
所以无论是向返回字符串还是返回json数据都需要加@ResponseBody注解,只有要跳转的时候不加
响应页面
响应页面就只需要一个@RequestMapping("/响应名")注解就可以了,因为之前一直用的注解@ResponseBody的作用是把返回的内容作为文本数据返回而不解析
UserController
java
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转中");
return "/page.jsp";
}
}
页面会跳转到jsp文件page上
响应字符串
加上@ResponseBody注解就行了
UserController
java
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回数据");
return "page.jsp";
}
最后返回字符串page.jsp而不是jsp页面
响应json数据
响应实体类对象
跟响应字符串差不多
User
java
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
UserController
java
@RequestMapping("/toPojo")
@ResponseBody
public User toPojo(){
System.out.println("返回pojo");
User user=new User();
user.setName("zhang");
user.setAge(15);
return user;
}
响应实体类集合
跟实体类差不多
UserController
java
@RequestMapping("/toPojoList")
@ResponseBody
public List<User> toPojoList(){
System.out.println("返回pojo");
//注意这里不要用一个User对象传两次,因为list.add方法往list中传user对象的时候传的是对象本事不是对象的副本
User user1 =new User();
user1.setName("zhang");
user1.setAge(15);
User user2=new User();
user2.setName("li");
user2.setAge(11);
List<User> list=new ArrayList<>();
list.add(user1);
list.add(user2);
return list;
}
REST
简介
格式
http://localhost/users 查询全部用户信息 GET(查询)
http://localhost/users/1 查询指定用户信息 GET(查询)
http://localhost/users 添加用户信息 POST(新增/保存)
http://localhost/users 修改用户信息 PUT(修改/更新)
http://localhost/users/1 删除用户信息 DELETE(删除)
注意描述模块的名称通常用复数表示此类资源而非单个资源,例如:users,books
根据REST风格对资源进行访问称为RESTful
优点
隐藏资源的访问行为,无法通过地址得知对资源是何种操作
书写简化
RESTful开发
普通开发
url传参是直接以/参数的形式
若不需要传参,按REST风格开发,@RequestMapping(value="/users",method=RequestMethod.GET)这种形式,同一个类所有的访问名都写成一样的(就像users这种风格),然后通过method来区分,这样只能限定到六个方法,基本满足简单开发,但实际开发更复杂,会扩展REST,这个后面再说
若要传参(查询指定用户信息或删除这种),在后端开发时在方法的参数前加上@PathVariable("参数名")表示这个参数是从url上获取的(这个用来替代@RequestParam注解),然后在访问名后面用/{参数名}的形式指定参数获取@RequestMapping(value="/users/{id}",method=RequestMethod.GET)
java
@Controller
public class RestController {
// 添加用的发送方式是post
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String insert(){
System.out.println("insert");
return "insert";
}
// 在参数前面加上@PathVariable注解表示参数是从url路径中获取的,删除的发送方式是delete,/user/{id}表示/user路径后面的值传给id参数
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable("id") Integer id){
System.out.println(id);
return "delete";
}
// 查询用的是GET
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String select(){
System.out.println("select");
return "select";
}
// 查询用的是GET,从路径上获取参数
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
@ResponseBody
public String selectById(@PathVariable("id") Integer id){
System.out.println(id);
return "select";
}
// 修改用的是PUT
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(){
System.out.println("update");
return "update";
}
}
快速开发
由于访问名,@ResponseBody都是公用的,可以@RequestMapping("/访问名")和@ResponseBody直接放在类上表示整个类所有方法通用,然后@Controller注解和@ResponseBody注解可以合并成@RestController注解,方法注解@RequestMapping(method=RequestMethod.GET)可以直接简化为@GetMapping,如果是需要参数的就是@GetMapping("/{参数名}")
java
@RestController
@RequestMapping("/books")
public class BookController {
// 添加用的发送方式是post
@PostMapping
public String insert(){
System.out.println("insert");
return "insert";
}
// 在参数前面加上@PathVariable注解表示参数是从url路径中获取的,删除的发送方式是delete,/user/{id}表示/user路径后面的值传给id参数
@DeleteMapping({"/{id}"})
public String delete(@PathVariable("id") Integer id){
System.out.println(id);
return "delete";
}
// 查询用的是GET
@GetMapping
public String select(){
System.out.println("select");
return "select";
}
// 查询用的是GET,从路径上获取参数
@GetMapping({"/{id}"})
public String selectById(@PathVariable("id") Integer id){
System.out.println(id);
return "select";
}
// 修改用的是PUT
@PutMapping
public String update(){
System.out.println("update");
return "update";
}
}
SSM整合
整合配置
坐标导入
xml
<dependencies>
<!-- springmvc-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.7</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
包框架
controller: 控制层,前后端交互
entity: 实体类,对应数据库的表
dao: 数据层,通过sql语句进行对数据库数据的操作
service: 功能层,通过对dao层的应用实现功能,需要子包impl(面向接口编程降低耦合度,dao层不需要是因为mybatis开发是自动代理直接用的接口不需要实现类,controller层不需要是因为需要应用mvc整合前后端,所有注解都基于实现类开发)
config: 配置,存放所有的配置类
配置
jdbc配置类(依赖于spring配置类)
通过外部properties文件导入数据库相关配置并存为一个DataSource类型的Bean方便后面mybatis配置使用
注意properties文件的导入不是直接导入到jdbc配置类中的而是导入到spring配置类中,然后jdbc配置类和mybatis配置类也导入spring配置类中,这三个都在一个位置就可以相互引用了,所以说jdbc配置类和mybatis配置类包括properties文件实际上都是spring配置类的一部分
JdbcConfig
java
//数据库连接配置类
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("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
Mybatis配置类(依赖于Spring配置类)
造一个存放数据库信息的工厂bean和获取代理开发用的mapper的bean,注意数据库信息存储的DataSource对象是通过spring按类型直接从Jdbc配置类中按类型获取的bean
MybatisConfig
java
//mybatis配置类
public class MybatisConfig {
//造一个工厂Bean用来存放数据库信息
//DataSource对象是spring按类型直接从jdbc配置类中获取的bean
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//给实体类起别名,默认为类名首字母小写,如果不起就需要写完整包名很麻烦
factoryBean.setTypeAliasesPackage("com.morgan.entity");
return factoryBean;
}
// 配置一个类来扫描数据层dao包获取mapper进行代理开发
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer=new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.morgan.dao");
return mapperScannerConfigurer;
}
}
Spring配置类
SpringConfig
java
//Spring配置类
@Configuration
@ComponentScan({"com.morgan.service"})
//引入文件资源的注解
@PropertySource("classpath:jdbc.properties")
//导入相关需要依赖的配置类的注解
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
SpringMVC配置类
SpringMVCConfig
java
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
public class SpringMVCConfig {
}
Web配置类
用来加载spring配置类和springmvc配置类并设定交给springmvc管理的请求
ServletConfig
java
//web配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 配置Spring配置类(根配置)
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// 配置springmvc配置类(web相关配置)
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
// 配置要交给springmvc配置的请求,在这里把所有的请求交给springmvc,若要交给tomcat再单独配置
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
功能实现
1.用注解开发dao层
BookDao(接口)
java
public interface BookDao {
//注意id设置的自增不需要写,前面的type是数据库字段,后面的type是参数book里的变量,表示把变量的值给到数据库2
@Insert("insert into tbl_book (type,name,description) values (#{type},#{name},#{description})")
void insert(Book book);
@Update("update tbl_book set type=#{type},name=#{name},description=#{description} where id=#{id}")
void update(Book book);
@Delete("delete from tbl_book where id=#{id}")
void delete(Integer id);
@Select("select * from tbl_book")
List<Book> selectAll();
@Select("select * from tbl_book where id=#{id}")
Book selectById(Integer id);
}
2.根据dao层方法实现service层功能
java
public interface BookService {
/**
* 添加一组数据
* @param book
*/
boolean insert(Book book);
/**
* 按id修改某行数据
* @param book
*/
boolean update(Book book);
/**
* 按id删除某行数据
* @param id
*/
boolean delete(Integer id);
/**
* 查询所有的数据
* @return
*/
List<Book> selectAll();
/**
* 按id查询某行数据
* @param id
* @return
*/
Book selectById(Integer id);
}
BookServiceImpl
java
//注解定义为bean
@Service
public class BookServiceImpl implements BookService {
//这里book可能会出现红标因为自动装配导致idea检查的时候找不到bean,但这个红标不影响程序
//自动装配bean,这个Service类Bean依赖于BookDao类型的Bean要用这个注解进行依赖注入
@Autowired
private BookDao bookDao;
@Override
public boolean insert(Book book) {
bookDao.insert(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 List<Book> selectAll() {
return bookDao.selectAll();
}
@Override
public Book selectById(Integer id) {
return bookDao.selectById(id);
}
}
3.根据service层开发controller层
BookController
java
//@Controller注解和@ResponseBody注解的合体
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
// 这种参数是从json数据请求体中获取的不是url所以不用@RequestParam
@PostMapping
public boolean insert(@RequestBody Book book) {
return bookService.insert(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable("id") Integer id) {
return bookService.delete(id);
}
@GetMapping
public List<Book> selectAll() {
return bookService.selectAll();
}
@GetMapping("/{id}")
public Book selectById(@PathVariable("id") Integer id) {
return bookService.selectById(id);
}
}
4.开启spring事务
在spring配置类上加上@EnableTransactionManager注解
SpringConfig
java
//Spring配置类
@Configuration
//开启spring事务
@EnableTransactionManagement
@ComponentScan({"com.morgan.service"})
//引入文件资源的注解
@PropertySource("classpath:jdbc.properties")
//导入相关需要依赖的配置类的注解
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
由于事务是跟数据库相关的所以在Jdbc配置类中添加一个事务管理器为bean
JdbcConfig
java
//平台事务管理器
//注意datasource用参数调用不用方法调用,要不然就不是spring管理了
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
在Service层接口上添加@Transactional注解开启事务
BookService
java
//开启事务,里面每个方法都是一个事务
@Transactional
public interface BookService {
/**
* 添加一组数据
* @param book
*/
boolean insert(Book book);
/**
* 按id修改某行数据
* @param book
*/
boolean update(Book book);
/**
* 按id删除某行数据
* @param id
*/
boolean delete(Integer id);
/**
* 查询所有的数据
* @return
*/
List<Book> selectAll();
/**
* 按id查询某行数据
* @param id
* @return
*/
Book selectById(Integer id);
}
5.额外:测试
在test.java包里创建测试类测试service层的功能
com.morgan.BookServiceTest
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
// 注意测试方法是不能有参数的,参数数据都是直接在方法里给的
@Test
public void selectByIdTest(){
Book book=bookService.selectById(1);
System.out.println(book);
}
@Test
public void selectTest(){
List<Book> list=bookService.selectAll();
System.out.println(list);
}
}
前后端数据传输协议
由于前后端通信时可能出现各种问题,比如前端无法确定获取到的数据是一个还是多个,无法确定获取到的数据的用途(增删改查?),无法确定数据传输是否成功,所以前后端约定一套协议(自己约定的并非规范)来限定传达这些信息,一般将controller层各个方法的结果,传输信息(传输类型,传输结果等,比如传输类型用2001表示增,1表示成功,添加成功的code就是20011),错误信息设置为一个结果实体类Result的三个属性,然后所有的方法传递的都是一个Result类型的对象
注意由于data可能是列表,数组等等,所以定义要用Object
Result
java
public class Result {
// 传输信息
private Integer code;
// 传输数据,由于可能是各种类型数据所以用Object
private Object data;
// 错误信息
private String msg;
// 定义三个构造函数任挑选
public Result() {
}
public Result(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public Result(Integer code, Object data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Code
java
public class Code {
//末尾数字区分成功(1)和失败(0),倒数第二位区分传输数据用途
public static final Integer INSERT_OK=200511;
public static final Integer DELETE_OK=200521;
public static final Integer UPDATE_OK=200531;
public static final Integer SELECT_OK=200541;
public static final Integer INSERT_ERR=200512;
public static final Integer DELETE_ERR=200522;
public static final Integer UPDATE_ERR=200532;
public static final Integer SELECT_ERR=200542;
}
BookController
java
//@Controller注解和@ResponseBody注解的合体
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
// 这种参数是从json数据请求体中获取的不是url所以不用@RequestParam
@PostMapping
public Result insert(@RequestBody Book book) {
boolean data = bookService.insert(book);
return new Result(data?Code.INSERT_OK:Code.INSERT_ERR,data);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean data = bookService.update(book);
return new Result(data?Code.UPDATE_OK:Code.UPDATE_ERR,data);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") Integer id) {
boolean data = bookService.delete(id);
return new Result(data?Code.DELETE_OK:Code.DELETE_ERR,data);
}
@GetMapping("/{id}")
public Result selectById(@PathVariable("id") Integer id) {
Book data = bookService.selectById(id);
Integer code=data!=null?Code.SELECT_OK:Code.SELECT_ERR;
String msg=data!=null?"":"数据查询失败,请重试";
return new Result(code,data,msg);
}
@GetMapping
public Result selectAll() {
List<Book> data = bookService.selectAll();
// 注意这里必须用data!=null只有这样才能证明是查询失败,如果用isEmpty有可能表本来就是空的
Integer code=data!=null?Code.SELECT_OK:Code.SELECT_ERR;
String msg=data!=null?"":"数据查询失败,请重试";
return new Result(code,data,msg);
}
}
异常处理
异常处理器
用的AOP思想对控制器类进行功能增强
在表现层controller层处理异常,用单独的异常类来进行异常的处理
给异常类加上@RestControllerAdvice:为Rest风格开发的控制器类做增强(并且这个注解自带@ResponseBody注解和Component注解)(注意由于一般异常类都放在表现层controller包下,如果不是,那还要在spring配置类中扫描到对应包)
给处理某一类异常对应的方法 加上@ExceptionHandler(异常类.class)注解来设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行,并且返回的也要是一个Result对象,这样可以反馈异常信息,并避免程序因异常直接不正常中断
异常码也需要规定
ExceptionAdvice
java
//对rest风格的控制器类做功能增强(其实这个就是一个AOP切面),这个注解包含自带@ResponseBody注解和Component注解相当于直接注册了
@RestControllerAdvice
public class ExceptionAdvice {
//设置该方法要处理的异常类型
@ExceptionHandler(Exception.class)
public Result doException(Exception e){
return new Result(Code.EXCEPTION,null,"发生Exception异常,请进行排查并重试");
}
}
项目异常处理
项目异常分类
业务异常(BusinessException)
规范的用户行为产生的异常
不规范的用户行为操作产生的异常
解决方案:发送对应消息传递给用户,提醒规范操作
系统异常(SystemException)
项目运行中可预计且无法避免的异常
解决方案:发送固定信息给用户,安抚用户; 发送特定信息给运维人员,提醒维护; 记录日志
其他异常(Exception)
编程人员未预期到的异常
解决方案:发送固定信息给用户,安抚用户; 发送特定信息给编程人员,提醒维护(纳入预期范围); 记录日志
项目异常处理
定义定义业务异常和系统异常的通知类(其他异常不需要,直接在写他的切面就行)
BusinessException
java
public class BusinessException extends RuntimeException{
// 定义一个code用来传递异常类型的信息
private Integer code;
public Integer getCode() {
return code;
}
// 定义自己需要的构造方法
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code,String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public BusinessException(Integer code,Throwable cause) {
super(cause);
this.code = code;
}
}
SystemException
java
//定义一个系统性异常,继承RuntimeException这样可以自动把异常往上抛,不需要一直手动throw
public class SystemException extends RuntimeException{
// 定义一个code用来传递异常类型的信息
private Integer code;
public Integer getCode() {
return code;
}
// 定义自己需要的构造方法
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code,String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public SystemException(Integer code,Throwable cause) {
super(cause);
this.code = code;
}
}
在Code类中添加三种异常对应的异常码
Code
java
//异常码,最后两位是对应异常编号
public static final Integer SYSTEM_EXCEPTION=50001;
public static final Integer EXCEPTION=50002;
public static final Integer BUSINESS_EXCEPTION=50003;
在异常处理器中添加业务类异常,系统性异常和其他异常的异常处理
ExceptionAdvice
java
//对rest风格的控制器类做功能增强(其实这个就是一个AOP切面),这个注解包含自带@ResponseBody注解和Component注解相当于直接注册了
@RestControllerAdvice
public class ExceptionAdvice {
// 系统性异常
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException se){
//记录日志
//发消息给运维
//发送邮件给开发人员,异常对象发送给开发人员
return new Result(se.getCode(),null,se.getMessage());
}
// 业务类异常
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException be){
return new Result(be.getCode(),null,be.getMessage());
}
//设置该方法要处理的异常类型
// 其他异常,其他异常是无法预测的那些异常,所以不需要写通知类
@ExceptionHandler(Exception.class)
public Result doException(Exception e){
//记录日志
//发消息给运维
//发送邮件给开发人员,异常对象发送给开发人员
return new Result(Code.EXCEPTION,null,"服务器繁忙,请稍后再试");
}
}
最后在业务类中模拟一下异常
BookServiceImpl
java
@Override
public Book selectById(Integer id) {
//仅做测试异常功能是否可用作用,实际开发用try-catch包围可能出现异常的地方进行处理
if(id==10086){
throw new SystemException(Code.BUSINESS_EXCEPTION,"服务器超时,请重试");
}else if(id==10087){
throw new BusinessException(Code.BUSINESS_EXCEPTION,"连接超时,请重试");
}
return bookDao.selectById(id);
}
拦截器
概念
拦截器是一种动态拦截方法调用的机制,相当于在contoller方法前后上一道门,实行对应的方法,比如权限校验等
也是AOP
作用
在指定的方法调用前后执行预先设定好的代码
阻止原始方法的执行(比如禁止无权限用户的访问)
拦截器和过滤器的区别
归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有的访问进行增强,Interceptor仅针对SpringMVC的访问进行增强(因为Filter是在tomcat时进行的,Interceptor实在springmvc进行的)
拦截器设置
先在Contoller层定义一个通知类ProjectInterceptor,实现接口HandlerInterceptor
在通知类中实现三个通知方法preHandle,postHandle,afterCompletion(接口方法)
preHandle是原始方法执行前执行的,返回值true/false决定原始方法是否执行,postHandle在原始方法执行之后,afterCompletion在postHandle执行之后
用@Component把他注册为Bean以便后面拦截器调用他
ProjectInterceptor
java
//本质上也是一个通知类
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 原始方法执行前方法,这里只是模拟一下所以直接打印,实际开发写自己的需求,比如权限的校验等
System.out.println("preHandle...");
// return true表示继续执行原始方法,如果return false表示拦截原始方法的执行
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 {
// 在postHandle方法执行之后执行
System.out.println("afterCompletion...");
}
}
比较麻烦的设置方式
在配置包下的web增强类(之前用这个类配置不给springmvc管理的目录)加入拦截器的定义,规定拦截对象
SpringMVCSupport
java
//定义一个类来对springmvc进行功能增强
@Configuration
//继承webmvc增强类WebMvcConfigurationSupport
public class SpringMVCSupport extends WebMvcConfigurationSupport {
// 自动装配拦截器bean以便下面切面方法使用
@Autowired
private ProjectInterceptor projectInterceptor;
// 排除不想交给springmvc管理的包
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
// 切面方法,对/books下的方法设置拦截器,注意**表示/books下所有包和子包,*表示/books下的所有包但不包括子包
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
}
}
由于这个SpringMVCSupport类是配置类,需要被SpringMVC扫描到,所以在SpringMVC配置类的扫描注解中加上config包,并实现WebMvcConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源等
SpringMVCConfig
java
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller","com.morgan.config"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
//WebMVCConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源
public class SpringMVCConfig implements WebMvcConfigurer {
}
简单的设置方式
直接在SpringMVC配置类实现接口WebMvcConfigurer,然后资源过滤和拦截器都可以直接定义在这里面
由于没有了增强类,也不需要再扫描config包
SpringMVCConfig
java
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
//WebMVCConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源
public class SpringMVCConfig implements WebMvcConfigurer {
// 自动装配拦截器bean以便下面切面方法使用
@Autowired
private ProjectInterceptor projectInterceptor;
// 排除不想交给springmvc管理的包
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
// 切面方法,对/books下的方法设置拦截器,注意**表示/books下所有包和子包,*表示/books下的所有包但不包括子包
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
}
}
拦截器参数
前置处理
java
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 原始方法执行前方法,这里只是模拟一下所以直接打印,实际开发写自己的需求,比如权限的校验等
System.out.println("preHandle...");
// return true表示继续执行原始方法,如果return false表示拦截原始方法的执行
return true;
}
request:请求对象
response:响应对象
handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的method对象进行了再包装
返回值为false,被拦截的处理器将不执行
完成后处理
java
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在原始方法执行之后执行
System.out.println("postHandle...");
}
ex:如果处理器执行过程中出现异常对象可针对异常情况单独处理,但这个通常被自定义异常类替代了,没什么用
拦截器链(了解)
和过滤器一样,拦截器也可以配多个,配的顺序就是执行顺序
SpringMVCConfig
java
@Autowired
private ProjectInterceptor projectInterceptor1;
@Autowired
private ProjectInterceptor projectInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor1).addPathPatterns("/books/**");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books/**");
}
拦截器前置处理是顺序,后置处理和后后置处理是倒序
比如依次三个拦截器interceptor1,interceptor2,interceptor3,执行的顺序是
pre1=>pre2=>pre3=>原始方法=>post3=>post2=>post1=>afterCompletion3=>afterCompletion2=>afterCompletion1
所以其实叫拦截器栈更合适,先进后出嘛
然后如果在某个pre处return false,那后面的pre方法,原始方法和所有的post方法都不会执行,afterCompletion会从return false的后一个继续执行到最后
比如pre3返回false,执行pre1=>pre2=>pre3=>afterCompoletion2=>afterCompletion1
pre2返回false,执行pre1=>pre2=>afterCompletion1
pre1返回false,执行pre1