5691 2019-12-16 2024-09-03
IoC搞了这么久,终于来到了AOP。鉴于原文内容较多(近8.5万字),特地拆成上下两部分。
第一部分主要讲理论,理论指导实践;第二部分主要深入源码层面,看下Spring是如何实现AOP的。
一、术语概述
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式。在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强(非侵入式编程)。
特别的,在Spring中,有如下概念
1、连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。
Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
2、切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。
AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
在Spring中,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。
3、增强器(Advisor)
拦截切点、施加增强,增强器可以有选择性地拦截/增强目标对象中的部分方法,可以理解为增强的集合载体。
4、增强(Advice)
增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
-
前置增强 - @Before:一般在增强方法调用前调用,执行参数校验。此时增加方法还未被调用。
-
环绕增强 - @Around:一般在增强方法的调用前后做统一逻辑处理,如记录一个方法开始时间、结束时间,以统计耗时,还可以进一步对增强方法返回结果做进一步处理。
-
后置增强 - @AfterReturning:一般用于对增强方法调用完成后返回的对象做进一步处理。
-
最终增强 - @After:一般用于对增强方法调用完成后,做统一处理,与后置增强区别如下
Object result = method.invoke(); // @AfterReturning逻辑,可以修改result return result // @After逻辑,等同于finally,所以这里也是可以修改result的
5、MethodInterceptor
方法拦截器,一般与相应的增强器相绑定,一起作用于方法增强的invoke。
二、代理实现
在运行时期生成新的代理类,即在代码运行期间生成代理类.class,这被称之为动态代理。
我们还是先回顾一下Java中对于动态代理的经典实现,如下
1、JDK动态代理
// 面向接口代理
public interface UserService {
void add();
}
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("==== add ====");
}
}
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层还是通过反射
System.out.println("==== before ====");
Object result = method.invoke(target, args);
System.out.println("==== after ====");
return result;
}
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
UserService proxy = (UserService)invocationHandler.getProxy();
proxy.add();
}
// 输出结果
==== before ====
==== add ====
==== after ====
以上就是一个简单的基于JDK代理的代理模式的应用,也是一个基本的AOP的实现了,在目标对象的方法执行之前和执行之后进行了增强。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西。
我们再次来回顾一下使用JDK代理的方式,在整个创建过程中,对于InvocationHandler的创建是最为核心的,在自定义的InvocationHandler中需要重写3个方法。
- 构造方法,将代理对象传入。
- invoke方法,此方法中实现了AOP增强的所有逻辑。
- getProxy方法,此方法千篇一律,但是必不可少。
2、CGLIB动态代理
CGLB是一个强大的高性能的代码生成包。它广泛地被许多AOP的框架使用。
CGLIB包的底层通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成Java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括claas文件的格式和指令集都很熟悉。
我们先快速地了解CGLIB的使用示例。
// 面向继承代理
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 注意这里并不需要事先初始化代理对象,设置代理类的父类
enhancer.setSuperclass(CglibProxyTest.class);
enhancer.setCallback(new MethodInterceptorImpl());
CglibProxyTest cglibProxyTest = (CglibProxyTest) enhancer.create();
cglibProxyTest.test();
System.out.println(cglibProxyTest);
}
public void test () {
System.out.println("CglibProxyTest test()");
}
private static class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before invoke " + method);
Object result = proxy.invokeSuper(obj, args);
System.out.println("after invoke " + method);
return result;
}
}
// 运行结果如下
before invoke public void xiaokui1.proxy.CglibProxyTest.test()
CglibProxyTest test()
after invoke public void xiaokui1.proxy.CglibProxyTest.test()
before invoke public java.lang.String java.lang.Object.toString()
before invoke public native int java.lang.Object.hashCode()
after invoke public native int java.lang.Object.hashCode()
after invoke public java.lang.String java.lang.Object.toString()
xiaokui1.proxy.CglibProxyTest$$EnhancerByCGLIB$$e15d545a@6bf2d08e
可以看到System.out.println(cglibProxyTest),首先 调用了toString方法,然后又调用了hashCode,生成的对象为CglibProxyTest$$EnhancerByCGLIB$$e15d545a@6bf2d08e的实例,这个类是运行时由CGLIB产生的。
3、静态代理
区别于动态代理在运行时期动态生成class,静态代理主要是在虚拟机启动时通过改变对象class字节码的方式来完成对目标对象的增强,注意这里并不会生成新的代理类。
静态代理与动态代理相比具有更高的效率,因为在动态代理调用的过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是启动时便完成了字节码的增强,当系统再次调用目标类时与调用正常的类并无差别,所以效率上会相对高些。
动态代理与静态代理的比较:
- 实现方式:静态代理在生成class文件时,已把增强写入;而动态代理是在运行期间创建一个新的代理对象,并进行增强。
- 性能:静态代理稍好一些,因为少了代理的步骤。
- 灵活性:灵活性一般来说与性能是反比例关系,静态代理由于是直接写死class文件,当代码数量庞大且逻辑复杂时,静态代理将显得有点臃肿且乏力;而动态代理是运行期间生成代理,可以做很多事先class文件里面没有说明的事情,所以动态代理一般用的比较多。
ps:关于静态代理这个概念,大众普及得少。一般到了需要静态代理的层面,都是讲字节码增强。市面上常见的有 Skywalking、TTL、SandBox、Arthas,一般为agent挂载方式修改字节码(如有兴趣,可以查看我的相关文章,还未发布^_^)。
4、AspectJ
AspectJ 是 Java 语言的一个 AOP 实现(静态代理增强框架),其主要包括两个部分:
- 第一个部分定义了如何表达、定义 AOP 编程中的语法规范,通过这套语言规范,我们可以方便地用 AOP 来解决 Java 语言中存在的交叉关注点问题
- 另一个部分是工具部分,包括字节码增强写入、编译器、调试工具等(保留但并未使用)。
AspectJ 是最早、功能比较强大的 AOP 实现之一,对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。
简而言之,AspectJ是一个代码生成工具,AspectJ语法就是用来定义代码生成规则的语法。
Spring AOP主要借用了AspectJ语法,保留对静态代理的支持(字节码写入增强,并未使用)。
5、多问几个为什么
这里有两个疑问:
- 为什么JDK动态代理要求被代理类必须实现某个接口?
- 为什么CGLIB动态代理不能代理final方法?(这个可能好一点理解,final方法无法被子类重写)
以下为个人的一点思考
public class ProxyClass {
static class A {
void a(){}
}
interface C {
void c();
}
static class B implements C {
@Override
public void c() {
}
}
// 假设这里分别传入 new A()、new B()
public Object createProxy(Object obj) {
// 基础类型不支持代理
if (obj.getClass().isPrimitive()) {
throw new RuntimeException("不支持的代理类型:" + obj.getClass());
}
// new A() cglib代理
if (obj instanceof Class) {
// 继承相比较接口,更为通用广泛
// 如果传入的是class对象,那么通过字节码技术生成被代理的子类
// 后续对代理对象方法调用,就是直接调用实例增强方法,CGLIB的经典实现
// 例如传入A.class,那么我通过调用A的已声明非final方法就可以实现增强效果
Object proxyA = new Object();
A a = (A) proxyA;
a.a();
}
// new B() jdk代理
else {
// 这里想象以下,除了通过对象的Class对象知道对象的具体方法,还有什么其他路径吗?
// 在Java中区别于继承的就是接口了
// 这里我主观意淫一下:CGLIB率先抢占了继承代理,而JDK动态代理只能退而取其次采用接口代理
// 因为继承虽然不能继承final方法,但使用范围远比 接口只能代理接口方法 条件限制宽泛得多
// 两者性能不在本次讨论范围之内
Object proxyB = new Object();
C b = (C) proxyB;
b.c();
}
return null;
}
}
简而言之,在Java生态中,只有面向接口和继承两种方式,可以获取原对象的方法信息(ClassMetaData),从而瞒天过海进行动态代理。
三、两个配置项
@EnableAspectJAutoProxy注解可用于显示开启Spring AOP动态代理,其中有两个配置项,我们需要关注一下。
// @EnableAspectJAutoProxy 注解起作用方式将会在后续文章讨论
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// 指示是否要创建基于子类 (CGLIB) 的代理,而不是基于标准 Java 接口的代理。默认值为false
// 默认优先使用面向接口的jdk代理,但cglib面向继承代理相对接口而言更为通用
boolean proxyTargetClass() default false;
// 是否暴露原始代理对象到 ThreadLocal,可通过 AopContext 类来获取
boolean exposeProxy() default false;
}
1、proxyTargetClass
是否强制使用CGLIB代理。
- JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理(如果是实现了多个接口,那么代理类也是实现多个接口的,但代理类型仍然为com.sun.proxy.$Proxy0,可以通过强制转换到某个实现接口类型)。
- CGLIB代理:实现原理类似与JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强。
2、expose-proxy
是否暴露被代理对象。
有时候目标对象内部的自我调用将无法实施切面中的增加,这时就需要这个属性了。如下代码
// 当增强方法A内部调用增强方法B,这时只触发方法A的增强
// 当需要触发方法B增强,那么可以使用下面办法
// 更为细致的讨论考虑见章节 一个复杂的例子
((UserService)AopContext.currentProxy()).test();
ps:为了避免可能出现的潜在兼容问题,请尽量不要使用上面这种方式。可以考虑 SpringUtil.getBean 或者业务类拆分的方式来实现。
接下来,我们看下Spring AOP中有哪些坑?我敢说,你至少会遇到一个(均为本人亲身经历)。
四、JDK代理出现空指针?
奇怪问题一之JDK动态代理失败:报空指针。
1、相关代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Log {
/**
* 记录方法耗时
*/
boolean statisticTime() default true;
}
@Slf4j
@Aspect
@Component
public class LogAspect {
private final Class[] aspectClass = {Log.class};
/**
* 扫描含有@Log注解的方法
*/
@Pointcut(value = "@annotation(com.example.demo.service.Log)")
public void cutService() {
}
@Around("cutService()")
public Object cutService(ProceedingJoinPoint point) throws Throwable {
Object result = null;
Signature signature = point.getSignature();
// 如果不是应用在方法级别
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
Method method = ((MethodSignature) signature).getMethod();
result = dealLog(point, method);
return result;
}
private Object dealLog(ProceedingJoinPoint point, Method method) throws Throwable{
Object target = null;
Log annotation = method.getAnnotation(Log.class);
boolean statisticTime = annotation.statisticTime();
if (statisticTime) {
long startTime = System.currentTimeMillis();
// 执行方法
System.out.println("准备执行方法:" + method.getName());
target = point.proceed();
long duration = System.currentTimeMillis() - startTime;
System.out.println("执行方法" + method.getName() + "耗时" + duration + "ms") ;
}
return target;
}
}
public interface UserService {
void testA();
void testB();
}
@Service
public class UserServiceImpl implements UserService {
@Log
@Override
public void testA() {
System.out.println("调用testA,再调用testB");
testB();
}
@Log
@Override
public void testB() {
System.out.println("调用testB,再调用testC");
testC();
}
public void testC() {
System.out.println("调用testC");
}
}
@Slf4j
@Component
public class TestAfterRunner implements ApplicationRunner {
@Autowired
private UserService userService;
@Override
public void run(ApplicationArguments args) throws Exception {
// 通过第二部分,我们知道具体干活的是类 AnnotationAwareAspectJAutoProxyCreator extends ProxyConfig
// 这里我们只关心父类 ProxyConfig 中的方法
ProxyConfig proxyConfig = SpringUtil.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
log.info("exposeProxy={} proxyTargetClass={}", proxyConfig.isExposeProxy(), proxyConfig.isProxyTargetClass());
// 什么都不做,spring 默认配置 exposeProxy=false proxyTargetClass=true
// 本类指定 @EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true),则为 exposeProxy=true proxyTargetClass=true
// 还可以通过 spring.aop.proxy-target-class来控制,
System.out.println(userService.getClass());
userService.testA();
}
}
当设置proxy-target-class: true时,输出如下
// 默认为true
class com.example.demo.service.UserServiceImpl$$EnhancerBySpringCGLIB$$120d515a
准备执行方法:testA
调用testA,再调用testB
调用testB,再调用testC
调用testC
执行方法testA耗时6ms
当设置proxy-target-class: false时,输出如下
// spring.aop.proxy-target-class = false
Caused by: java.lang.NullPointerException: null
at com.example.demo.service.LogAspect.dealLog(LogAspect.java:61) ~[classes/:na]
at com.example.demo.service.LogAspect.cutService(LogAspect.java:46) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
2、代理生成的.class
出现上面第二种情况很奇怪:Spring的Aspect无法拦截jdk代理接口上的注解,而CGLIB却可以。我们查看两者生成的class文件,如下
1、JDK生成的.class
// JDK代理生成 $Proxy0.class
// 直接在Spring中无法生成,需要单独拿出来
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
UserService userService = new UserServiceImpl();
JdkProxy jdkProxy = new SimpleJdkProxy();
jdkProxy.setObject(userService);
UserService userService = (UserService)jdkProxy.getProxy();
userService.testA();
}
public final class $Proxy0 extends Proxy implements UserService {
// 分别对应equals、toString、testA、testB、hashCode方法
// 丢弃了原始注解
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("site.xiaokui.spring.test.aop.UserService").getMethod("testA");
m4 = Class.forName("site.xiaokui.spring.test.aop.UserService").getMethod("testB");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void testA() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void testB() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
2、CGLIB生成的.class
// CGLIB生成3个class UserServiceImpl$$FastClassByCGLIB$$7444a6e.class UserServiceImpl$$EnhancerByCGLIB$$11fec01d.class UserServiceImpl$$EnhancerByCGLIB$$11fec01d$$FastClassByCGLIB$$57403537.class
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/hk/TEMP");
UserService userService = new UserServiceImpl();
IProxy proxy = new SimpleCglibProxy();
proxy.setObject(userService);
userService = (UserService) proxy.getProxy();
userService.testA();
}
// 3个class生成的代码行数可达 850 行,这里只显示一部分
public class UserServiceImpl$$FastClassByCGLIB$$7444a6e extends FastClass {
public UserServiceImpl$$FastClassByCGLIB$$7444a6e(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1147415770:
if (var10000.equals("testA()V")) {
return 1;
}
break;
// 省略大部分代码
return -1;
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
UserServiceImpl var10000 = (UserServiceImpl)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.testB();
return null;
case 1:
var10000.testA();
return null;
case 2:
var10000.testC();
return null;
case 3:
return new Boolean(var10000.equals(var3[0]));
case 4:
return var10000.toString();
case 5:
return new Integer(var10000.hashCode());
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
UserServiceImpl var10000 = new UserServiceImpl;
UserServiceImpl var10001 = var10000;
int var10002 = var1;
try {
switch(var10002) {
case 0:
var10001.<init>();
return var10000;
}
} catch (Throwable var3) {
throw new InvocationTargetException(var3);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public int getMaxIndex() {
return 5;
}
}
FastClass是Cglib实现的一种通过给方法建立下标索引来访问方法的策略,为了绕开反射。 上面的描述代表MethodPeoxy可以根据对方法建立索引调用方法,而不需要使用传统Method的invoke反射调用,提高了性能,当然额外的得多生成一些类信息。
3、分析结果
先上结果,如下
// 问题出在这一行
// 获取签名
Signature signature = point.getSignature();
// 这个方法是代理对象上面Method
// 因此对于CGLIB是可以继承注解的,而JDK代理是没有注解的
// 因此对于CGLIB方法是可以看着等效的,而JDK是抽象方法,自然获取不到后面的注解
// ps 这种使用方式其实很多,一般来说不会有问题,但如果你非要强制使用jdk动态时会出问题
// ps 不信? 你可以看下你系统代码里面的切面注解使用方式
Method method = ((MethodSignature) signature).getMethod();
// 改为如下代码即可解决
// 被代理对象
Object realObj = point.getTarget();
MethodSignature msig = (MethodSignature) signature;;
// 这个方法才是被代理对象的,因此就可以获取到了注解信息
Method currentMethod = realObj.getClass().getMethod(msig.getName(), msig.getParameterTypes());
核心解决方法是获取被代理类上面的注解,这就是为什么Spring中@EnableAspectJAutoProxy#proxyTargetClass值默认为false,但Spring Boot却在配置文件帮你设置成true的原因 。
// 类 EnableAspectJAutoProxy
boolean proxyTargetClass() default false;
// 文件 spring-configuration-metadata.json
{
"name": "spring.aop.proxy-target-class",
"type": "java.lang.Boolean",
"description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
"defaultValue": true
}
五、代理方法如何连续增强?
对于对象方法内部的连续增强调用,该如何增强呢?
还是以上文例子分析,我们想在增强testA方法的同时下,又增强testB(默认是不会触发增强的,从字节码层面我们就不难看出,这是在代理类的内部方法调用,不会走代理类上层(Advisor-增强器)的 invoke 方法,自然无法触发增强)。
可以通过如下三种方法来实现增强:
1、暴露代理对象,调用代理对象的增强方法,而不是默认的this实例方法内部调用
// 暴露代理对象,不暴露则报错 Cannot find current proxy
// 此时与 proxyTargetClass 无关,true or false都不影响
@EnableAspectJAutoProxy(exposeProxy = true)
@Log
@Override
public void testA() {
System.out.println("调用testA,再调用testB");
((UserService) AopContext.currentProxy()).testB();
// testB();
}
2、改造成bean方法调用
// 这种大家也好理解,不做解释
SpringUtil.getBean(UserService.class).testB();
3、服务分块
通过代码改造,使之不是实例内部方法调用。
三种方法本质都是同一个解决思路,关键正确的调用代理bean的方法,而不是调用被代理对象的方法。
六、AspectJ非用不可?
前文我们知道,Spring主要用了AspectJ的AOP规则语法,让我们可以更加方便快捷的识别的哪些方法需要被增强、什么时候被增强,以及具体如何增强。
一个切面本质就是一个增强器,我们也可以在完全不用AspectJ的情况,手动编写增强器代码来实现增强。如下
// 打个广告,关注微信公众号:好看的HK,第一时间掌握最新动态!
@Component
public class TestService {
private String name;
public TestService() {
}
public TestService(String name) {
this.name = name;
}
public void test1() {
System.out.println(this.name + " called test1");
}
}
@Component
public class CustomTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator implements PriorityOrdered, BeanPostProcessor, ApplicationRunner {
private final TestService testService1 = new TestService("server1");
private final TestService testService2 = new TestService("server2");
@Override
public void run(ApplicationArguments args) throws Exception {
for (int i = 0; i < 10; i++) {
SpringUtil.getBean(TestService.class).test1();
}
}
@Override
public int getOrder() {
// 必须,必须赶在 AnnotationAwareAspectJAutoProxyCreator 前完成注册
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AnnotationAwareAspectJAutoProxyCreator) {
AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = (AnnotationAwareAspectJAutoProxyCreator) bean;
annotationAwareAspectJAutoProxyCreator.setCustomTargetSourceCreators(this);
}
return bean;
}
// 此处用法并不是特例,详见类 AbstractBeanFactoryBasedTargetSource、CommonsPool2TargetSource、ThreadLocalTargetSource
@Override
protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {
if (TestService.class.isAssignableFrom(beanClass)) {
return new AbstractBeanFactoryBasedTargetSource() {
@Override
public Object getTarget() throws Exception {
// 在这里可以干你任何你想干的事情,包括但不限于方法重写、字节码增强、动态获取对象等
if (RandomUtil.randomBoolean()) {
return testService1;
} else {
return testService2;
}
}
};
}
return null;
}
}
// 省略输出
Spring提供了一个前置代理的机会,方便用户自己扩展,相关上层接口为 TargetSource、AbstractBeanFactoryBasedTargetSource(下一章节将会深入到源码层面看下具体实现)。
主要使用场景为 动态获取对象,常见的类有 AbstractBeanFactoryBasedTargetSource、CommonsPool2TargetSource、ThreadLocalTargetSource。
七、AspectJ非注解不可?
注解的本质也是代码,我们也可以通过硬编码来实现@AspectJ中的注解功能。如下
// 大概是这个样子,那么super.findCandidateAdvisors();就会找到这个Advisor并应用于bean,判断是否需要代理
// 使用AspectJ底层也是到这里,不过相对于这种显式声明Advisor,AspectJ是隐式的且自动构建的
@Component
public class SysAdvisor extends AbstractPointcutAdvisor implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
SpringUtil.getBean(TestService.class).test1();
}
@Override
public Pointcut getPointcut() {
return new Pointcut() {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
if (TestService.class.isAssignableFrom(targetClass)) {
System.out.println(method + "成功匹配代理规则");
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return matches(method, targetClass);
}
};
}
};
}
@Override
public Advice getAdvice() {
class TempAdvice implements MethodBeforeAdvice, AfterAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(method.getName()+"手动加入增强开始");
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(method.getName()+"手动加入增强结束");
}
}
return new TempAdvice();
}
}
八、总结
很喜欢我大学时期学习Spring AOP领悟出来的一段话,如下:
- 看山是山,看水是水
- 看山不是山,看水不是水
- 看山还是山,看水还是水
总访问次数: 37次, 一般般帅 创建于 2019-12-16, 最后更新于 2024-09-03
欢迎关注微信公众号,第一时间掌握最新动态!