Loading

Java设计模式之代理模式

代理模式

代理模式顾名思义,就是以你的名义帮你处理一些琐事,例如:用户A 近期准备结婚,他需要预定饭店,布置会场,邀请亲朋,制定菜单等等等等.... 这样处理事情就比较麻烦,针对这些琐事就可以找 婚庆公司 来处理,他们可以解决婚礼过程中的大部分事情,这样用户A 只需要面对结婚这件事儿本身就可以,而不需要考虑其他的事情。

上面这个例子中 婚庆公司的定位就是代理,Java 中有些地方也需要使用代理模式进行解决,例如事务,日志等等,我们希望 service 中只需要负责业务逻辑本身而不要掺杂其他的代码,这里就可以用到代理模式

代理模式总结起来一句话:可以做到在不修改目标对象的功能前提下,对目标功能扩展

我们需要了解掌握三种代理模式:静态代理,JDK 动态代理,CGLIB 动态代理,这里通过日志的方式进行了解

静态代理模式

我们需要准备 service 接口以及代理类和被代理类:

正常执行添加日志

// service接口
public interface UserService {
    // 添加用户的方法
    int insertUser(String username, String password);
}
// 实现类
public class UserServiceImpl implements UserService {
    @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假装有DAO层
        // count = userDao.insertUser(username, password);
        return count;
    }
}
// 测试类
public class AppTest{
    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        System.out.println(us.insertUser("老八", "秘制小汉堡"));
    }
}

当我们运行测试类的 main 方法后发现控制台只输出了数字 1,如果我们想要实现日志功能的话,需要在实现类中添加打印语句,如下所示:

// 【改】实现类
public class UserServiceImpl implements UserService {
    @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假装有DAO层
        // count = userDao.insertUser(username, password);
        System.out.println("用户【" + username + "】插入成功!");
        return count;
    }
}

日志添加完成!但是按照之前的说法,我们不希望在 service 中掺杂其他的代码,它仅仅负责业务逻辑即可,所以我们要移除掉实现类中的输出语句,新建一个代理类来完成这部分功能

使用静态代理添加日志

// 代理类
public class UserServiceProxy implements UserService {
    private UserService us;
    public UserServiceProxy( UserService us ){
        this.us = us;
    }
    @Override
    public int insertUser(String username, String password) {
        int count = this.us.insertUser(username, password);
        System.out.println("用户【" + username + "】插入成功!");
        return count;
    }
}
// 测试类
public class AppTest{
    public static void main(String[] args) {
    	// 这里创建代理类,将被代理类的实例作为参数传递过去,返回代理对象
        UserService us = new UserServiceProxy( new UserServiceImpl() );
        System.out.println(us.insertUser("老八", "秘制小汉堡"));
    }
}

创建代理类的对象,将被代理的实例作为参数传过去,让代理类以自己的名义帮自己实现日志功能,这样实现类中只需要负责书写业务逻辑就可以了。这就是 静态代理模式

JDK动态代理

静态代理可以做到在不修改源代码的情况下添加新的功能,但是如果 service 比较多的话静态代理的弊端就出来了,每个 service 都对应一个代理类,这样代码量就大大增加,这里就可以使用 JDK动态代理来解决这个问题:

代码实现

JDK动态代理依靠 java.lang.reflect.Proxy 实现,保持接口和实现类不动,新建代理类:

public class LogProxy {
    public static Object getInstance( Object obj){
        // 获取被代理类的类加载器
        ClassLoader classLoader = obj.getClass().getClassLoader();
        // 获取被代理的类实现的所有接口
        Class<?>[] classes = obj.getClass().getInterfaces();
        // 在这里接管目标对象执行函数的过程
        // proxy不用管,method:被执行的函数,args:传递的参数
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args)->{
            	Object result = method.invoke(obj, args);
                System.out.println("用户【" + Arrays.asList(args) + "】插入成功!");
                return result;
            };
        Object obj = Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        return obj;
    }
}

我们通过 Proxy.newProxyInstance 来获取到代理对象,调用这个方法需要传递三个参数:

  • 【classLoader】被代理的类的类加载器
  • 【classes】被代理的类所实现的所有接口
  • 【invocationHandler】具有回调功能的函数式接口,这里直接通过 lambda 表达式创建对象即可,当被代理的类执行某个方法的时候,真正的执行过程会在这里触发,通过 lambda 创建该类的实例需要三个形参:
    • 【proxy】暂时不理解...不用管
    • 【method】被代理的类执行的方法的实例
    • 【args】被代理类执行方法时传入的参数
    • 最后记得将 invoke 执行函数后的结果返回,不然调用者那边会收不到返回结果

