在开发中,经常遇到代理问题,尤其是动态代理,在这里,本人对Java中的动态代理做一个小结。
在工作中,我们发现,当对所有业务类都需要打日志时,我们有两种方案:
1. 在每个类中加入日志代码(每个类都写一次,累不累?!);
2. 实现动态代理,只需要写一次日志代码就搞定了(对于我这种懒人来说,当然是这种了!);
有的人会说,那直接使用Spring的AOP不就行了么?
答案当时是:可以的!但是,你知道AOP是怎么实现的么?
AOP实际上就是动态代理,而且可选的动态代理还有两种,一种是JDK自带的代理,另一种是Cglib(多以Cglib为主)。
那么在将动态代理之前,先来一个小例子帮助大家理解,不多说,直接上代码:
业务接口类:
public interface OrderService {
String getOrder();
long getOrderId();
}
业务实现类:
public class OrderServiceImpl implements OrderService {
@Override
public String getOrder() {
System.out.println("------getOrder-----");
return "order_123456";
}
@Override
public long getOrderId() {
System.out.println("------getOrderId-----");
return 123456;
}
}
对于这个业务,我们需要进行代理,在每次执行getOrder方法的时候,我们需要进行其他一些操作,那么我试着去代理一下:
public class OrderServiceStaticProxy {
private OrderService orderService;
OrderServiceStaticProxy(OrderService orderService) {
this.orderService = orderService;
}
public String getOrder() {
System.out.println("------before getOrder-----");
String result = orderService.getOrder();
System.out.println("------after getOrder-----");
return result;
}
public long getOrderId() {
return orderService.getOrderId();
}
}
测试类:
public class StaticProxyTest {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderServiceStaticProxy orderServiceProxy
= new OrderServiceStaticProxy(orderService);
System.out.println(orderServiceProxy.getOrder());
System.out.println(orderServiceProxy.getOrderId());
}
}
输出:
------before getOrder-----
------getOrder-----
------after getOrder-----
order_123456
------getOrderId-----
123456
好了,我们已经成功为业务实现了一个代理了!
这是一个静态代理,OrderServiceStaticProxy类通过持有OrderService对象,来实现对OrderService所有方法的代理,比如控制在执行方法前后打印日志等。但是这种方法的缺点也很明显,就是我每写一个类,再增加一个代理类,有100个,我就写100次,有1000个,我就写1000次!!!程序员当然是要以最小的代价来完成工作!所以,我如何能只写一遍代理,然后就直接使用就可以了呢?
好了,正片开始了
废话不多说,直接上代码,代码之下,无所遁形:
public class OrderServiceProxy implements InvocationHandler {
private Object target;
OrderServiceProxy() {
super();
}
OrderServiceProxy(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
if (Objects.equals("getOrder", method.getName())) {
System.out.println("-------before-------");
result = method.invoke(target, args);
System.out.println("-------after-------");
} else {
result = method.invoke(target, args);
}
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
OrderServiceImpl orderService = new OrderServiceImpl();
InvocationHandler invocationHandler = new OrderServiceProxy(orderService);
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(
orderService.getClass().getClassLoader(),
orderService.getClass().getInterfaces(),
invocationHandler);
System.out.println(orderServiceProxy.getOrder());
System.out.println(orderServiceProxy.getOrderId());
}
}
输出:
-------before-------
------getOrder-----
-------after-------
order_123456
------getOrderId-----
123456
在讲解这段代码之前,我先简单讲一个JVM中的加载机制。
总得来说,就是我们写的java代码,机器是不认识的,所有需要编译,转成二进制文件,就是编译完后的.class文件。那么,这些二进制文件是我们一启动JVM就加载进去了么?答案是:否。这些二进制文件只有在被调用的时候才会由相应的ClassLoader加载进JVM中,这时候,才能真正有了对象供程序调用。好,加载机制先了解到这里(详细的过程,可查阅JVM相关的资料,这里不细说)
先说OrderServiceProxy,它实现了InvocationHandler,InvocationHandler中有一个方法invoke(),invoke()中三个参数,分别是:需要代理的对象proxy,代理对象的方法method,以及需要的参数args。什么意思呢?就是说,假如我现在知道了我需要代理对象、方法、参数了,我不就可以根据判断来就执行我需要额外执行业务了吗?然后通过method.invoke(target, args)来调用代理对象的方法了,至于method.invoke(target, args)这里面的实现逻辑,我们是不用关心的,因为里头其实就是写如何找到相关的类,执行相关方法的流程,JDK都已经封装好了。
然后,再通过Proxy.newProxyInstance()来生成一个代理类,这个代理类,实际上是调用invoke()方法来执行target(即代理对象)的方法。
如果想了解Proxy具体原理的话,往下看:
public class Proxy implements java.io.Serializable {
...
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 1.通过ClassLoader和接口来查找接口类
*/
Class> cl = getProxyClass0(loader, intfs);
/*
* 2.通过接口来获取构造对象
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
/*
* 3.通过构造对象和InvocationHandler来构造实例,并返回
*/
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
}
具体来讲就三个步骤:
1. 根据ClassLoader和Interface来获取接口类(前面已经讲了,类是由ClassLoader加载到JVM的,所以通过ClassLoader和Interface可以找到接口类)
2.获取构造对象;
3.通过构造对象和InvocationHandler生成实例,并返回,就是我们要的代理类。
Java动态代理优缺点:
优点:
1.Java本身支持,不用担心依赖问题,随着版本稳定升级;
2.代码实现简单;
缺点:
1.目标类必须实现某个接口,换言之,没有实现接口的类是不能生成代理对象的;
2.代理的方法必须都声明在接口中,否则,无法代理;
3.执行速度性能相对cglib较低;
使用cglib代理需要添加jar依赖:
cglib
cglib
2.2.2
同样,也是直接上代码:
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy)
throws Throwable {
Object result;
if (Objects.equals("getOrder", method.getName())) {
System.out.println("----before-----");
result = methodProxy.invokeSuper(proxy, params);
System.out.println("-----after-----");
} else {
result = methodProxy.invokeSuper(proxy, params);
}
return result;
}
public Object getProxy(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
enhancer.setClassLoader(target.getClass().getClassLoader());
return enhancer.create();
}
}
public class CglibTest {
public static void main(String[] args) {
OrderServiceImpl orderService = new OrderServiceImpl();
CglibProxy proxy = new CglibProxy();
OrderServiceImpl orderServiceProxy
= (OrderServiceImpl) proxy.getProxy(orderService);
System.out.println(orderServiceProxy.getOrder());
System.out.println(orderServiceProxy.getOrderId());
}
}
输出:
------getOrder-----
-----after-----
order_123456
------getOrderId-----
123456
Cglib原理:
1.通过字节码增强技术动态的创建代理对象;
2.代理的是代理对象的引用;
Cglib优缺点:
优点:
1.代理的类无需实现接口;
2.执行速度相对JDK动态代理较高;
缺点:
1.字节码库需要进行更新以保证在新版java上能运行;
2.动态创建代理对象的代价相对JDK动态代理较高;
Tips:
1.代理的对象不能是final关键字修饰的