前言
🌟🌟本期讲解关于spring aop的切面表达式和自身实现原理介绍~~~
🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客
🔥 你的点赞就是小编不断更新的最大动力
🎆那么废话不多说直接开整吧~~
目录
📚️1.切点表达式
🚀1.1execution表达式
🚀1.2@annotation
1.自定义注解
2.切面类
3.添加注解
📚️2.Spring AOP原理
🚀2.1代理模式
1.静态代理(了解)
2.动态代理(八股)
🚀2.2Spring AOP原理总结
📚️3.总结
📚️1.切点表达式
我们在上一期了解到了关于通知中切点表达式execution表达式,但是没有做很详细的介绍,不仅如此在切点表达式中,有两种方式,下面来一一介绍
🚀1.1execution表达式
切点表达式的结构如下所示:
execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
而在我们上期写的表达式中格式如下:
@Around("execution(* com.example.springaop.controller.*.*(..))")
解释:
* :匹配任意字符,只匹配⼀个元素(返回类型, 包, 类名, ⽅法或者⽅法参数)
.. :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
上述的controller后的两个 * 代表就是每一层,括号里的 .. 就是表示任意的方法参数;这里也可以代表无参数;
举例:
execution(public String com.example.demo.controller.TestController.t1())
解释:testcontroller类下的名为t1的方法,并且这里的方法是无参的,访问修饰符可以省去;
execution(* com.example.demo.controller.TestController.t1())
解释:这里可以访问的就是没有任何限制的访问权限,匹配所有返回类型;
execution(* com.example.demo.controller.TestController.*())
解释:这里匹配所有的方法,这些方法是无参的;
execution(* com.example.demo.controller.*.*(..))
解释:这里匹配controller包下所有类的所有方法
execution(* com..TestController.*(..))
解释:这里匹配com包下的所有testcontroller类的所有方法
🚀1.2@annotation
我们在上述的简述中了解到,关于execution表达式如何作用于匹配某个方法和类,但是当存在两个类,都要进行匹配,并且里面的方法也是无规则的,那如何呢,此时就要@annotation进行操作了;
假如有以下的两个控制类:
@RestController
@RequestMapping("/test")
public class TestController {@RequestMapping("/t1")public String test1(){log.info("这是t1执行");return "t1";}@RequestMapping("/t2")public String test2(){log.info("这是t2在执行");return "t2";}
}
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/u1")public String user1(){return "u1";}@RequestMapping("/u2")public String user2(){return "u2";}
}
那么此时我们要将第一个控制器类的第一个方法进行匹配,以及第二个控制器类的方法进行匹配,那么此时的操作就是如下所示:
1.自定义注解
代码如下所示:
//定义自己的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅.
@Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期
2.切面类
使⽤ @annotation 切点表达式定义切点, 只对 @MyAspect ⽣效
代码如下所示:
@Aspect
@Component
@Slf4j
public class MyAnnotationAspect {@Before("@annotation(com.example.springaop.config.MyAspect)")public void doBefore(){log.info("before start...");}@After("@annotation(com.example.springaop.config.MyAspect)")public void doAfter(){log.info("after start...");}
}
这里小编设置了两种通知类,第一种是在目标方法执行前进行执行,第二种通知是在方法执行后进行执行,当然表达式里的的是我们自己注解的全限定路径,加上我们的定义注解名称;
3.添加注解
代码如下所示:
@MyAspect@RequestMapping("/t1")public String test1(){log.info("这是t1执行");return "t1";}
这是第一个类的第一个方法;
@MyAspect@RequestMapping("/u2")public String user2(){return "u2";}
这是第二个类的第二个方法;
此时就与这两个方法进行了匹配,那么就可执行切面通知了;
📚️2.Spring AOP原理
🚀2.1代理模式
代理模式也叫委托模式定义:为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤(其实就是中介的作用)
1. Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)
2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.(真正的房东)
3. Proxy: 代理类. RealSubject的代理 (中介)
这里的代理模式分为两种
• 静态代理: 由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译, 在程序运⾏前代理类的
.class ⽂件就已经存在了.
• 动态代理: 在程序运⾏时, 运⽤反射机制动态创建⽽成
1.静态代理(了解)
代码如下:
定义一个subject业务接口;
public interface SubjectHouse {void rentHouse();
}
让代理对象继承这个接口,并写出要代理什么
public class RealSubjectHouse implements SubjectHouse{//真正的房东@Overridepublic void rentHouse(){System.out.println("我是房东,我要出租房子");}
}
最后中介,进行代理
public class HouseProxy implements SubjectHouse{private SubjectHouse subjectHouse;public HouseProxy(SubjectHouse subjectHouse) {this.subjectHouse = subjectHouse;}@Overridepublic void rentHouse() {System.out.println("我是中介开始代理");subjectHouse.rentHouse();System.out.println("我是中介结束代理");}
}
解释这里使用了一个向上转型的思想,这里实际指定就是代理的对象就是realsubjecthouse(即真正的房东);
从上述代码可以看出, 我们修改接⼝(Subject)和业务实现类(RealSubject)时, 还需要修改代理类
(Proxy).
同样的, 如果有新增接⼝(Subject)和业务实现类(RealSubject), 也需要对每⼀个业务实现类新增代理类(Proxy).
2.动态代理(八股)
相⽐于静态代理来说,动态代理更加灵活.
我们不需要针对每个⽬标对象都单独创建⼀个代理对象, ⽽是把这个创建代理对象的⼯作推迟到程序运
⾏时由JVM来实现. 也就是说动态代理在程序运⾏时, 根据需要动态创建⽣成
这里的动态代理分为两种
JDK动态代理;
CGLIB动态代理;
JDK动态代理代码(了解)
public class JDKInvocationHandler implements InvocationHandler {private Object target;//目标对象public JDKInvocationHandler(Object target) {this.target = target;}/*** 代理对象 通过 invoke调用 目标对象的方法*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是代理, 开始代理");//通过反射, 调用目标对象的方法Object result = method.invoke(target, args);System.out.println("我是代理, 结束代理");return result;}
}
进行main方法调用:
SubjectHouse proxy= (SubjectHouse) Proxy.newProxyInstance(SubjectHouse .class.getClassLoader(),new Class[]{SubjectHouse .class},new JDKInvocationHandler(target));proxy.rentHouse();
newProxyInstance() , 这个⽅法主要⽤来⽣成⼀个代理对象
Loader: 类加载器, ⽤于加载代理对象.
interfaces : 被代理类实现的⼀些接⼝(这个参数的定义, 也决定了JDK动态代理只能代理实现了接⼝的⼀些类)
h : 实现了 InvocationHandler 接⼝的对象,target被代理对象
CGLIB动态代理代码
添加依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
public class CGLibMethodInterceptor implements MethodInterceptor {private Object target;public CGLibMethodInterceptor(Object target) {this.target = target;}/*** 调用代理对象的方法*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("我是中介, 开始代理");Object result = method.invoke(target, args);System.out.println("我是中介, 结束代理");return result;}
}
最后进行调用:
SubjectHouse houseSubject = (SubjectHouse) Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target));houseSubject.rentHouse();
上述存在一个比较重要的知识点:
JDK动态代理存在一个致命的问题,即只能代理接口,不能代理类,而CGLIB既可以代理接口,又可以代理类;
🚀2.2Spring AOP原理总结
spring AOP原理,是要从源码进行解读的,但是源码过于复杂,最终的情况就是如下所示:
在源码中的代理⼯⼚有⼀个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置(这里的默认值是根据不同情况来进行定义的)
大致意思就是在 proxyTargetClass为false时,在实现接口时使用JDK代理,只实现类的情况下,使用CGLIB代理,若proxyTargetClass,为true,那么所有实现方式都使用CGLIB代理;
Spring默认使用的的proxyTargetClass为false,但是在spring boot2.x之后proxyTargetClass默认为true,使用CGLIB进行代理;
当然我们可以使用配置项进行配置
spring.aop.proxy-target-class=false
这里设置proxyTargetClass默认为false;
📚️3.总结
本期主要讲解了关于切点表达式的两种表达方式,以及Spring AOP实现原理的两种代理模式,即JDK代理,以及CGLIB代理,最后进行Spring AOP原理的总结;
🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!
💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。
😊😊 期待你的关注~~~