本文主要介绍Java
中两种常见的动态代理方式:JDK原生动态代理
和CGLIB动态代理
。java
就是为其余对象提供一种代理以控制对这个对象的访问。代理能够在不改动目标对象的基础上,增长其余额外的功能(扩展功能)。编程
代理模式角色分为 3 种:dom
Subject
(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;RealSubject
(真实主题角色):真正实现业务逻辑的类;Proxy
(代理主题角色):用来代理和封装真实主题;若是根据字节码的建立时机来分类,能够分为静态代理和动态代理:ide
学习动态代理前,有必要来学习一下静态代理。函数
静态代理在使用时,须要定义接口或者父类,被代理对象(目标对象)与代理对象(Proxy)一块儿实现相同的接口或者是继承相同父类。性能
来看一个例子,模拟小猫走路的时间。学习
// 接口 public interface Walkable { void walk(); } // 实现类 public class Cat implements Walkable { @Override public void walk() { System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
若是我想知道走路的时间怎么办?能够将实现类Cat
修改成:测试
public class Cat implements Walkable { @Override public void walk() { long start = System.currentTimeMillis(); System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("walk time = " + (end - start)); } }
这里已经侵入了源代码,若是源代码是不能改动的,这样写显然是不行的,这里能够引入时间代理类CatTimeProxy
。ui
public class CatTimeProxy implements Walkable { private Walkable walkable; public CatTimeProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { long start = System.currentTimeMillis(); walkable.walk(); long end = System.currentTimeMillis(); System.out.println("Walk time = " + (end - start)); } }
若是这时候还要加上常见的日志功能,咱们还须要建立一个日志代理类CatLogProxy
。this
public class CatLogProxy implements Walkable { private Walkable walkable; public CatLogProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { System.out.println("Cat walk start..."); walkable.walk(); System.out.println("Cat walk end..."); } }
若是咱们须要先记录日志,再获取行走时间,能够在调用的地方这么作:
public static void main(String[] args) { Cat cat = new Cat(); CatLogProxy p1 = new CatLogProxy(cat); CatTimeProxy p2 = new CatTimeProxy(p1); p2.walk(); }
这样的话,计时是包括打日志的时间的。
若是咱们须要计算SDK
中100个方法的运行时间,一样的代码至少须要重复100次,而且建立至少100个代理类。往小了说,若是Cat
类有多个方法,咱们须要知道其余方法的运行时间,一样的代码也至少须要重复屡次。所以,静态代理至少有如下两个局限性问题:
因此,咱们须要一个通用的代理类来代理全部的类的全部方法,这就须要用到动态代理技术。
学习任何一门技术,必定要问一问本身,这到底有什么用。其实,在这篇文章的讲解过程当中,咱们已经说出了它的主要用途。你发现没,使用动态代理咱们竟然能够在不改变源码的状况下,直接在方法中插入自定义逻辑。这有点不太符合咱们的一条线走到底的编程逻辑,这种编程模型有一个专业名称叫AOP
。所谓的AOP
,就像刀同样,抓住时机,趁机插入。
JDK实现代理只须要使用newProxyInstance方法,可是该方法须要接收三个参数:
@CallerSensitive 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); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ 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<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } 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); } }
方法是在Proxy
类中是静态方法,且接收的三个参数依次为:
ClassLoader loader
//指定当前目标对象使用类加载器Class<?>[] interfaces
//目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler h
//事件处理器主要是完成InvocationHandler h
的编写工做。
接口类UserService
:
public interface UserService { public void select(); public void update(); }
接口实现类,即要代理的类UserServiceImpl
:
public class UserServiceImpl implements UserService { @Override public void select() { System.out.println("查询 selectById"); } @Override public void update() { System.out.println("更新 update"); } }
代理类UserServiceProxy
:
public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target){ this.target = target; } @Override public void select() { before(); target.select(); after(); } @Override public void update() { before(); target.update(); after(); } private void before() { // 在执行方法以前执行 System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { // 在执行方法以后执行 System.out.println(String.format("log end time [%s] ", new Date())); } }
主程序类:
public class UserServiceProxyJDKMain { public static void main(String[] args) { // 1. 建立被代理的对象,即UserService的实现类 UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2. 获取对应的classLoader ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); // 3. 获取全部接口的Class, 这里的userServiceImpl只实现了一个接口UserService, Class[] interfaces = userServiceImpl.getClass().getInterfaces(); // 4. 建立一个将传给代理类的调用请求处理器,处理全部的代理对象上的方法调用 // 这里建立的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl InvocationHandler logHandler = new LogHandler(userServiceImpl); /* 5.根据上面提供的信息,建立代理对象 在这个过程当中, a.JDK会经过根据传入的参数信息动态地在内存中建立和.class 文件等同的字节码 b.而后根据相应的字节码转换成对应的class, c.而后调用newInstance()建立代理实例 */ // 会动态生成UserServiceProxy代理类,而且用代理对象实例化LogHandler,调用代理对象的.invoke()方法便可 UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); // 调用代理的方法 proxy.select(); proxy.update(); // 生成class文件的名称 ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceJDKProxy"); } }
这里能够保存下来代理生成的实现了接口的代理对象:
public class ProxyUtils { /* * 将根据类信息 动态生成的二进制字节码保存到硬盘中, * 默认的是clazz目录下 * params :clazz 须要生成动态代理类的类 * proxyName : 为动态生成的代理类的名称 */ public static void generateClassFile(Class clazz, String proxyName) { //根据类信息和提供的代理类名称,生成字节码 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); String paths = clazz.getResource(".").getPath(); System.out.println(paths); FileOutputStream out = null; try { //保留到硬盘中 out = new FileOutputStream(paths + proxyName + ".class"); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
getProxyClass0()
生成代理类。JDK
生成的最终真正的代理类,它继承自Proxy
并实现了咱们定义的接口.Proxy.newProxyInstance()
生成代理类的实例对象,建立对象时传入InvocationHandler
类型的实例。InvocationHandler
类中的invoke()
方法。代理对象不须要实现接口,可是目标对象必定要实现接口,不然不能用动态代理
JDK
的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK
的动态代理,cglib
是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现加强,但由于采用的是继承,因此不能对final
修饰的类进行代理。
Cglib
代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
Cglib
子类代理实现方法:
cglib
的jar
文件,可是Spring
的核心包中已经包括了Cglib
功能,因此直接引入Spring-core.jar
便可.final
,不然报错final/static
,那么就不会被拦截,即不会执行目标对象额外的业务方法.基本使用
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency>
方法拦截器
public class LogInterceptor implements MethodInterceptor{ /* * @param o 要进行加强的对象 * @param method 要拦截的方法 * @param objects 参数列表,基本数据类型须要传入其包装类 * @param methodProxy 对方法的代理, * @return 执行结果 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(o, objects); after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } }
测试用例
这里保存了代理类的.class
文件
public class CglibMain { public static void main(String[] args) { // 建立Enhancer对象,相似于JDK动态代理的Proxy类 Enhancer enhancer = new Enhancer(); // 设置目标类的字节码文件 enhancer.setSuperclass(UserDao.class); // 设置回调函数 enhancer.setCallback(new LogInterceptor()); // create会建立代理类 UserDao userDao = (UserDao)enhancer.create(); userDao.update(); userDao.select(); } }
结果
log start time [Mon Nov 30 17:26:39 CST 2020] UserDao 更新 update log end time [Mon Nov 30 17:26:39 CST 2020] log start time [Mon Nov 30 17:26:39 CST 2020] UserDao 查询 selectById log end time [Mon Nov 30 17:26:39 CST 2020]
JDK
动态代理只须要实现 InvocationHandler
接口,重写 invoke
方法即可以完成代理的实现,Proxyxx.class
代理类字节码,并生成对象extends
了Proxy
,而java是不容许多重继承的,可是容许实现多个接口优势:解决了静态代理中冗余的代理实现类问题。
缺点:JDK
动态代理是基于接口设计实现的,若是没有接口,会抛异常。
JDK
动态代理限制了只能基于接口设计,而对于没有接口的状况,JDK
方式解决不了;CGLib
采用了很是底层的字节码技术,其原理是经过字节码技术为一个类建立子类,并在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。MethodInterceptor
接口,重写 intercept
方法,经过 Enhancer
类的回调方法来实现。CGLib
在建立代理对象时所花费的时间却比JDK多得多,因此对于单例的对象,由于无需频繁建立对象,用CGLib
合适,反之,使用JDK
方式要更为合适一些。CGLib
因为是采用动态建立子类的方法,对于final
方法,没法进行代理。优势:没有接口也能实现动态代理,并且采用字节码加强技术,性能也不错。
缺点:技术实现相对难理解些。