在要搞明白spirngAOP之前,需要先了解代理模式,以及动态代理的两种实现方式。
代理模式
静态代理
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
其主要缺点是:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
结构图:
代理类持有被代理类的实例,并实现接口方法,方法实现中调用被代理对象的方法实现。
示例代码
package proxy;
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
运行结果:
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
jdk动态代理
实现思路:和上面的静态代理类图一样,只是代理类由jdk动态生成并加载。
由于依赖于抽象接口,因此被代理类必须是实现某个接口的。(或者干脆就没有被代理类,只有接口定义)
典型应用:
- mybatis的mapper实现
- springAOP实现中的一种方式
上代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyTest {
public static void main(String[] args) {
Subject proxy = ((Subject) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader()
, RealSubject.class.getInterfaces(), new MyHandler(new RealSubject())));
proxy.request();
}
}
//抽象主题
interface Subject {
void request();
}
//真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("访问真实主题方法...");
}
}
class MyHandler implements InvocationHandler{
private Subject subject;
public MyHandler(Subject subject){
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("访问真实主题之前的预处理。");
method.invoke(subject);
System.out.println("访问真实主题之后的后续处理。");
return null;
}
}
运行结果同上
Proxy.newProxyInstance做了什么? 看一下jdk的源码,跟到java.lang.reflect.Proxy.ProxyClassFactory#apply这个方法,里面就是根据一定规则生成代码字节流,然后ClassLoader加载。拿到class对象后外面通过构造方法反射newInstance实例化。
cglib动态代理
上面讲到jdk动态代理时有一个约束点,被代理类必须实现某个接口,而实际开发中并不是所有类都有接口实现,这时候需要使用cglib动态代理方式。
典型应用:
- SpringAOP应用
原理:cglib使用ASM直接对Class字节码操作,生成代理类并加载。 代理类继承自被代理类,重写被代理类方法,注入定义的方法拦截器,同时通过调用被代理类的方法实现代理。
cglib和jdk动态代理的区别:jdk动态代理是基于接口,cglib是基于继承关系。
上代码:
import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
//把CGLIB生成的字节码文件保存到本地,到时候可以拖进idea里看
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\cglib");
MethodInterceptor m1 = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("拦截器1 before");
methodProxy.invokeSuper(o, objects);
System.out.println("拦截器1 after");
return null;
}
};
MethodInterceptor m2 = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("拦截器2 before");
methodProxy.invokeSuper(o, objects);
System.out.println("拦截器2 after");
return null;
}
};
//返回索引对应Callback数组里的拦截器索引
CallbackFilter callbackFilter = method -> {
if (method.getName().equals("f1")) {
return 0;
} else if (method.getName().equals("f2")) {
return 1;
}
return 2;
};
CglibObj cglibObj1 = (CglibObj) Enhancer.create(CglibObj.class,
null, callbackFilter, new Callback[]{m1, m2,NoOp.INSTANCE});
cglibObj1.f1();
System.out.println("******************");
cglibObj1.f2();
System.out.println("******************");
}
}
class CglibObj {
public void f1() {
System.out.println("f1");
}
public void f2() {
System.out.println("f2");
}
}
运行结果:
拦截器1 before
f1
拦截器1 after
******************
拦截器2 before
f2
拦截器2 after
******************
说明:
- spring的cglib和net.sf.cglib, spring实际上是重新打包,就当是重写了net.sf.cglib的。这样就不再有外部依赖。
- 上述代码中第一行将生成的class写到了指定目录,可以去反编译看一下生成的内容。 蛮复杂的,不讲了,我们只关注应用。
- 应用中主要是定义MethodInterceptor和CallbackFilter,主要是拦截器。 看AOP源码时主要关注这个。
至此了解了动态代理相关知识,这是学习AOP的基础知识。