• 静态代理
  • 动态代理
  • cglib代理

代理模式的用途主要是对目标对象的方法功能进行扩展,不是用来new对象的设计模式

比如你希望该类所有方法都新增一个功能,但是不需要对该类修改代码,只需要写一个代理类

可以用于输出日志信息,统一进行事务管理

Spring 框架的AOP就用到了代理模式中的动态代理


静态代理

对目标对象的方法进行增强

使用租房收房举例

定义一个出租的接口,指定出租和收回两个方法

  1. public interface Rent {
  2. /**
  3. * 出租
  4. */
  5. void rent();
  6. /**
  7. * 回收房
  8. */
  9. void recycle();
  10. }

定义房东类,实现出租接口

  1. public class Host implements Rent {
  2. @Override
  3. public void rent() {
  4. System.out.println("房东要出租房子!!!");
  5. }
  6. @Override
  7. public void recycle() {
  8. System.out.println("房东回收房子");
  9. }
  10. }

然后写一个代理类,用于增强目标对象的方法

将房东类的所有方法进行增强,类似于运行日志

  1. public class HostProxy implements Rent {
  2. private Host target;
  3. public HostProxy(Host target) {
  4. this.target = target;
  5. }
  6. @Override
  7. public void rent() {
  8. System.out.println("开始代理。。。。。。。。。");
  9. Long start = System.currentTimeMillis();
  10. target.rent();
  11. Long end = System.currentTimeMillis();
  12. System.out.println("提交结束。。。。。。。。。共耗时:" + (end - start) + " ms");
  13. }
  14. @Override
  15. public void recycle() {
  16. System.out.println("开始代理。。。。。。。。。");
  17. Long start = System.currentTimeMillis();
  18. target.recycle();
  19. Long end = System.currentTimeMillis();
  20. System.out.println("提交结束。。。。。。。。。共耗时:" + (end - start) + " ms");
  21. }
  22. }

使用代理对象

  1. public static void main(String[] args) {
  2. //创建目标对象(被代理对象)
  3. Host host = new Host();
  4. //创建代理对象
  5. HostProxy hostProxy = new HostProxy(host);
  6. //通过代理对象调用被代理的方法
  7. hostProxy.rent();
  8. hostProxy.recycle();
  9. }

输出结果为,这样就在原有方法的基础上进行了增强

  1. 开始代理。。。。。。。。。
  2. 房东要出租房子!!!
  3. 提交结束。。。。。。。。。共耗时:0 ms
  4. 开始代理。。。。。。。。。
  5. 房东回收房子
  6. 提交结束。。。。。。。。。共耗时:0 ms

但是这样做扩展性很差,被代理对象每增加一个方法就需要在代理类中修改代码,麻烦!更推荐使用动态代理

动态代理(JDK代理)

动态代理在实现静态代理的基础上,被代理对象即使新增方法也不需要修改代理类的代码

需要用到jdk的包java.lang.reflect.Proxy提供的一个方法来获取代理对象,所以又叫jdk代理

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)

接收的三个参数:

  1. 目标对象使用的类加载器
  2. 目标对象实现的接口类型,使用泛型方法确认类型
  3. InvocationHandler接口的实现类:事情处理,执行目标对象方法时,会触发处理器方法,会把当前执行的目标对象方法作为参数传入

InvocationHandler接口提供了一个方法

使用反射的机制来实现被代理的方法调用

  1. public Object invoke(Object proxy, Method method, Object[] args)
  2. throws Throwable;

代理类的代码

  1. public class ProxyInvocationHandler implements InvocationHandler {
  2. //被代理的接口,使用Object类就可以百搭了
  3. private Object target;
  4. //设置被代理的接口
  5. public void setTarget(Object target) {
  6. this.target = target;
  7. }
  8. //生成得到代理类
  9. //Proxy提供创建动态代理类和实例的静态方法
  10. public Object getProxy() {
  11. //返回被代理的对象
  12. //参数:
  13. //1、目标对象使用的类加载器
  14. //2、目标对象实现的接口类型,使用泛型方法确认类型
  15. //3、InvocationHandler接口的实现类:由于本类实现了该接口所以就传个this
  16. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  17. }
  18. //处理代理实例,并返回结果 该方法由InvocationHandler提供
  19. @Override
  20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21. //查看调用的是哪个方法
  22. log(method.getName());
  23. Long start = System.currentTimeMillis();
  24. //使用反射机制实现动态代理
  25. Object result = method.invoke(target, args);
  26. Long end = System.currentTimeMillis();
  27. System.out.println("方法执行完成!共耗时:" + (end - start) + " ms");
  28. return result;
  29. }
  30. //日志方法
  31. private void log(String name) {
  32. System.out.println("----------------------");
  33. System.out.println("代理开始");
  34. System.out.println("使用了" + name + "方法");
  35. }
  36. }

