title: 07.实现ProxyFactory
tag: 笔记 手写SSM AOP
通过JDK的动态代理和ByteBuddy
字节码技术来生成代理代理对象
Spring的AOP
在实现了IOC容器后,我们就可以继续实现AOP了,在这之前我们需要首先了解Spring的AOP。
AOP与代理(Proxy)模式基本类似,关于代理模式在之前实现BeanPostProcessor时已经介绍过,基于一个接口或者基于继承来代理一个类对其功能进行增强。
我们又可以根据生成代理对象的方式分为两种代理:
- 静态代理
这种代理方式就是手动为被代理对象编写一个代理对象来实现代理。
- 动态代理
这种方式是在运行期间动态生成一个代理类的字节码码文件(.class文件),并通过Java的类加载器ClassLoader
将动态生成的代理类字节码文件进行加载生成一个Class对象在堆中,这样就可以拿到代理类对象了。
同时,动态代理又有两种实现方式:
- 使用Java标准库的动态代理机制,不过仅支持对接口代理,无法对具体类实现代理;
- 使用CGLIB或Javassist这些第三方库,通过动态生成字节码,可以对具体类实现代理。
Spring的实现方式是:
- 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
- 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。
除了实现代理外,还得有一套机制让用户能定义代理。Spring又提供了多种方式:
- 用AspectJ的语法来定义AOP,比如
execution(public * com.itranswarp.service.*.*(..))
; - 用注解来定义AOP,比如用
@Transactional
表示开启事务。
Summer的实现思路
我们使用和Spring相同的代理实现方式:
- 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
- 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。
而切面的类我们同样仿照Spring的方式来实现:
- 切面(Aspect):用户需要使用
@Aspect
定义一个切面类。 - 通知(Advice):我们项目仅支持Around的通知,因为其它通知可以使用Around来实现。
- 切入点(Pointcut):我们仅支持用户自定义注解来定义切面,用户需要在@Around注解中传入需要匹配的注解。
- 目标对象(Target Object):即被代理的对象。
明确了需求,我们来看如何实现动态生成字节码。Spring采用的是CGLIB,而目前CGLIB已经停止维护,因此我们选择ByteBuddy来动态生成字节码。
我们这里先实现代理的创建,我们使用工厂模式来创建代理对象,并且使用策略模式来根据不同情况选择不同的方式来生成代理。
实现代理对象的创建
- 如果被代理对象实现了了接口,Spring直接使用JDK实现对接口的代理。
- 如果被代理对象没有实现接口,那么Spring就使用CGLIB动态生成字节码实现代理。
看到上面的需求我们就可以很自然的想到策略模式来实现。
实现创建代理的不同策略
我们先创建一个策略的接口GeneratorProxyStrategy
:
public interface GeneratorProxyStrategy {
Object createProxy(Object bean, InvocationHandler handler);
}
策略类必须要实现一个createProxy
方法。
之后我们可以根据需求实现GeneratorProxyStrategy
:
- 使用
ByteBuddy
动态生成字节码,这种方式是基于继承方式实现的代理。
public class ByteBuddyStrategy implements GeneratorProxyStrategy{
ByteBuddy byteBuddy = new ByteBuddy();
@Override
public Object createProxy(Object bean, InvocationHandler handler){
Class<?> targetClass = bean.getClass();
Class<?> proxyClass = byteBuddy
.subclass(targetClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)//默认构造器创建子类
.method(ElementMatchers.isPublic()) //代理pubulic方法
.intercept(InvocationHandlerAdapter.of(
handler)) //传入InvocationHandler,代理方法的逻辑
.make() //生成字节码
.load(targetClass.getClassLoader()) // 使用类加载器加载代理对象Class到堆中
.getLoaded(); //拿到被代理对象
Object proxy = null;
try {
proxy = proxyClass.getConstructor().newInstance();
}catch (Exception e){
throw new RuntimeException(e);
}
return proxy;
}
}
ByteBuddy
生成代理对象的写法不难理解,我们需要重点注意的只是InvocationHandler
。这个我们在最后介绍。
- 使用
JDK
动态生成字节码,这种方式是基于接口方式实现的代理。
public class JDKStrategy implements GeneratorProxyStrategy{
@Override
public Object createProxy(Object bean, InvocationHandler handler) {
return Proxy.newProxyInstance(bean.getClass().getClassLoader(), //传入类加载器
bean.getClass().getInterfaces(), //拿到要代理的接口方法
handler);
}
}
实现ProxyFactory
/**
* @author 白日
* @create 2024/1/1 16:06
* @description 创建代理对象的工厂
*/
public class ProxyFactory {
GeneratorProxyStrategy proxyStrategy;
public Object createProxy(Object bean, InvocationHandler handler){
chooseStrategy(bean);//选择代理策略
return proxyStrategy.createProxy(bean, handler);//创建实例
}
/**
* 根据对象的接口数量,选择代理策略
* @param bean 代理对象
*/
private void chooseStrategy(Object bean){
if(isUniqueInterface(bean.getClass())){
this.proxyStrategy = new JDKStrategy();
}else{
this.proxyStrategy = new ByteBuddyStrategy();
}
}
//该类是否只有一个接口
private Boolean isUniqueInterface(Class<?> clazz){
Class<?>[] interfaces = clazz.getInterfaces();
return interfaces.length == 1;
}
}
这样,我们只需要一个Bean和一个InvocationHandler就可以创建这个Bean的一个代理对象,最后我们来了解一个这个InvocationHandler是什么。
InvocationHandler
InvocationHandler
接口是proxy
代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler
接口类的invoke
方法来调用。
invoke
方法的签名:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
- proxy:表示代理对象
- method:表示被代理对象的一个方法
- args:方法的参数
我们可以在invoke
方法中编写代理对象方法的执行逻辑。
前面说过代理对象调用方法时都会将调用转发到InvocationHandler
接口类的invoke
方法来调用,那么我们在invoke方法中再次使用proxy调用method方法,就会再次转发到InvocationHandler
接口类的invoke
方法,这样就可以实现一种递归的结构。
下面我们编写一个InvocationHandler
实现回溯:
public class PoliteInvocationHandler implements InvocationHandler {
int index = 0;
Object target; //原始对象
public PoliteInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method originMethod = target.getClass()
.getDeclaredMethod(method.getName(), method.getParameterTypes());
// 修改标记了@Polite的方法返回值:
if (originMethod.getAnnotation(Polite.class) == null) {
return method.invoke(proxy, args);
}
if(index == 3) return method.invoke(target); // 递归出口,调用被代理对象的方法
index++;
System.out.println("执行被代理方法前" + index);
Object res = method.invoke(proxy,args); //递归入口,等于Object res = invoke(proxy, method, args);
System.out.println("执行被代理方法后" + index);
index--;
return res;
}
}
执行效果:
执行被代理方法前1
执行被代理方法前2
执行被代理方法前3
hello1,Bob
执行被代理方法后3
执行被代理方法后2
执行被代理方法后1
从执行结果可以看出我们在InvocationHandler中实现了递归的结构,这在下节实现Around中一个PointCut
对应多个Advice
的主要思想。