若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
资料中已经给了十分详细的笔记(Markdown 文档),此处只是汇集了一下(只是为了方便在线阅览,无其他用途)
1.Bean 的加载控制
@Controller
我们一般使用 @Autowired
注解让 Spring 容器帮我们自动装配 bean。该注解根据类型查找容器中的 bean。
如果在容器中存在多个相同类型的 bean,注入参数的属性名又和容器中 bean 的名称不一致,此时就需要借助 @Qualifier
注解来指定注入哪个名称的 bean 对象。@Qualifier
注解后的值就是需要注入的 bean 的名称。
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
- 注意:
@Qualifier
不能独立使用,必须和@Autowired
一起使用。
@Component
是一个通用的注解,可标注任意类为 Spring 组件。
如果一个 bean 不知道属于哪个层的时候,可以使用 @Component
注解标注。
- 注意:
@Component
注解是不可以添加在接口上的,因为接口是无法创建对象的。
对于 @Component
注解,还衍生出了其他三个注解 @Controller
、@Service
、@Repository
方便我们后期在编写类的时候能很好的区分出这个类是属于 表现层、业务层 还是 数据层 的类。
@Configuration
注解一般用来声明配置类,可以使用 @Component
注解替代。
名称 | @Controller |
---|---|
类型 | 类注解 |
位置 | SpringMVC 控制器类定义上方 |
作用 | 设定 SpringMVC 的核心控制器 bean |
@Controller
+@RequestMapping
+@ResponseBody
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}
@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置 Spring 配置类扫描路径,用于加载使用注解格式定义的 bean |
相关属性 | excludeFilters:排除扫描路径中加载的 bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的 bean,需要指定类别(type)和具体项(classes) |
- excludeFilters 属性:设置扫描加载 bean 时,排除的过滤规则
- type 属性:设置排除规则,当前使用按照 bean 定义时的注解类型进行排除
- ANNOTATION:按照注解排除
- ASSIGNABLE_TYPE:按照指定的类型过滤
- ASPECTJ:按照 Aspectj 表达式排除,基本上不会用
- REGEX:按照正则表达式排除
- CUSTOM:按照自定义规则排除
- classes 属性:设置排除的具体注解类,当前设置排除
@Controller
定义的 bean
@Configuration
@ComponentScan(value="com.itheima",
excludeFilters=@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig { }
2.请求与响应
@RequestMapping
名称 | @RequestMapping |
---|---|
类型 | 类注解 或 方法注解 |
位置 | SpringMVC 控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
- 注意
- 当类上和方法上都添加了
@RequestMapping
注解,前端发送请求的时候,要和两个注解的 value 值相加匹配才能访问到。 @RequestMapping
注解 value 属性前面加不加/
都可以。
- 当类上和方法上都添加了
@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
@RequestParam
可以用来获取查询参数(比如 http://localhost/user?type=student
)。
此外,它还可以传递集合参数。
同名请求参数可以使用 @RequestParam
注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
传递参数是 JSON 数据
SpringMVC 接收 JSON 数据的实现步骤为:
- 导入 jackson 包
- 使用 PostMan 发送 JSON 数据
- 开启 SpringMVC 注解驱动,在配置类上添加
@EnableWebMvc
注解 - Controller 方法的参数前添加
@RequestBody
注解
@EnableWebMvc
名称 | @EnableWebMvc |
---|---|
类型 | 配置类注解 |
位置 | SpringMVC 配置类定义上方 |
作用 | 开启 SpringMVC 多项辅助功能 |
在 SpringMVC 的配置类中开启 SpringMVC 的注解支持,这里面就包含了将 JSON 转换成对象的功能。
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc // 开启 json 数据类型自动转换
public class SpringMvcConfig { }
@RequestBody
名称 | @RequestBody |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
使用 @RequestBody
注解将外部传递的 json 数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
如果响应 json 的方法很多的话,那么每个方法都需要加上 @ResponseBody
注解,这样一来重复性太高了。
我们可以将 @ResponseBody
提到类上面,让所有的方法都有 @ResponseBody
的功能
- 注意:一个请求方法只可以有一个
@RequestBody
(但是可以有多个@RequestParam
和@PathVariable
)
@PathVariable
名称 | @PathVariable |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 绑定路径参数与处理器方法形参间的关系,要求 路径参数名 与 形参名 一一对应 |
@RequestMapping(value="/users/{id}", method = RequestMethod.DELETE)
@ReponseBody
public String delete(@PathVariable Integer id){ }
@RequestBody
与 @RequestParam
- 区别
@RequestParam
用于接收 url 地址传参,表单传参【application/x-www-form-urlencoded
】@RequestBody
用于接收 json 数据【application/json类型的数据
】
- 应用
- 后期开发中,发送 json 格式数据为主,
@RequestBody
应用较广 - 如果发送非 json 格式数据,选用
@RequestParam
接收请求参数
- 后期开发中,发送 json 格式数据为主,
@RequestBody
与 @RequestParam
与 @PathVariable
关于接收参数的注解,目前已经讲了三个注解了:@RequestBody
、@RequestParam
、@PathVariable
- 区别
@RequestParam
用于接收 URL 地址传参或表单传参@RequestBody
用于接收 json 数据@PathVariable
用于接收路径参数,使用{参数名称}
描述路径参数
- 应用
- 后期开发中,发送请求参数超过 1 个时,以 json 格式为主,
@RequestBody
应用较广 - 如果发送非 json 格式数据,选用
@RequestParam
接收请求参数 - 采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用
@PathVariable
接收请求路径变量,通常用于传递 id 值
- 后期开发中,发送请求参数超过 1 个时,以 json 格式为主,
@ResponseBody
(响应数据是 JSON 数据)
名称 | @ResponseBody |
---|---|
类型 | 方法注解 或 类注解 |
位置 | SpringMVC 控制器方法定义上方和控制类上 |
作用 | 设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能 |
相关属性 | pattern:指定日期时间格式字符串 |
说明:
- 该注解可以写在类上或者方法上
- 写在类上就是该类下的所有方法都有
@ReponseBody
功能 - 当方法上有
@ReponseBody
注解后- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成 JSON 响应给前端
此处用到了类型转换,内部是通过 Converter 接口的实现类完成的,它可以实现:
- 对象转 JSON 数据(
POJO -> json
) - 集合转 JSON 数据(
Collection -> json
)
响应 POJO 集合对象
@Controller
public class UserController {
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);
User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
return userList;
}
}
3.Rest 风格
REST 简介
当我们想表示一个网络资源的时候,可以使用两种方式
- 传统风格 资源 描述形式
http://localhost/user/getById?id=1
查询 id 为 1 的用户信息http://localhost/user/saveUser
保存用户信息
- REST 风格 描述形式
http://localhost/user/1
http://localhost/user
传统方式一般是一个请求 URL 对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求 URL 地址,就大概知道该 URL 实现的是一个什么样的操作。查看 REST 风格的描述,你会发现请求地址变的简单了,并且光看请求 URL 并不是很能猜出来该 URL 的具体功能
总结以上情况,得出 REST 的优点
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
但是问题也随之而来,一个相同的 URL 地址即可以是新增也可以是修改或者查询,那么到底我们该如何区分该请求到底是什么操作呢?
- 按照 REST 风格访问资源时使用行为动作区分对资源进行了何种操作
http://localhost/users
:查询全部用户信息 GET(查询)http://localhost/users/1
:查询指定用户信息 GET(查询)http://localhost/users
:添加用户信息 POST(新增/保存)http://localhost/users
:修改用户信息 PUT(修改/更新)http://localhost/users/1
:删除用户信息 DELETE(删除)
请求的方式比较多,但是比较常用的就4种,分别是 GET、POST、PUT、DELETE。
按照不同的请求方式代表不同的操作类型。
- 发送 GET 请求是用来做查询
- 发送 POST 请求是用来做新增
- 发送 PUT 请求是用来做修改
- 发送 DELETE 请求是用来做删除
注意:上述行为是约定方式,约定不是规范,可以打破,所以称 REST 风格,而不是 REST 规范
- REST 提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
- REST 中规定 GET/POST/PUT/DELETE 针对的是 查询/新增/修改/删除,但是我们如果非要用 GET 请求做删除,这点在程序上运行是可以实现的
- 但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
- 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts…
清楚了什么是 REST 风格后,我们后期会经常提到一个概念叫 RESTful,那什么又是 RESTful 呢?
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格
后期我们在进行开发的过程中,大多是都是遵从 REST 风格来访问我们的后台服务,所以可以说咱们以后都是基于 RESTful 来进行开发的。
具体代码风格
设定 HTTP 请求动作(动词)
@RequestMapping(value="",method = RequestMethod.POST|GET|PUT|DELETE)
设定请求参数(路径变量)
@RequestMapping(value="/users/{id}",method = RequestMethod.DELETE)
@ReponseBody
public String delete(==@PathVariable== Integer id){ }
方法形参和路径参数不一致
如果方法形参的名称和路径 {}
中的值不一致,该怎么办?
@RestController
(@Controller
+ @ResponseBody
)
使用 @RestController
注解替换 @Controller
与 @ResponseBody
注解,简化书写
名称 | @RestController |
---|---|
类型 | 类注解 |
位置 | 基于 SpringMVC 的 RESTful 开发控制器类定义上方 |
作用 | 设置当前控制器类为 RESTful 风格,等同于 @Controller 与 @ResponseBody 两个注解组合功能 |
@RestController
注解标注在类上,表明这是一个控制器 bean,直接将函数的返回值填入 HTTP 响应体中。
@XXXMapping
(处理常见的 HTTP 请求类型)
在 Rest每个方法的 @RequestMapping
注解中都要使用 method 属性定义请求方式,重复性太高。
使用 @GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
代替
名称 | @GetMapping + @PostMapping + @PutMapping + @DeleteMapping |
---|---|
类型 | 方法注解 |
位置 | 基于 SpringMVC 的 RESTful 开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @GetMapping 对应 GET 请求 |
相关属性 | value(默认):请求访问路径 |
4.统一异常处理
@RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | REST 风格开发的控制器增强类定义上方 |
作用 | 为 REST 风格开发的控制器类做增强 |
说明:此注解自带 @ResponseBody
注解与 @Component
注解,具备对应的功能
@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常。
@RestControllerAdvice
+ @ExceptionHandler
- 备注:这里的 Result 就是一个统一结果封装类
@RestControllerAdvice // 用于标识当前类为 REST 风格对应的异常处理器
public class ProjectExceptionAdvice {
@ExceptionHandler(SystemException.class) // 用于设置当前处理器类对应的异常类型
public Result doSystemException(SystemException ex){
// 记录日志
// 发送消息给运维
// 发送邮件给开发人员,ex 对象发送给开发人员
return new Result(ex.getCode(),null,ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}
// 除了自定义的异常处理器,该注解也可以保留对 Exception 类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
// 记录日志
// 发送消息给运维
// 发送邮件给开发人员,ex 对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR, null, "系统繁忙,请稍后再试!");
}
}
5.拦截器
本小节没有和 SpringMVC 相关的新注解。
但是拦截器是非常重要的知识点,创建它也需要同时用到好几个注解,故写在本文中。
5.1.简单介绍
拦截器(Interceptor)是一种动态拦截方法调用的机制,在 SpringMVC 中动态拦截控制器方法的执行。
作用:在指定的方法调用前后执行预先设定的代码,阻止原始方法的执行。总结起来就是拦截器是用来做增强的。
拦截器和过滤器在作用和执行顺序上也很相似,那么二者间的区别是什么?
- 归属不同:Filter 属于 Servlet 技术,Interceptor 属于 SpringMVC 技术
- 拦截内容不同:Filter 对 所有访问进行增强,Interceptor 仅针对 SpringMVC 的访问进行增强
这两个知识点是很重要的,这里讲的比较简略。
下面推荐几篇介绍拦截器和过滤器的创建使用和区别的文章,诸位可自行观看。
可以只看第一篇。(反正第一篇的参考文章就是其下两篇)
5.2.拦截器开发
首先创建一个拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。
之后可以将上面设置的普通拦截器注入到项目 配置类 中,并设置相应拦截规则。
也可以使用 配置器支持类 + 配置类 来避免被侵入的安全问题。
此外,上述的所有类,都必须被 SpringMVC 容器扫描到。
5.2.1.创建拦截器类
创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。
此外,拦截器类是必须被 SpringMVC 容器扫描到的,故需要加上 @Component
注解。
总结:@Component
注解 + 实现 HandlerInterceptor 接口,并重写接口中的方法。
// 定义拦截器类,实现 HandlerInterceptor 接口
// 注意:当前类必须受 Spring 容器控制,否则没有意义
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
// 原始方法调用前执行的内容
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
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...");
}
}
拦截器中的 preHandler() 方法:
- 如果返回 true,则代表放行,会执行原始 Controller 类中要请求的方法;
- 如果返回 false,则代表拦截,后面的就不会再执行了。
5.2.2.配置拦截器类
@Configuration
注解 + 继承 WebMvcConfigurationSupport 类
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
// 注入拦截器
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 配置拦截器
registry.addInterceptor(projectInterceptor) // 添加拦截器
.addPathPatterns("/books","/books/*" ); // 添加拦截地址
}
}
5.2.3.SpringMVC 配置文件添加 SpringMvcSupport 包扫描
@Configuration
+ @ComponentScan(SpringMvcSupport 包)
+ @EnableWebMvc
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{ }
5.2.4.简化 SpringMvcSupport 的编写
将上面设置的普通拦截器注入到项目配置类中,并设置相应拦截规则
注意:配置类直接实现 WebMvcConfigurer 接口可以简化开发,但具有一定的侵入性
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
此后就无需 SpringMvcSupport 类了。
5.2.5.拦截器的执行流程
5.3 拦截器参数
5.3.1 前置处理方法
原始方法之前运行 preHandle
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的 Method 对象进行了再包装
使用 request 对象可以获取请求数据中的内容,如获取请求头的 Content-Type
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
System.out.println("preHandle..." + contentType);
return true;
}
使用 handler 参数,可以获取方法的相关信息
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm = (HandlerMethod) handler;
String methodName = hm.getMethod().getName(); // 可以获取方法的名称
System.out.println("preHandle..." + methodName);
return true;
}
5.3.2 后置处理方法
原始方法运行后运行,如果原始方法被拦截,则不执行
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
前三个参数和上面的是一致的。
- modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。
因为这里现在都是返回 json 数据,所以该参数的使用率不高。
5.3.3 完成处理方法
拦截器最后执行的方法,无论原始方法是否执行
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
前三个参数与上面的是一致的。
- ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
因为这里现在已经有全局异常处理器类了,所以该参数的使用率也不高。
在上述的三个方法中,最常用的是 preHandle(),在这个方法中可以通过返回值来决定是否要进行放行。
我们可以把业务逻辑放在该方法中,如果满足业务则返回 true 放行,不满足则返回 false 拦截。