MagicWolf

Spring AOP浅析(一) 代理模式及实现

Spring AOP 是代理模式的应用,可以使用JDK提供的Proxy类或通过字节码增强来实现。想要知道Spring如何实现AOP就必须要先了解代理模式。

1 代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问;
使用代理模式创建代理对西安,让代理对象控制某对象的访问,被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象

–《Head First 设计模式》

1.1 UML类图

2 代理模式实现

参照上面的UML类图,先写出Subject接口和实际对象

1
2
3
4
5
6
7
8
9
10
11
12
//抽象角色
public interface Subject {
public void request(String param);
}
//目标角色
public class RealSubject implements Subject{
@Override
public void request(String param) {
System.out.println("param:"+param);
}
}

2.1 静态代理

静态代理的实现很简单,对目标角色进行封装,并拦截方法调用。这里实现一个参数验证的代理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class StaticProxy implements Subject{
Subject realSubject;
public StaticProxy(Subject realSubject) {
this.realSubject=realSubject;
}
public void before(String param){
if(!param.equals("magicalwolf"))//验证参数
throw new IllegalArgumentException();
}
@Override
public void request(String param) {
before(param);
realSubject.request(param);
}
}
public class Main {
public static void main(String[] args) {
Subject target=new RealSubject();
Subject proxy=new StaticProxy(target);
proxy.request("magicalwolf");
proxy.request("hello");
}
}
输出:param:magicalwolf
Exception in thread "main" java.lang.IllegalArgumentException
......

静态代理的实现为每个实际对象都产生一个代理类,并将其作为内部属性。这会导致类数量的急速增加,并且代理类的代码存在大量重复。这就需要动态代理来实现。

2.2 动态代理

2.2.1 通过JDK实现

JDK提供的动态代理需要实现InvocationHandler接口的invoke方法.此方法为整个代理的入口。方法签名为

1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

第一个参数是代理对象,第二个参数是目标方法,第三个参数是目标方法参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxyHandler implements InvocationHandler{
Object target;
public ProxyHandler(Object target) {
this.target=target;
}
public void before(String param) throws Throwable{
if(!param.equals("magicalwolf"))
throw new IllegalArgumentException();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before((String)args[0]);
return method.invoke(target, args);
}
}

之后通过Proxy类的newProxyInstance方法创建代理对象

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

第一个参数是目标对象的类加载器,第二个参数是目标类实现的接口,第三个参数是处理器InvocationHandler

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
Subject target=new RealSubject();
Subject proxy=(Subject)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ProxyHandler(target));
proxy.request("magicalwolf");
proxy.request("hello");
}
}
输出:与静态代理一样

可以看到使用JDK提供的动态代理无需为每个对象都实现一个代理类,通过处理器,可以对不同的对象生成代理类。

处理器只关注处理逻辑,并且可以重用,上面的例子中处理器用于参数检查,与某种格式的请求方法耦合在一起,如果需要对不同的方法进行,需要写多个处理器,但是还是比静态代理方便很多。

对于更通用的功能,比如日志记录,调用次数统计,一个处理器就可以代理所有的对象。

注:但是JDK的动态代理是通过代理接口实现的,如果对象没有实现接口,那就无能为力了

2.2.2 通过字节码增强实现(CGLIB)

CGLIB是一个强大的高性能的代码生成包,在运行时动态生成字节码并生成新类。

CGLIB提供了MethodInterceptor接口,当调用目标方法时被回调,类似于InvocationHandler.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RequestInterceptor implements MethodInterceptor{
public void before(String param) throws Throwable{
if(!param.equals("magicalwolf"))
throw new IllegalArgumentException();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
before((String)args[0]);
return proxy.invokeSuper(obj, args);//调用父类的方法
}
}

生成代理类

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); //字节码增强器
enhancer.setSuperclass(RealSubject.class); //代理类
enhancer.setCallback(new RequestInterceptor());//回掉方法
Subject proxy=(Subject) enhancer.create();
proxy.request("magicalwolf");
proxy.request("hello");
}
}

可以看到,CGLIB是通过动态生成目标类的子类,在子类中设置拦截逻辑,来进行动态代理。因此目标类的方法不能被final修饰。

2.3 代理模式与装饰者模式

从UML上看装饰者模式和代理模式很像,装饰者和代理者都对目标方法进行修改,但是二者还是有很大的区别。

  • 装饰者模式关注于对目标方法进行增强。装饰者只能在外加一层装饰。
  • 代理模式关注于对目标方法的控制。代理者拥有控制权限,可以决定目标方法是否调用。