Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。
JDK 动态接口代理
- JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类: Proxy 和 InvocationHandler 。
InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动 态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实 例,生成目标类的代理对象。
CGLib 动态代理 - : CGLib 全称为 Code Generation Library ,是一个强大的高性能,高质量的代码生成类库,可以 在运行期扩展 Java 类与实现 Java 接口, CGLib 封装了 asm ,可以再运行期动态生成新的 class 。 和 JDK 动态代理相比较: JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通 过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。
静态代理
前提声明一个接口TargetInterface,Target类实现这个接口,并实现接口方法,而后TargetProxy也实现这个接口,并实例化一个Target类—target,然后在实现的接口方法中调用target的方法,并在调用其书写执行前后的语句。
具体代码
接口类
public interface TargetInteface {
void method1();
void method2();
int method3(Integer i);
}
实现类
public class Target implements TargetInteface {
@Override
public void method1() {
System.out.println(" Target method1 running ...");
}
@Override
public void method2() {
System.out.println("Target method2 running ...");
}
@Override
public int method3(Integer i) {
System.out.println("Target method3 running ...");
return i;
}
}
代理类
public class TargetProxy implements TargetInteface {
private Target target =new Target();
@Override
public void method1() {
System.out.println("执行方法前...");
target.method1();
System.out.println("执行方法后...");
}
@Override
public void method2() {
System.out.println("执行方法前...");
target.method2();
System.out.println("执行方法后...");
}
@Override
public int method3(Integer i) {
System.out.println("执行方法前...");
int method3 = target.method3(i);
System.out.println("执行方法后...");
return method3;
}
}
测试类
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = new TargetProxy();
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
结果输出
总结
静态代理非常简单,但是它的缺陷也是显然的,因为静态代理的代理关系在 IDE 编译时就确定了,如果接口改变了,不仅实现类要改变,代理类也要改变,代理类和接口之间的耦合非常严重。所以在现实实践中很少用到
JDK动态代理
JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类: Proxy 和 InvocationHandler 。
InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动 态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实 例,生成目标类的代理对象。
实例代码
主要修改的代理类和测试类
代理类
public class TargetProxy {
public static <T> Object getTarget(T t) {
return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
System.out.println("执行方法前...");
Object invoke = method.invoke(t, args);
System.out.println("执行方法后...");
return invoke;
}
});
}
}
测试类
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
测试结果
总结
利用JDK实现动态代理,本质就是利用 Java 的反射机制在程序运行期动态的创建接口的实现类,并生产代理对象而已
涉及两个类InvocationHandler 和 Proxy
- Proxy 类的作用是动态创建一个代理对象,也就是代理对象不需要我们自己写。用的最多的是 newProxyInstance
- 其中第一个参数是被代理的类的加载器,传入的目的是告诉 JDK 由哪个类加载器对生成的代理进行加载。其实就是真实的类(实现类)的对象的加载器。
- 第二个参数是代理类需要实现的接口,可以多个。其实就是接口 TargetInterface,很好理解,在静态代理模式中,我们就需要手动实现这个接口,来实现代理类。
- 第三个参数就是实现了InvocationHandler接口的类即可,原因是此类里有 invoke 方法,而通过 Proxy 的 newProxyInstance 方法生成的代理类去调用接口方法(dosth)时,对方法(dosth)的调用会自动委托给 InvocationHandler 接口的 invoke 方法,这样也就实现了代理模式。
- InvocationHandler 接口只有一个方法 —— invoke,参数分别是:
proxy:代理的真实对象,也就是实现类的对象
method:要调用真实对象的某个方法的Method对象,也就是会调用 dosth 的方法的对象
args:调用真实对象某个方法时接受的参数,没有就是空数组
缺点:JDK 动态代理这种方式只能代理通过接口定义业务方法的类,这是其缺陷
CGLib
CGLib 全称为 Code Generation Library ,是一个强大的高性能,高质量的代码生成类库,可以 在运行期扩展 Java 类与实现 Java 接口, CGLib 封装了 asm ,可以在运行期动态生成新的 class 。 和 JDK 动态代理相比较: JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通 过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。
实例代码
首先,CGLib 代理的类不需要实现接口,代理类使用Enhancer生成代理对象,并指定目标类以及代理的行为
public class TargetProxy {
public static <T> Object getProxy(T t) {
Enhancer en = new Enhancer(); //帮我们生成代理对象
en.setSuperclass(t.getClass());//设置要代理的目标类
en.setCallback(new MethodInterceptor() {//代理要做什么
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("执行方法前。。。");
//调用原有方法
Object invoke = methodProxy.invokeSuper(object, args);
// Object invoke = method.invoke(t, args);// 作用等同与上面。
System.out.println("执行方法后。。。");
return invoke;
}
});
return en.create();
}
}
测试
public class TargetUser {
public static void main(String[] args) {
Target target = (Target) TargetProxy.getProxy(new Target());
System.out.println(target.getClass().getName());
target.method1();
}
}
结果输出
总结
- CGLib 底层是利用 asm 字节码框架实现的,该框架可以在 Java 程序运行时对字节码进行修改和动态生成,故它可以代理普通类,具体细节是通过继承和重写需要被代理的类(NoInterfaceReal)来实现。
- CGLib 可以实现对方法的代理,即可以实现拦截(只代理)某个方法。
- 通过CGLib 的 Enhancer 类来create 代理对象。而对这个对象所有非final方法的调用都会委托给 MethodInterceptor 接口的 intercept,我们可以在该方法内部写拦截代码,最后在通过调用MethodProxy 对象的 invokeSuper() 方法,把调用转发给真实对象
不足:无法对 final 类、或者 final 方法进行代理