设计模式(代理模式)

简单介绍

代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,我们可以对实际对象中的方法进行一定程度的增强。

在这里插入图片描述

静态代理

静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

根据上面代理模式的类图,来写一个简单的静态代理的例子:

有一个User的实体类,我们有一个业务层,业务层干的事情就是保存一个实体对象,业务层调用持久层dao,进行数据库存储。我们通过一个代理对象来代理业务层的操作。

  • 实体类
package com.company.proxy;

public class User {

    private Integer userId;

    private String userName;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

  • 业务层接口
package com.company.proxy;

public interface UserService {
    int save(User user);
}

  • 业务层实现类
package com.company.proxy;

public class UserServiceImpl implements UserService{
    private UserDao userDao;
    @Override
    public int save(User user) {
        userDao = new UserDaoImpl();
        userDao.insert(user);
        System.out.println("Service 调用dao层添加user");
        return 1;
    }
}

  • 持久层接口
package com.company.proxy;

public interface UserDao {
    int insert(User user);
}

  • 持久层实现类
package com.company.proxy;

public class UserDaoImpl implements UserDao{
    @Override
    public int insert(User user) {
        System.out.println("插入一条数据成功!!!");
        return 1;
    }
}

  • 静态代理类
package com.company.proxy.staticproxy;

import com.company.proxy.User;
import com.company.proxy.UserService;
import com.company.proxy.UserServiceImpl;

public class UserServiceStaticProxy2 implements UserService {
    // 被代理的对象
    private UserService userService;

    public UserServiceStaticProxy2(UserService userService1){

        if (userService1.getClass() == UserServiceImpl.class) {
            this.userService = (UserService)userService1;
        }
    }

    @Override
    public int save(User user) {
        beforeMethod();
        this.userService.save(user);
        afterMethod();
        return 0;
    }

    public void beforeMethod(){
        System.out.println("before mothod ===========");
    }

    public void afterMethod(){
        System.out.println("after mothod ===========");
    }
}

该静态代理类主要的作用就是对业务层方法进行代理,同时befoe、after方法是可以对业务层方法进行增强。

  • 测试类
package com.company.proxy.staticproxy;

import com.company.proxy.User;
import com.company.proxy.UserService;
import com.company.proxy.UserServiceImpl;

public class test2 {
    public static void main(String[] args) {
        User user = new User();
        user.setUserId(2);
        user.setUserName("xiaohuang");
        // 需要被代理的对象
        UserService userService = new UserServiceImpl();
        // 生成代理对象
        UserServiceStaticProxy2 proxy2 = new UserServiceStaticProxy2(userService);
        // 使用代理对象代理执行业务
        proxy2.save(user);
    }
}

在这里插入图片描述

Spring中的AOP面向切面编程,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作(增强),这个切点就相当于我们这个例子中的save方法。

动态代理

代理类在程序运行时创建的代理方式被成为动态代理。
我们上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

    public void giveMoney() {
        //调用被代理方法前加入处理方法
        beforeMethod();
        stu.giveMoney();
    }

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。所以建议使用动态代理实现。

动态代理的简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

这里我们的业务与静态代理完全相同,只需要把静态代理修改为动态代理就行。

  • 代理类
package com.company.proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceDynamicProxy implements InvocationHandler {
    // 需要进行代理的对象(被代理对象)
    private Object target;
    // 通过构造函数传入被代理对象
    public UserServiceDynamicProxy(Object target) {
        this.target = target;
    }

    /**
     * 创建被代理对象的代理类的对象
     * @return 代理类对象
     */
    public Object bind() {
        Class cls = target.getClass();
        /**
         * loader : 确定需要谁的代理类对象(确定被代理类)
         * interfaces : 该被代理类实现了哪些接口
         * h : InvocationHandler
         */
        return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), this);
    }

    /**
     * 代理类执行业务方法(当我们使用代理类对象调用被代理类的方法时执行)
     * @param proxy
     * @param method : 执行的方法
     * @param args   : 该方法拥有的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object argObject = args[0];
        beforeMethod();
        // invoke : Method类中提供的invoke方法,作用就是执行被代理对象target的method方法(会将代理类中的参数传递过去执行)
        Object object = method.invoke(target, args);
        afterMethod();
        return object;
    }

    public void beforeMethod(){
        System.out.println("DynamicProxy before mothod ===========");
    }

    public void afterMethod(){
        System.out.println("DynamicProxy after mothod ===========");
    }
}

  • 测试
public class test {
    public static void main(String[] args) {
        User user = new User();
        user.setUserId(1);
        user.setUserName("fulin");
        UserService userServiceDynamicProxy = (UserService)new UserServiceDynamicProxy(new UserServiceImpl()).bind();
        userServiceDynamicProxy.save(user);
    }
}

通过分析这个代理类,大致应该就是如果我们需要对某些类进行代理,且要增强的方法相同,就可以使用相同的代理类,增强方法不同,那么就重新搞个代理类就行了。

动态代理的优势:
在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。

动态代理类看不见源码具体实现,理解起来非常的抽象,如果仅仅是学会使用,可以直接背下来,以后直接根据模板弄。需要更深入的了解还是需要进行源码级别的解读。

动态代理原理分析

我们利用Proxy类的 newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤:

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);
        }
    }

重点是这四处位置:

final Class<?>[] intfs = interfaces.clone();
// 动态代理的关键:产生代理类(不是代理对象)
Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});

上面第二句中,我们深入源码(这里不展示),会发现这个类文件时缓存在java虚拟机中的。我们可以通过下面的方法将其打印到文件里面,一睹真容:

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Student.class.getInterfaces());
String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(classFile);
    fos.flush();
    System.out.println("代理类class文件写入成功");
} catch (Exception e) {
    System.out.println("写文件错误");
}

对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
 
public final class $Proxy0 extends Proxy implements Person {
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
  *被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
  *
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //这个静态块本来是在最后的,我把它拿到前面来,方便描述
   static
  {
    try
    {
      //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  *this.h.invoke(this, m3, null);这里简单,明了。
  *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
  *再联系到InvacationHandler中的invoke方法。嗯,就是这样。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
 
}

算了,第一遍也不打算再深入了,后面复习看这篇博客去跑一边。推荐博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值