简单例子理解jdk和cglib动态代理
前言
动态代理:是java在运行时,动态生成代理对象,实现对目标对象的方法进行扩展(增强)。
java有2种动态代理实现方法:
jdk动态代理:要被代理的目标对象必须实现某个接口。动态代理的原理是动态生成一个和目标对象实现了相同接口的 代理对象。这个代理对象和被代理对象是兄弟关系。增强后的方法,通过反射调用实现。
cglib动态代理:要被代理的目标对象是任意的非final对象。动态代理的原理是动态生成一个继承自目标对象的子对象,这个代理对象和被代理对象是父子关系。增强后的方法,通过回调对象调用实现。
一、工程例子
1、代码结构
pom.xml需要引入cglib包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>zhc.xxx</groupId>
<artifactId>spring-4</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
</project>
2、jdk的动态代理
依次新建4个文件:
UserService.java 定义一个接口。
package jdkProxy;
public interface UserService {
public String save(String name);
}
UserServiceImpl.java 接口实现类,也是要被代理的对象
package jdkProxy;
public class UserServiceImpl implements UserService{
@Override
public String save(String name) {
System.out.println(name + " 已保存。");
return "ok";
}
}
LogPrint.java 希望在目标对象的save方法执行前,额外执行打印日志的方法又不入侵目标对象的代码。
package jdkProxy;
public class LogPrint {
public void info(Object[] args){
System.out.println("日志:接受保存用户请求,参数:"+args[0]);
}
}
JdkProxyTest.java 测试对象(解释说明见注释)
package jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();//要被代理的对象
LogPrint log = new LogPrint();//被代理后额外要操作的对象,这里模拟打印日志。
// newProxyInstance() 方法,jdk在运行时,动态创建了一个对象Object,这个对象继承自 Proxy 对象;
// 且实现了 我们定义的 UserService 接口。因此这个对象可以被强转为 UserService;
// jdk动态创建的这个对象,也实现了 UserService 中定义的 save() 方法,但这个方法实现内容是:
// 通过反射,调用了new InvocationHandler()所产生对象的 invoke方法。也就是第22-25行代码,从而是实现了动态代理。
UserService userProxyService = (UserService) Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info(args);
Object object = method.invoke(userService,args);
return object;
}
});
userProxyService.save("Tom");//被动态代理了,结果是:先执行了 log.info();在执行save方法。
userService.save("Tom");//没有被代理,结果是:只会执行save方法。
}
}
3、cglib动态代理
依次新建3个文件:
TargetService.java 被代理的目标对象
package cglibProxy;
public class TargetService {
public String save(String name) {
System.out.println(name + " 已保存。");
return "ok";
}
}
LogPrinter.java 希望在目标对象的save方法执行前,额外执行打印日志的方法又不入侵目标对象的代码。
package cglibProxy;
public class LogPrinter {
public void info(Object[] args){
System.out.println("日志:接受保存用户请求,参数:"+args[0]);
}
}
CglibProxyTest.java 测试cglib动态代理(详情说明见注释)
package cglibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyTest {
public static void main(String[] args) {
TargetService targetService = new TargetService();//要被代理的对象
LogPrinter log = new LogPrinter();//被代理后额外要操作的对象,这里模拟打印日志。
Enhancer enhancer = new Enhancer();//创建增强器
enhancer.setSuperclass(TargetService.class);//设置增强器的父类class,这里指要代理的对象:TargetService
// 设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.info(objects);
// Object object = method.invoke(targetService,objects);// 2种方法都可以
Object object = methodProxy.invoke(targetService,objects);
return object;
}
});
// 创建代理对象,cglib动态创建的代理对象是继承自 TargetService ,因此可以强转
// 代理对象调用 save(),其内部实现实际上是调用了 intercept方法
// 代理对象具有调用 intercept方法的能力,是因为setCallback设置了回调对象。
//(和jdk的动态代理是通过实现接口的方式获得invoke方法的方式不同)
TargetService targetProxyService = (TargetService) enhancer.create();
// 使用代理对象
targetProxyService.save("Tom");//结果是:先执行了 log.info();在执行save方法。
// 没有使用代理对象
targetService.save("Tom");//结果是:只会执行save方法。
}
}
4、小结
动态代理技术的目的是在不入侵代码的情况下,实现对方法的增强,常用于面向切面编程,精准测试覆盖率统计等各种jvm插桩技术。
一般有接口的情况下,使用jdk的动态代理;没有接口的情况下,使用cglib的动态代理,cglib需要引入第三方包,底层是基于ASM字节码生成框架。
spring框架的面向切面编程实现,就是采用了这2种动态代理技术。