Springboot学习-day17
1. AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
Spring AOP的术语
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指可以被拦截到的点,在Spring中,指可以被动态代理拦截目标类的方法 |
Pointcut(切入点) | 指要对哪些Joinpoint进行拦截,即被拦截的连接点 |
Advice(通知) | 指拦截到Joinpoint之后要做的事情,即对切入点增强的内容 |
Target(目标) | 指代理的目标对象 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程 |
Proxy(代理) | 指生成的代理对象 |
Aspect(切面) | 切入点和通知的结合 |
AOP的通知分类
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
创建一个控制类EasyAController
@RestController
public class EasyAController {
//连接点
@RequestMapping("testA")
public String testA(){
System.out.println("testA方法------");
return "EasyA method";
}
}
创建一个切点,并实现通知
@Aspect
@Component
public class AOPObj {
//定义切点
//execution(* cn.xxx.dao..*(..))
@Pointcut("execution(* com.easy.controller.EasyAController.testA(..))")
public void pointCutTestA(){
//切点
}
//通过动态代理来实现的面向切面的思想,以此在不修改源代码的情况下对已有功能进行增强
@Before("pointCutTestA()")
public void before(){
System.out.println("前置通知----------");
}
@After("pointCutTestA()")
public void after(){
System.out.println("后置通知----------");
}
@AfterThrowing("pointCutTestA()")
public void afterThrowing(){
System.out.println("异常后通知----------");
}
@AfterReturning("pointCutTestA()")
public void afterReturning(){
System.out.println("返回后通知----------");
}
}
启动Spring项目,访问testA,可以看到如下图所示的输出
在控制类EasyAController的testA中创造一个异常
@RestController
public class EasyAController {
@RequestMapping("testA")
public String testA(){
System.out.println("testA方法------");
int a = 12 / 0;
return "EasyA method";
}
}
重启Spring项目,访问testA,可以看到如下输出
以上定义切点注解的@Pointcut中的参数是切点表达式
execution类型的语法如下所示
execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
-
modifier:匹配修饰符,public, private 等,省略时匹配任意修饰符
-
ret-type:匹配返回类型,使用 * 匹配任意类型
-
declaring-type:匹配目标类,省略时匹配任意类型
-
- … 匹配包及其子包的所有类
-
name-pattern:匹配方法名称,使用 * 表示通配符
-
- 匹配任意方法
-
- set* 匹配名称以 set 开头的方法
-
param-pattern:匹配参数类型和数量
-
- () 匹配没有参数的方法
- (…) 匹配有任意数量参数的方法
- (*) 匹配有一个任意类型参数的方法
- (*,String) 匹配有两个参数的方法,并且第一个为任意类型,第二个为 String 类型
-
throws-pattern:匹配抛出异常类型,省略时匹配任意类型
使用示例
// 匹配public方法
execution(public * *(..))
// 匹配名称以set开头的方法
execution(* set*(..))
// 匹配AccountService接口或类的方法
execution(* com.xyz.service.AccountService.*(..))
// 匹配service包及其子包的类或接口
execution(* com.xyz.service..*(..))
参考: https://blog.csdn.net/weixin_43793874/article/details/124753521
2. 代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
代理模式的目的
1.功能增强:通过代理业务对原有业务进行增强
2.控制访问:通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性
2.1 静态代理
简单来说代理模式就是将被代理类包装起来然后重新实现相同的方法,并且调用原来方法的同时可以在方法前后添加一个新的处理。而这种包装可以使用继承或者组合来使用。当我们调用的时候需要使用的是代理类的对象来调用而不是原来的被代理对象。
静态代理的特点
- 1.代理类是手动实现的,需要自己去创建一个类
- 2.代理类所代理的目标类是固定的
静态代理可以通过继承或实现代理类的接口来实现
2.1.1 通过继承实现静态代理
通过继承被代理对象,重写被代理方法,可以对其进行代理。
优点:被代理类无需实现接口
缺点:只能代理这个类,要想代理其他类,要想代理其他类需要写新的代理方法。
cglib动态代理就是采用这种方式对类进行代理。不过类是由cglib
帮我们在内存中动态生成的。
public class Tank{
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
new ProxyTank().move();
}
}
class ProxyTank extends Tank{
@Override
public void move() {
System.out.println("方法执行前...");
super.move();
System.out.println("方法执行后...");
}
}
2.1.2 通过组合实现静态代理
定义一个被代理类需要和代理类都需要实现的接口。(接口在这里的目的就是起一个规范作用保证被代理类和代理类都实现了接口中的方法)。代理类需要将该接口作为属性,实例化时需要传入该接口的对象,这样该代理类就可以实现代理所有实现这个接口的类了。
优点:可以代理所有实现接口的类。
缺点:被代理的类必须实现接口。
JDK动态代理就是采用的这种方式实现的。同样的代理类是由JDK自动帮我们在内存生成的。
public class Tank implements Movable{
@Override
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
Tank tank = new Tank();
new LogProxy(tank).move();
}
}
class LogProxy implements Movable{
private Movable movable;
public LogProxy(Movable movable) {
this.movable = movable;
}
@Override
public void move() {
System.out.println("方法执行前....");
movable.move();
System.out.println("方法执行后....");
}
}
interface Movable {
void move();
}
静态代理存在的问题
1.当目标类增多时,代理类也需要增多,导致代理类的关系不便
2.当接口当中的功能增多或者修改,都会影响实体类,违反开闭原则(程序对访问开放,对修改关闭)
2.2 动态代理
动态代理其实本质还是 将被代理类包装一层,生成一个具有新的相同功能的代理类。
但是与静态代理不同的是,这个代理类我们自己定义的。而动态代理这个代理类是根据我们的提示动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
实现动态代理的方式:
1.JDK动态代理
2.CGLIB动态代理
2.2.1 JDK动态代理
通过java提供的Proxy
类帮我们创建代理对象。
优点:可以生成所有实现接口的代理对象
缺点:JDK反射生成代理必须面向接口, 这是由Proxy的内部实现决定的。生成代理的方法中你必须指定实现类的接口,它根据这个接口来实现代理类生成的所实现的接口。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 使用jdk的动态代理
*/
public class Tank implements Movable{
@Override
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
Tank tank = new Tank();
// reflection 反射 通过二进制字节码分析类的属性和方法
//newProxyInstance: 创建代理对象
// 参数一: 被代理类对象
// 参数二:接口类对象 被代理对象所实现的接口
// 参数三:调用处理器。 被调用对象的那个方法被调用后该如何处理
Movable o = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader()
,new Class[]{Movable.class}
,new LogProxy(tank));
o.move();
}
}
class LogProxy implements InvocationHandler {
private Movable movable;
public LogProxy(Movable movable) {
this.movable = movable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法:"+method.getName()+"()执行前");
Object invoke = method.invoke(movable, args); // 此处相当于 movable.move()
System.out.println("方法:"+method.getName()+"()执行后");
return invoke;
}
}
interface Movable {
void move();
}
2.2.2 CGLIB动态代理
CGLib(Code Generate Library) 与JDK动态代理不同的是,cglib生成代理是被代理对象的子类。因此它拥有继承方法实现静态代理的优点:不需要被代理对象实现某个接口。
缺点:不能给final类生成代理,因为final类无法拥有子类。
使用cglib生成代理类也很简单,只要指定父类和回调方法即可
首先需要引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); // 增强者
enhancer.setSuperclass(Tank.class); // 指定父类
enhancer.setCallback(new TimeMethodInterceptor()); // 当被代理对象的方法调用的时候会调用 该对象的intercept
Tank tank = (Tank)enhancer.create(); // 动态代理的生成
tank.move(); // 生成之后会调用
}
}
class TimeMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("生成的类名"+o.getClass().getName());
System.out.println("生成的类的父类"+o.getClass().getSuperclass().getName());
System.out.println("方法执行前,被代理的方法"+method.getName());
Object result = null;
result = methodProxy.invokeSuper(o, objects);
System.out.println("方法执行后,被代理的方法"+method.getName());
return result;
}
}
class Tank{
public void move(){
System.out.println("Tank moving clacla....");
}
}
参考:https://blog.csdn.net/Passer_hua/article/details/122617628
3. SpringMVC
Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了 Web 应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合
3.1 @Controller @RestController
@Controller 注解可以将一个普通的 Java 类标识成控制器(Controller)类
RestController和@Controller的区别
RestController注解相当于@ResponseBody + @Controller合在一起的作用。
(1)如果只是使用@RestController注解Controller层,那么Controller层中的方法无法返回到 jsp 页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的数据就是return语句里面的内容。
例如:本来应该到success.jsp页面的,则其显示success
(2)如果需要返回到指定页面,则需要用@Controller 配合视图解析器lnternalResourceViewResolver才行。
(3)如果需要返回JSON、XML或者自定义mediaType到页面,则需要在对应的方法上加上ResponseBody注解。
3.2 @RequestMapping
@RequestMapping通常被标注在控制器方法上,负责将请求与处理请求的控制器方法关联起来,建立映射关系。
路径映射: 当一个用户访问一个URL的时候,将用户的请求对应到程序中 某个类的某个方法 的过程就叫映射。
@RequestMapping 既可以修饰类,也可以修饰方法。当修饰类和方法时,访问的地址是类路径+方法路径,如果只使用方法路径去访问会报错。
@RequestMapping有五个属性
value:设置控制器方法的请求映射地址
name:方法的注释
method:设置请求方式,如果没有设置则可以处理所有类型的请求
params:用于指定请求中的参数,只有当请求中携带了符合条件的参数时,控制器方法才会对该请求进行处理。
headers:headers 属性用于设置请求中请求头信息,只有当请求中携带指定的请求头信息时,控制器方法才会处理该请求。
3.3 @PathVariable
@PathVariable 注解的作用是将 URL 中的路径参数(Path parameters)绑定到方法的参数上。在 Spring MVC 中,我们可以通过在控制器(Controller)的方法参数上添加 @PathVariable 注解来获取 URL 中的变量值,并将其作为方法参数的值进行使用。
@GetMapping("/users/{id}")
public String getUserInfo(@PathVariable("id") Long userId) {
// 处理 userId 的逻辑
return "User ID: " + userId;
}
总结来说,@PathVariable
注解的作用是用于获取 URL 中的路径参数,并将其绑定到方法的参数上,方便在方法中使用。
3.4 @Requestparam
RequestParam 是 Spring MVC 中用来处理 HTTP 请求参数的注解,主要用于绑定请求中的查询参数或表单字段到控制器方法的参数。
@RequestMapping("paramB")
public Map paramB(@RequestParam Map params){
return params;
}
3.5 @Get/Post/Put/DeleteMapping
- @GetMapping-------->通常对应查询操作
- @PostMapping-------->通常对应新增操作
- @PutMapping------------>通常对应修改操作
- @DeleteMapping--------->通常对应删除操作
其实是对RequestMapping的一种请求方式的缩写
如@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写
@RequestMapping(value = "/findAllUser",method = RequestMethod.GET)等于@GetMapping(value = "/findAllUser")
@RequestMapping(value = "/addUser",method = RequestMethod.POST)等于@PostMapping(value = "/addUser")
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)等于@PutMapping(value = "/updateUser")
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)等于@DeleteMapping(value = "/deleteUser")
@RestController
//@ControllerAdvice
//@RequestMapping("staff")
public class StaffController {
@GetMapping("staff")
public CommonResult getList(Staff staff){
List<Staff> list = null;
System.out.println("获取数据");
return CommonResult.success(list);
}
@PostMapping("staff")
public CommonResult addStaff(Staff staff){
System.out.println("新增数据");
return CommonResult.success(staff);
}
@DeleteMapping("staff/{id}")
public CommonResult delStaff(@PathVariable int id){
System.out.println("删除数据" + id);
return CommonResult.success();
}
@PutMapping("staff")
public CommonResult editStaff(Staff staff){
System.out.println("编辑数据");
return CommonResult.success();
}
}
3.6 请求转发 重定向
转发:同一个服务器中不同的服务进行转发 forward
浏览器发送了一个请求 可以转发到项目中受保护的资源WEB-INF
转发是request对象执行forward方法
@Controller
public class EasyEController{
@RequestMapping("methodA")
public String methodA(){
System.out.println("------methodA");
return "forward:/methodB";
}
@RequestMapping("methodB")
@ResponseBody
public String methodB(){
System.out.println("------methodB");
return "this is methodB";
}
}
访问methodA页面时,页面显示this is methodB但地址栏的url地址没有改变,还是methodA
重定向:可以在不同的服务之间进行跳转
浏览器发送了多次请求
通过response对象通知浏览器,重新访问,执行的是redirect方法
@Controller
public class EasyEController{
public String methodA(){
System.out.println("------methodA");
return "redirect:/methodB";
}
@RequestMapping("methodB")
@ResponseBody
public String methodB(){
System.out.println("------methodB");
return "this is methodB";
}
}
访问methodA页面时,页面显示this is methodB,地址栏的url地址也变为methodB
3.6 SpirngMVC的运行原理
1.用户通过浏览器发起一个 HTTP 请求,该请求会被 DispatcherServlet(前端控制器)拦截
2.DispatcherServlet 调用 HandlerMapping(处理器映射器)找到具体的处理器(Handler)及拦截器
3.HandlerMapping将Handler以 HandlerExecutionChain 执行链的形式返回给 DispatcherServlet
4.DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器)
5.HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(即 Controller 控制器)对请求进行处理
6.Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息)
7.HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet
8.DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析
9.ViewResolver 解析完成后,会将 View 视图并返回给 DispatcherServlet
10.DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图)
11.视图负责将结果显示到浏览器(客户端)
3.7 SpringMVC的异常处理机制
Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。
Spring MVC 允许我们在控制器类(Controller 类)中通过 @ExceptionHandler 注解来定义一个处理异常的方法,以实现对控制器类内发生异常的处理。
@ExceptionHandler 注解中包含了一个 value 属性,我们可以通过该属性来声明一个指定的异常。如果在程序运行过程中,这个 Controller 类中的方法发生了这个指定的异常,那么 ExceptionHandlerExceptionResolver 就会调用这个 @ExceptionHandler 方法对异常进行处理。
定义在某个控制器类中的 @ExceptionHandler 方法只在当前的控制器中有效,它只能处理其所在控制器类中发生的异常。
@ExceptionHandler 方法的优先级–指定异常类型最精确的
@ExceptionHandler(value = {Exception.class})
public CommonResult exh(){
return CommonResult.success(200, "网页被外星人带走了");
}
全局异常处理,使用@ControllerAdvice注释来修饰控制类,当其他类出现异常时,也会调用该类中被@ExceptionHandler修饰的处理异常的方法
4. 拦截器
在系统中,经常需要在处理用户请求之前和之后执行一些行为,例如检测用户的权限,或者将请求的信息记录到日志中,即平时所说的“权限检测”及“日志记录”。当然不仅仅这些,所以需要一种机制,拦截用户的请求,在请求的前后添加处理逻辑。
Spring MVC 提供了 Interceptor 拦截器机制,用于请求的预处理和后处理。
要想自定义拦截器,必须实现HandlerInterceptor接口
package com.qzcsbj.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行preHandle方法了。。。");
return true; // 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方法了。。。");
}
}
控制器
package com.qzcsbj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@RequestMapping("/testInterceptor")
public String testInterceptor(){
System.out.println("控制器中的方法执行了。。。");
return "success";
}
}
springmvc.xml中配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的资源URI-->
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1"
class="com.qzcsbj.interceptor.HandlerInterceptorDemo1">
</bean>
</mvc:interceptor>
</mvc:interceptors>
测试
er;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@RequestMapping("/testInterceptor")
public String testInterceptor(){
System.out.println("控制器中的方法执行了。。。");
return "success";
}
}
springmvc.xml中配置拦截器
```xml
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的资源URI-->
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1"
class="com.qzcsbj.interceptor.HandlerInterceptorDemo1">
</bean>
</mvc:interceptor>
</mvc:interceptors>
测试