Spring中的JDK黑客,Spring黑客
JDK动态代理和CGlib动态代理
JDK动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理: 利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别: JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
一、JDK动态代理:
我们举个简单的例子:
有一个接口,定义了一个sum()方法:
package change; public interface Computer { int sum(int a,int b); }
当然还有它的实现类:
package change.Impls; import change.Computer; public class ComputerImpl implements Computer { @Override public int sum(int a, int b) { return a+b; } }
接着我们定义一个代理类,用来在执行的时候加入一些操作(逻辑验证或者业务需求相关的操作等)
package aspect; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; 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("我在检查"); } }
那么代理有了,我们要怎么使用呢?
下面我们在测试类中去执行:
import aspect.ComputerProxy; import change.Computer; import change.Impls.ComputerImpl; import java.lang.reflect.Proxy; 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); } }
执行结果
3 我在检查 3
二、CGlib动态代理:
CGlib方式生成动态代理的步骤与JDK生成大致相同,只是在配置动态代理的时候需要调用其他的接口,而JDK是原生的不需要导入Jar包
所以如果要使用Spring动态代理,必须导入spring-aop 这个jar包
下载地址: https://repo.spring.io/release/org/springframework/spring/
具体配置如下:
Spring代理实例
package aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; 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("检查权限:checkPopedom()!"); } }
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则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。