JDK动态代理的缺点

我们通过 Proxy.newProxyInstance 传参成功返回了代理对象 ,然后在测试类中成功转换为 UserService 实例进行日志增强,现在我们尝试打印一下转换之前的代理对象的 class 是什么样子的:

// 代理类
public class LogProxy {
    public static Object getInstance(Object obj) {
        // 省略多余代码
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] classes = obj.getClass().getInterfaces();
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
        // 获取代理对象并进行打印
        Object proxy = 
            Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        System.out.println(proxy.getClass());
        return proxy;
    }
}

打印结果为:class com.sun.proxy.$Proxy0,并不是我们熟悉的类型,那为什么他可以转换为 UserService 类型呢?因为他获取了被代理目标类实现的所有接口:obj.getClass().getInterfaces(),让我们打印一下代理对象实现的接口是什么样子的:

public class LogProxy {
    public static Object getInstance(Object obj) {
        // 省略多余代码
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] classes = obj.getClass().getInterfaces();
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
        // 获取代理对象并进行打印
        Object proxy = 
            Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
        return proxy;
    }
}

打印结果为:[interface site.hanzhe.proxy.jdkProxy.UserService],代理对象实现了和被代理对象一模一样的接口,所以他可以转换为 UserService,那么如果我们被代理的类没有实现任何接口会怎么样呢?

// 修改被代理的类,让他不实现任何接口
public class UserServiceImpl /* implements UserService */ {
    // @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假装有DAO层
        // count = userDao.insertUser(username, password);
        System.out.println(username + "--" + password);
        return count;
    }
}

被代理类修改完成了,代理类保持不变,在测试类中修改强制转换类型,然后进行测试

public class AppTest{
    public static void main(String[] args) {
        UserServiceImpl us =
            	(UserServiceImpl)LogProxy.getInstance(new UserServiceImpl());
        us.insertUser("老八", "秘制小汉堡");
    }
}

系统抛出了 java.lang.ClassCastException 异常,详细信息为 com.sun.proxy.$Proxy0 cannot be cast to site.hanzhe.proxy.jdkProxy.UserServiceImpl,告诉我们无法强制转换为 UserServiceImpl 类型的对象,这样一来我们就测出了一个问题,JDK动态代理要求 被代理的对象必须实现至少一个接口

CGLIB动态代理

在之前的静态代理中,代理类与被代理类实现了同一个接口,从而达到对每个函数进行代理的目的,而在 JDK动态代理中要求被代理的类必须实现至少一个接口,那么如果我们被代理的类没有实现接口的话如何代理?

CGLIB动态代理模式,以被代理对象作为父类,动态创建一个子类作为代理对象并返回,CGLIB动态代理底层使用了 ASM字节码框架来生成子类,相比较比 Java反射效率要高

想要使用 CGLIB动态代理需要引入 CGLIB 和 ASM 的 jar 包,这里记录一下两个 jar 包所对应的 maven 坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.0</version>
</dependency>

代码实现

// 一个没有实现任何接口的 service 类
public class UserService {
    // 添加用户的方法
    public int insertUser(String username, String password){
        int count = 1;
        // 假装有DAO层
        // count = userDao.insertUser(username, password);
        System.out.println(username + "--" + password);
        return count;
    }
}
// 代理类代码
public class LogProxy {
    public static Object getInstance(Object obj){
        // 创建CGLIB的工具类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(
            (InvocationHandler)(Object proxy, Method method, Object[] args)->{
                Object result = method.invoke(obj, args);
                System.out.println("用户【" + Arrays.asList(args) + "】插入成功!");
                return result;
            }
        );
        return enhancer.create();
    }
}

代理类代码说明:

  • 【Enhancer】CGLIB中的工具类,通过工具类来动态创建一个新的实例
    • 【setSuperclass】设置对象继承的父类,该方法需要传入被代理类的 class 对象
    • 【setCallback】通过回调的方式实现代理功能,该方法需要传入一个 Callback 对象, 这里使用他下面子接口 InvocationHandler,也就是 JDK动态代理时手动创建的那个对象,由于类型原因在使用的时候使用了强制类型转换
      • 【InvocationHandler】函数式接口的三个参数这里就不解释了,记得将 invoke 结果返回即可
posted @ 2021-01-10 21:05  Java小学生丶  阅读(117)  评论(0)    收藏  举报