Java设计模式-结构型模式-代理模式

代理模式

代理模式

创建一个代理对象来控制对原始对象的访问,可以用来扩展原始对象的功能,同时保护原始对象

一般使用代理模式的目的有两个:

  1. 保护目标对象
  2. 增强目标对象

代理模式有两种实现方案:静态代理 和 动态代理

下面以Dao层执行sql语句为例,讲述代理模式的 应用

静态代理

静态代理就是通过组合/继承的方式,代理类关联原始类,通过调用代理类的方法来实现 原始类的方法增强

这里有一个UserDaoImpl,作为原始类,提供一个模拟插入用户的接口

java 复制代码
public interface UserDao {
    void insert();
}
/*****************************************************/

public class UserDaoImpl implements UserDao{

    public void insert(){
        System.out.println("插入一个用户");
    }
}

创建一个静态代理类,StaticProxyUserDao,完成对userDao的增强

java 复制代码
public class StaticProxyUserDao implements UserDao{

    private UserDao userDao;

    public StaticProxyUserDao(UserDao userDao) {
        this.userDao = userDao;
    }


    @Override
    public void insert(){
        //事务开启...
        System.out.println("事务开启");
        userDao.insert();
        //方法增强...
        //事务提交...
        System.out.println("事务提交");
    }
}

测试代码如下,很容易理解,就不解释了

java 复制代码
@Test
public void staticProxyTest(){
    //直接使用
    UserDao userDao = new UserDaoImpl();
    userDao.insert();

    System.out.println("---------------------------------------------------");
    //通过静态代理的方式,增强insert()方法
    StaticProxyUserDao staticProxyUserDao = new StaticProxyUserDao(userDao);
    staticProxyUserDao.insert();
}

输出:
插入一个用户
---------------------------------------------------
事务开启
插入一个用户
事务提交

动态代理

通过静态代理的方式可以实现方法的增强,但对于每个增强的类,都需要再写一个代理类,这样显得代码比较臃肿,所以就有了动态代理,可以在程序执行过程中,动态的代理目标类。

动态代理有两种实现方式:JDK动态代理CGlib动态代理

JDK动态代理

JDK动态代理是Java标准库中提供的一种代理方式

JDK动态代理是依赖接口的。

实现方式:通过Proxy类中的 静态方法 newProxyInstance(...)实现

java 复制代码
@Test
public void JdkProxyTest(){
    UserDao userDao = new UserDaoImpl();
    /*
    * 参数1:指定一个类加载器
    * 参数2:指定接口 getInterfaces()
    * 参数3:指定对应的代理方法
    * */
    System.out.println();
    UserDao proxyUserDao = (UserDao)Proxy.newProxyInstance(UserDao.class.getClassLoader(), UserDaoImpl.class.getInterfaces(), (proxy, method, args) -> {
        System.out.println("事务开启");
        Object invoke = method.invoke(userDao,args);
        System.out.println("事务关闭");
        return invoke;
    });
    proxyUserDao.insert();
}

可以看出来,JDK动态代理的方法 必须在接口中有定义,所以,如果想要代理没有在接口中定义的方法,这就需要使用CGlib来实现

CGlib动态代理

CGlib 是基于 类继承实现的,通过继承目标类 来实现类的代理,所以目标类必须是可继承的

CGlib支持更细粒度的代理------针对某个方法进行代理

CGLIB是第三方提供的包,需要引入Jar包

java 复制代码
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.1</version>
</dependency>

spring-core jar包里包含cglib ,所以使用spring框架的工程可以直接使用。

实现方式:通过Enhancer类实现,如下:

java 复制代码
@Test
public void CGlibProxyTest(){
    // 创建Enhancer对象 类似于 Proxy类
    Enhancer enhancer = new Enhancer();
    // 设置父类的字节码对象 即需要代理的类
    enhancer.setSuperclass(UserDaoImpl.class);
    //设置回调函数,增强方法
    /**
         * o 代理对象,即生成的代理类的实例,这里是生成的代理对象,不是被代理对象
         * method 被代理方法的Method对象
         * args 方法调用时传递的参数数组
         * methodProxy 方法代理对象
         */
    enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
        System.out.println("事务开启");
        //这里注意使用的是 invokeSuper() 调用的是父类的接口
        Object invoke = methodProxy.invokeSuper(o,objects);
        System.out.println("事务关闭");
        return invoke;
    });
    // 生成代理对象
    UserDaoImpl proxy = (UserDaoImpl) enhancer.create();
    proxy.insert();
}

这里提一下注意的点:

JDK动态代理,依赖接口完成代理,没有实例对象,所以需要传入实例对象,

即 method.invoke(userDao,args); 中的userDao,所以在代理之前,需要先有个实例化的对象

而CGlib动态代理,依赖类继承实现,本身是知道代理对象的所有结构的,所以不需要传入实例对象,所以:

enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {

System.out.println("事务开启");

Object invoke = methodProxy.invokeSuper(o,objects);

System.out.println("事务关闭");

return invoke;

});
第一个参数是 代理对象,而不是被代理对象,调用方法使用的是 invokeSuper() 不是invoke(),调用的是父类的接口

关于CGlib和JDK代理的详细代理过程,可以看下这篇文章
https://blog.csdn.net/ren9436/article/details/125602288

相关推荐
达文汐2 分钟前
【困难】力扣算法题解析LeetCode332:重新安排行程
java·数据结构·经验分享·算法·leetcode·力扣
培风图南以星河揽胜3 分钟前
Java版LeetCode热题100之零钱兑换:动态规划经典问题深度解析
java·leetcode·动态规划
启山智软27 分钟前
【中大企业选择源码部署商城系统】
java·spring·商城开发
我真的是大笨蛋29 分钟前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怪兽源码1 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite1 小时前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙1 小时前
java 通过Minio上传文件
java·开发语言
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji52612 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言