什么是拦截器
类似于门口的保安,用来做用户的验证,他是Spring提供的核心功能之一,用来拦截用户的请求
目标方法执行前,要做的事情。(要去实现这个接口)
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("登录拦截器校验");
目标方法执行后,要做的事情
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("目标方法执行后"); }
描写拦截的内容,拦截的规则
@Override public void addInterceptors(InterceptorRegistry registry) { //add路径,exclude排除什么我们不去拦截 registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login") .excludePathPatterns(excludePath);//**表示会给所有方法添加拦截器 }
源码了解:DispatcherServlet
先初始化,有一个init(),再会去进行一些组件的初始化,然后会回调一个dodispatch,会检查有没有一些文件,
initHandlerMappings(会找到所有的HandlerBean,找到他想要的Bean,他会拿到一个处理器
(类似@RequestMapping拿到这个处理器就会获得一个映射),initHandlerAdapter(根据url,来找到处理器,然后再去找到适配器,适配器来使用处理器,再去执行一个拦截器,然后执行具体的方法,Controller,后续继续执行拦截器代码
slf4j:门面模式
Spring用了什么模式:适配器模式,代理模式,单例模式,门面模式(对其统一进行封装)。
SpringAOP
AOP面向切面编程:
切面使某一类特定问题,,也可以理解为面向特定方法编程
比如登录校验就是一类特定问题,登录校验拦截器,对登录校验这类问题统一处理,AOP是一种思想,对某一类事物的集中处理
比如计时这个操作
long start=System.currentTimeMillis(); long end=System.currentTimeMillis(); log.info(""+(end-start)+"ms")
OOP面向对象编程
两个处理的维度不同
假如说对于所有方法的打印耗时时间
只是应用了@Aspectj的注解,但是是spring自己实现的,aspectj是一个第三方的
好处:代码无侵入,不修改原始的业务方法,就可以对原始的业务方法进行功能的增强或者功能的改变,减少了重复代码,提升了开发效率,维护方便
package com.example.demo.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;
@Slf4j
@Aspect //表示一个切面类
@Component
public class TimeAspect {
//作用域,以及作用方式
@Around("execution(* com.example.demo.demos.book.controller.*.*(..))")
//这个方法的对象表示是作用目标
public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
long start=System.currentTimeMillis();
//让目标方法执行
Object result=joinPoint.proceed();
long end=System.currentTimeMillis();
log.info(joinPoint.getSignature()+"消耗时间"+(end-start)+"ms");
return result;
}
}
切点:其中表达式成为切点@Around("execution(* com.example.de
mo.demos.book.controller.*.*(..))"),用来告诉程序对哪些方法进行功能增强
连接点:目标方法ProceedingJoinPoint joinPoint,切面要去作用的方法。
AOP概念
切点:高二五班学生(那个Controller)
连接点:小张,小李,小王(在图书管理系统中是我们的方法,就是连接点)
通知:具体的逻辑,要去做什么处理
AOP: 对一类事情的几种处理
通知:具体的逻辑,要做什么处理(xxx具体要干什么,下面的一整串代码叫通知)
切面:切点+通知(全部代码叫切面(不包括导包啥的),一个类有多个切面。
测试五大方法
@Around:环绕通知,在目标方法的前后,都被执行
@Before:前置通知,通知方法,在目标方法前执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning:返回后通知,此直接标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
package com.example.aopdemo.demos.web; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Slf4j @Aspect //表示这是一个切面类 @Component public class AspectDemo { //方法的作用域,以及作用方式 @Before("execution(* com.example.aopdemo.demos.web.controller..*.*(..))") public void doBefore(){ log.info("执行AspectDemo before"); } @After("execution(* com.example.aopdemo.demos.web.controller..*.*(..))") public void doAfter(){ log.info("执行AspectDemo after"); } @AfterReturning("execution(* com.example.aopdemo.demos.web.controller..*.*(..))") public void doAfterReturning(){ log.info("执行AspectDemo afterReturning"); } @AfterThrowing("execution(* com.example.aopdemo.demos.web.controller..*.*(..))") public void doAfterThrowing(){ log.info("执行AspectDemo afterThrowing..."); } 方法的作用域,以及作用方式 @Around("execution(* com.example.aopdemo.demos.web.controller..*.*(..))") //Around表示的是方法前后,是必须要返回结果的 public Object doAround(ProceedingJoinPoint joinPoint 目标方法) throws Throwable { //方法执行前 log.info("执行AspectDemo Around前"); //执行目标方法。 Object result=joinPoint.proceed(); //方法执行后。 log.info("执行AspectDemo doAround 后"); return result; } }
运行结果的顺序,先是Around,再是before,后面的是先执行after再是Around
当发生异常的时候,不执行AfterReturning,也不执行Around方法后的逻辑
切点的学习
切点表达式:
1.execution(根据方法的签名来匹配)
2.@annotation(根据注释匹配)
如何把切点提出来定义(代码更加优雅)
@Target({ElementType.METHOD})//表示我们的注解可以应用到哪里
@Target({ElementType.TYPE})//用于描述类,接口或者enum声明
@Target({ElementType.PARAMETER})//描述参数
@Target({ElementType.TYPE_USE})//可以标注任意类型
@Retention(RetentionPolicy.RUNTIME) //注解的生命周期
@Retention(RetentionPolicy.SOURCE)//此注解存在源代码中,如同slf4j,编译成字节码会被丢弃
@Retention(RetentionPolicy.CLASS)//编译时注解,存在于源代码和字节码中,但是运行时候会被丢弃
AOP实现方式
1.基于注解@Aspect
2.基于自定义注解
3.基于Spring API(通过xml方式,少见)
4.基于代理来实现(更久远,更笨,不推荐)
AOP回顾:一种思想,对一类事物的集中处理
统一功能:就是AOP的一种实现
切点:其中表达式成为切点@Around("execution(* com.example.demo.demos.book.controller.*.*(..))"),用来告诉程序对哪些方法进行功能增强
通知:具体做什么事情
切面=切点+通知
AOP(常考,但是相对用的少,例如:多线程)的实现方式有几种:
代理模式:
AOP常见实现方式:
1.Spring AOP
2.aspectj
Spring使用了aspectj的注解,自己进行了实现
Spring AOP原理:基于动态代理来实现的:
(租客通过房屋中介->找到房东)
切点
代理模式:
静态:(给学生提供服务之前,先分配一个老师,进行答疑,别的同学问,我再给他分配一个老师。
动态:(不提前分配,学生问的问题类型,进行分配老师)
静态代理:在程序运行前,代理对象已经对目标对象进行空步骤对预执行代码
动态代理两种常见实现方式(基于反射)
JDK动态代理只能代理接口,不能代理类。
CGIB动态代理接口和类,都能被代理。
一般问题
SpringAOP如何实现的(基于动态代理实现的)->动态代理是怎么实现的(Spring动态代理基于JDK,CGlib)->Spring使用的是哪个呢(两个都用了)->什么时候用JDK,什么时候使用CGlib(Springboot2.x开始默认使用CGLIB代理,我们可以通过spring.aop.proxy-target-class=false 设置JDK代理)->JDK和CGlib的区别(
JDK动态代理只能代理接口,不能代理类。
CGIB动态代理接口和类,都能被代理。
)