然后调用

  1. public static void main(String[] args) {
  2. //真实角色
  3. Host host = new Host();
  4. //代理角色(现在没有)
  5. ProxyInvocationHandler handler = new ProxyInvocationHandler();
  6. //通过调用程序来处理我们要调用的对象
  7. handler.setTarget(host);
  8. //得到被代理的对象
  9. Rent proxy = (Rent) handler.getProxy();
  10. proxy.rent();
  11. proxy.recycle();
  12. }

运行结果

  1. ----------------------
  2. 代理开始
  3. 使用了rent方法
  4. 房东要出租房子!!!
  5. 方法执行完成!共耗时:1 ms
  6. ----------------------
  7. 代理开始
  8. 使用了recycle方法
  9. 房东回收房子
  10. 方法执行完成!共耗时:0 ms

这样即使被代理的类新增了方法也不需要改动代理类的方法

但是这样的动态代理有一个缺点,就是被代理的类必须实现接口

有时候想要代理不实现任何接口的类就需要用到Cglib代理了

Cglib代理

Cglib代理也算是动态代理的一种,本质是代理被代理对象的子类,所以使用final定义的类就不能被代理

想要使用cglib代理需要导入一个依赖

  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib</artifactId>
  4. <version>2.2.2</version>
  5. </dependency>

创建一个不实现任何接口的类

用老师类来举例

  1. public class TeacherService {
  2. public void teach() {
  3. System.out.println("老师授课中。。。。。。");
  4. }
  5. }

然后写代理类,需要实现cglib提供的接口MethodInterceptor

MethodInterceptor提供了一个intercept方法和上面动态代理的InvocationHandler接口提供的invoke方法类似

代理类

  1. public class ProxyFactory implements MethodInterceptor {
  2. /**
  3. * 维护一个目标对象(被代理的对象)
  4. */
  5. private Object target;
  6. public ProxyFactory(Object target) {
  7. this.target = target;
  8. }
  9. /**
  10. * 返回代理对象
  11. *
  12. * @return target的代理对象
  13. */
  14. public Object getProxyInstance() {
  15. //创建一个工具类
  16. Enhancer enhancer = new Enhancer();
  17. //设置父类
  18. enhancer.setSuperclass(target.getClass());
  19. //设置回调函数
  20. //调自己
  21. enhancer.setCallback(this);
  22. //创建子类对象(代理对象)
  23. return enhancer.create();
  24. }
  25. /**
  26. * 实现目标对象的调用
  27. * 调用目标对象的方法
  28. *
  29. * @param o
  30. * @param method
  31. * @param args
  32. * @param methodProxy
  33. * @return
  34. * @throws Throwable
  35. */
  36. @Override
  37. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  38. System.out.println("Cglib代理模式开启。。。。。");
  39. Long start = System.currentTimeMillis();
  40. //调用代理对象的方法
  41. Object result = method.invoke(target, args);
  42. Long end = System.currentTimeMillis();
  43. System.out.println("Cglib代理模式提交。。。。。共耗时:" + (end - start) + " ms");
  44. return result;
  45. }
  46. }

然后运行

  1. public static void main(String[] args) {
  2. //创建目标对象
  3. TeacherService target = new TeacherService();
  4. ProxyFactory proxyFactory = new ProxyFactory(target);
  5. //返回被代理的对象
  6. TeacherService proxy = (TeacherService) proxyFactory.getProxyInstance();
  7. //执行代理对象的方法
  8. proxy.teach();
  9. }

输出运行结果,同样实现了效果

  1. Cglib代理模式开启。。。。。
  2. 老师授课中。。。。。。
  3. Cglib代理模式提交。。。。。共耗时:0 ms

cglib代理的范围比jdk代理更广

Spring AOP中的应用

由AopProxyFactory根据AdvisedSupport对象的配置来决定
默认策略如果目标类是接口,则使用JDK代理,否则使用cglib