思路
我们使用@Aroud,@Before,@After来确定是在这个类的方法的什么时候进行处理属于是类级别的注释,使用其他的注解(方法级别)来定义要做的事 即如下的样子
@Component @Around("aroundInvocationHandler") public class OriginBean {
@Value("${customer.name}") public String name;
@Polite public String hello() { return "Hello, " + name + "."; }
public String morning() { return "Morning, " + name + "."; } }
|
AOP的本质实际上也就是代理,和我们前文所处理的BeanPostProcessor区别在于,BeanPostProcessor在Bean生命周期的固定阶段执行,而AOP在方法调用时动态执行。BeanPostProcessor - 适合Bean级别的处理,AOP - 适合方法级别的横切关注点。
按照廖老师的说法,实现方法拦截的不外乎3种
- 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
- 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
- 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。
最后一种相对简单,不涉及到任何的JVM底层:1.使用Java标准库的动态代理机制,不过仅支持对接口代理,无法对具体类实现代理。2.使用CGLIB或Javassist这些第三方库,通过动态生成字节码,可以对具体类实现代理。
我们仅实现简单的通过注解进行AOP的方式
实现ProxyResolver
动态代理需要两种bean:1.原始的bean,2.拦截器,拦截对应的bean自动调用拦截器实现代理功能
拦截器的接口我们使用java标准库自带的InvocationHandler
应为CGLIB已经不维护了,所以我们选用的是bytebuddy
实现ProxyResovler接口,官网有相关的使用方式
依赖引入
<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>LATEST</version> </dependency>
|
public class ProxyResolver { ByteBuddy byteBuddy = new ByteBuddy();
public <T> T createProxy(T bean, InvocationHandler handler) { Class<?> targetClass = bean.getClass(); Class<?> proxyClass = this.byteBuddy .subclass(targetClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR) .method(ElementMatchers.isPublic()).intercept(InvocationHandlerAdapter.of( new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return handler.invoke(bean, method, args); } })) .make() .load(targetClass.getClassLoader()).getLoaded(); Object proxy; try { proxy = proxyClass.getConstructor().newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } return (T) proxy; } }
|
注意InvocationHandler有两层:外层的invoke()传入的Object是Proxy实例,内层的invoke()将调用转发至原始Bean。
对于每个注解我们都要编写一个InvocationHandler
public class PoliteInvocationHandler implements InvocationHandler { @Override public Object invoke(Object bean, Method method, Object[] args) throws Throwable { if (method.getAnnotation(Polite.class) != null) { String ret = (String) method.invoke(bean, args); if (ret.endsWith(".")) { ret = ret.substring(0, ret.length() - 1) + "!"; } return ret; } return method.invoke(bean, args); } }
|
实现@Aroud注解
上一节实现了代理类的装配,也就相当于实现AOP的关键点——生成代理已经完全实现了。
注解设计:要有一个值来指定查找什么样子的拦截器。和上节实现Polite注解一样
我们要实现一个InvocationHandler来指定这个注解会做什么
@Component public class AroundInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getAnnotation(Polite.class) != null) { String ret = (String) method.invoke(proxy, args); if (ret.endsWith(".")) { ret = ret.substring(0, ret.length() - 1) + "!"; } return ret; } return method.invoke(proxy, args); } }
|
在IOC容器中装配AOP,AOP的装配是在实例化完成后通过BeanPostProcessor实现的,所以我们需要实现一个AroundProxyBeanPostProcessor。使得在bean实例化完成之后能对@Aroud的类进行装配
public class AroundProxyBeanPostProcessor implements BeanPostProcessor {
Map<String, Object> originBeans = new HashMap<>();
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Class<?> beanClass = bean.getClass(); Around anno = beanClass.getAnnotation(Around.class); if (anno != null) { String handlerName; try { handlerName = (String) anno.annotationType().getMethod("value").invoke(anno); } catch (ReflectiveOperationException e) { throw new AopConfigException(); } Object proxy = createProxy(beanClass, bean, handlerName); originBeans.put(beanName, bean); return proxy; } else { return bean; } }
Object createProxy(Class<?> beanClass, Object bean, String handlerName) { ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextUtils.getRequiredApplicationContext(); BeanDefinition def = ctx.findBeanDefinition(handlerName); if (def == null) { throw new AopConfigException(); } Object handlerBean = def.getInstance(); if (handlerBean == null) { handlerBean = ctx.createBeanAsEarlySingleton(def); } if (handlerBean instanceof InvocationHandler handler) { return ProxyResolver.getInstance().createProxy(bean, handler); } else { throw new AopConfigException(); } }
@Override public Object postProcessOnSetProperty(Object bean, String beanName) { Object origin = this.originBeans.get(beanName); return origin != null ? origin : bean; } }
|
对于Before和After,Around其实已经包含了,我们只需要使用Adapter模式提供两个拦截器模版,实现这两个模板就好
public abstract class BeforeInvocationHandlerAdapter implements InvocationHandler {
public abstract void before(Object proxy, Method method, Object[] args);
@Override public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(proxy, method, args); return method.invoke(proxy, args); } } public abstract class AfterInvocationHandlerAdapter implements InvocationHandler { public abstract Object after(Object proxy, Object returnValue, Method method, Object[] args);
@Override public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = method.invoke(proxy, args); return after(proxy, ret, method, args); } }
|
对于类@Aroud注解的拓展
比如说spring中的@Transactional,其逻辑也是AOP所做的,那我们就需要一个TransactionProxyBeanPostProcessor。
所以我们可以使用泛型来增强复用。用泛型代码处理Annotation,先抽象出一个AnnotationProxyBeanPostProcessor
public abstract class AnnotationProxyBeanPostProcessor<A extends Annotation> implements BeanPostProcessor {
Map<String, Object> originBeans = new HashMap<>(); Class<A> annotationClass;
public AnnotationProxyBeanPostProcessor() { this.annotationClass = getParameterizedType(); } ... }
|
后续如果想添加注解,就只需要如下即可了
public class TransactionalProxyBeanPostProcessor extends AnnotationProxyBeanPostProcessor<Transactional> { }
|
总结
首先我们先对方法的调用实现了拦截,使得一个对象的所有public方法被设置了拦截器后返回新的代理对象。即在这一步之后,该类的所有方法调用的时候都会去经过实现的拦截器。
搞定了最难的方法拦截之后,接下来的就是对@Aroud要做的事,拦截器的实现,以及何时调用ProxyResolver生成代理的确定了。
接着我们会实现一个实现了InvocationHandler的拦截器,在其中invoke写入逻辑,比如在方法识别到什么注解的时候会干什么事
此时我们有了原始bean,实现了拦截器,这是就可以在IOC中装配AOP了,AOP的装配使用BeanPostProcessor自动装配,所以我们会实现一个BeanPostProcessor,做的事情就是查找是否有Aroud注解,如果有就返回实现Aroud值中拦截器的代理类,这个时候,这个代理类每次调用类的时候就会收到方法拦截器的拦截了。在廖老师的例子里并没有很好的体现Aroud的样式,实际上就是在拦截器的invoke前后添加就是一个环绕了,同理前后也是
生成代理和bean创建中的BeanPostProcessor有关