JDK动态代理和CGlib动态代理
JDK动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理: 利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别: JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
一、JDK动态代理:
我们举个简单的例子:
有一个接口,定义了一个sum()方法:
/**
* 目标接口类
*/
public interface Computer {
int sum(int a,int b);
}
当然还有它的实现类:
/**
* 接口实现类
*/
public class ComputerImpl implements Computer {
@Override
public int sum(int a, int b) {
return a+b;
}
}
接着我们定义一个代理类,用来在执行的时候加入一些操作(逻辑验证或者业务需求相关的操作等)
public class ComputerProxy implements InvocationHandler {
Object target;//目标
public ComputerProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
check();
Object result = method.invoke(target, args);
return result;
}
private void check(Object... args) {
// 模拟其他操作
System.out.println("我在检查");
}
}
那么代理有了,我们要怎么使用呢?
下面我们在测试类中去执行:
public class Test {
public static void main(String[] args) {
//正常调用
Computer computer=new ComputerImpl();
int sum = computer.sum(1, 2);
System.out.println(sum);
//利用JDK动态代理调用
//目标对象
Computer target = new ComputerImpl();
//代理对象
ComputerProxy proxy=new ComputerProxy(target);
//生成动态代理
Computer jdkProxy = (Computer) Proxy.newProxyInstance(Computer.class.getClassLoader(),
new Class[]{Computer.class}, proxy);
int result = jdkProxy.sum(1, 2);
System.out.println(result);
}
}
二、CGlib动态代理:
CGlib方式生成动态代理的步骤与JDK生成大致相同,只是在配置动态代理的时候需要调用其他的接口,而JDK是原生的不需要导入Jar包
所以如果要使用Spring动态代理,必须导入spring-aop 这个jar包
下载地址: https://repo.spring.io/release/org/springframework/spring/
具体配置如下:
Spring代理实例
public class SpringProxy implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//获取目标方法参数
Object[] arguments = methodInvocation.getArguments();
//模拟其他操作
check(arguments);
//调用目标方法
Object result = methodInvocation.proceed();
return result;
}
private void check(Object... args) {
// 模拟其他操作
System.out.println("我在检查!");
}
}
xml文件配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1、目标对象 -->
<bean id="target" class="com.cc.dao.impl.ComputerImpl"/>
<!-- 2、Spring代理实例 -->
<bean id="springProxy" class="com.cc.proxy.SpringProxy"/>
<!--3、代理对象:是目标对象与Spring代理融合体(cglib) -->
<bean id="ComputerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 1)注入目标对象 -->
<property name="targetName" value="target"/>
<!-- 2)Spring代理实例 -->
<property name="interceptorNames">
<array>
<value>springProxy</value>
</array>
</property>
</bean>
</beans>
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans2.xml")
public class SpringHkTest1 {
@Autowired
@Qualifier("ComputerProxy")
private Computer computer;
@Test
public void testSpringHk() {
System.out.println( computer.sum(30, 5));;
}
}
注意:在编写测试类时不能使用main方法运行,因为main方法不会开启注解扫描,会报错,需要在Test单元测试中开启(另外junit4.10以上的版本是没有集成hamcrest-core-1.3.jar包的,需要额外下载这个包,或者将junit版本降级,否则也会报错)
总结:
-
JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
-
JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。