别告诉我,作为Java开发工程师你没用过Spring;别告诉我,不知道Spring充分运用了AOP思想;别告诉我,不知道AOP思想的实现没有用到动态代理。如果这些问题,全都不知道的话,emmm… 没事,大兄弟,我们一起学习吧,我们还年轻不是嘛!
动态代理是什么?
在这之前不得不提下Java的设计模式-代理模式。代理模式主要是代理类为委托类负责于预消息处理、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
实现代理的方式不止反射机制,还有这里的ASM啊。
JDK代理和CGLib代理的简单使用
拿大家都很熟悉的日志打印举例子。
对象的话,我们采取车和人。车和人都能跑 ,如果路程定位1公里的话,按照正常来说,两对象的速度决然不同。假设有一个人买了一辆可以无人驾驶的车,拿到车的第一天,心情激动的很,就想看看车正常跑个1公里是咋样的,但毕竟新车不太放心,但坐在车内怕怕的。于是选择了和车一起跑个1公里。
现在的需求就是打印出两对象的1公里的实际耗时。
maven
CGLIB 是需要额外引入jar包的。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
Runner.java
public interface Runner {
void run();
}
Car.java
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Car implements Runner {
@Override
public void run() {
//模拟时长:
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("===> Car 到达终点 ...");
}
}
Person.java
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Person implements Runner {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5) + 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("===> Person 到达终点 ...");
}
}
JdkProxy.java
代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxy implements InvocationHandler {
private Object object;
public Object getProxy(Object object) {
this.object = object;
return Proxy.newProxyInstance(this.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("=====> JdkProxy 记录开始时间:" + start);
Object result = method.invoke(object, args);
long end = System.currentTimeMillis();
System.out.println("=====> JdkProxy 记录结束时间:" + end);
System.out.println("=====> JdkProxy 计算出" + method.getName() + "耗时:" + (end - start) + "毫秒");
return result;
}
}
CglibProxy.java
代理类
package com.yuki.springsource.proxy;
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 CglibProxy implements MethodInterceptor {
Object object;
//生成代理对象
public Object getProxy(Object object) {
this.object = object;
Enhancer enhancer = new Enhancer();
//设置父类为构造时传的类型
enhancer.setSuperclass(object.getClass());
//MethodInterceptor 是Callback的父接口
enhancer.setCallback(this);
return enhancer.create();
}
//方法拦截
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("=====> CglibProxy 记录开始时间:" + start);
//调用父类的方法
Object result = methodProxy.invokeSuper(o, objects);
long end = System.currentTimeMillis();
System.out.println("=====> CglibProxy 记录结束时间:" + end);
System.out.println("=====> CglibProxy 计算出" + method.getName() + "耗时:" + (end - start) + "毫秒");
return result;
}
}
ProxtTest.java
测试类
public class ProxyTest {
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
Runner runner = (Runner) jdkProxy.getProxy(new Car());
runner.run();
CglibProxy cglibProxy = new CglibProxy();
Runner runner1 = (Runner) cglibProxy.getProxy(new Person());
runner1.run();
}
}
运行结果:
神奇的嘞,这日志打印的妙啊。Car 和Person 完全不需要关心如何打印日志的逻辑,一个劲地run就完事了,剩下的交给代理就行啦。
Car.java
,Person.java
,Runner.java
充当着委托类 ,CglibProxy.java
,JdkProxy.java
充当着代理类的角色。
区别分析
- JDK代理底层原理是反射机制 重新生成一个代理类 ,在程序运行期间,可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。
可以通过看下源码里有类似 xxx.newInstance() 。和Spring IOC 实例对象的方法很像啦。
而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的,它是通过实现子类 super 父类的的方式来完成调用的。在编写代码的过程种也发现 setSupper
- JDK 是Java自带的,而CGLib 是需要加载第三方类的;
- JDK Proxy 只能代理继承接口的类,而CGLib 还可以代理普通的class ,除开被关键字 final 修饰类。