Java从静态代理到动态代理

目录

0 代理模式

代理模式是一种设计模式,说的简单一点,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。

代理模式图例:

Java从静态代理到动态代理

针对该图进行解释,以下几点需要说明:

  1. 用户只关心接口功能(Subject),而不在乎谁提供了功能。
  2. 接口真正实现者是RealSubject,但它不与用户直接接触,而是通过代理。
  3. 代理是 Proxy,它实现了 Subject 接口,能够直接与用户接触。
  4. 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。

1 静态代理

静态代理的方式,不需要引入外部依赖包,通过代码即可实现。

用一个电影院实例说明:针对一部新电影,电影是电影公司委托给影院进行播放的。这个时候的关系是,电影有播放的能力,对外接口能力是播放功能,该接口能被代理,具体的电影是被代理的对象,电影院就是代理,在播放电影基础能力的保证上,增加了自己插播广告,卖票,售卖爆米花等能力。

  • 能被代理的接口

    public interface Movie {
    		//播放的能力
        public void play();
    }
    
  • 具体的被代理对象

    被代理对象实现电影接口,具体播放电影的能力,

    public class TitanicMovie implements Movie{
        @Override
        public void play() {
            System.out.println("正在播放电影《泰坦尼克号》");
        }
    }
    
  • 代理对象

    代理类同样需要实现电影接口,它有一个 play() 方法。不过调用 针对被代理对象的play() 方法时,进行了一些相关利益的处理

    public class Cinema implements Movie{
        private String adLogo;
    
        Movie movie;
        
        public Cinema(Movie movie){
            this.movie = movie;
        }
        
        @Override
        public void play() {
            //代理商自己接的广告
            ad();
            //执行代理的任务
            movie.play();
        }
    
        /**
         * 接收广告
         */
        public boolean receiveAds(String adLogo){
            this.adLogo = adLogo;
            return true;
        }
        /**
         * 接广告插播   代理增强的能力
         */
        private void ad(){
            System.out.println(adLogo);
        }
    }
    
  • 用户client

        public static void main(String[] args) {
            Movie titanicMovie = new TitanicMovie();
            Cinema cinema = new Cinema(titanicMovie);
    
            cinema.receiveAds("让天下没有难做的生意");
            cinema.play();
        }
    

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

静态代理的叫法,是因为代理对象的类型是事先预定好的。即示例中的Cinema类。

2 动态代理(JDK)

动态代理和静态代理的目的,功能都一致,唯一区别就是静态和动态的区别。

上文代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

静态代理与动态代理的区别主要在:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

特点:
动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

用一个商店代理卖货的实例来说明:

同理,首先需要有一个能被代理的接口

  • 被代理接口 设计一个酒的接口

    public interface SellWine {
        void sell();
    
        void stock();
    }
    
  • 再来一个实例对象 实现代理的接口

    public class Maotai implements SellWine {
    
    
        @Override
        public void sell() {
            System.out.println("国酒茅台");
        }
    
        @Override
        public void stock() {
            System.out.println("茅台正在补货中。。。");
        }
    }
    
  • 代理对象,此处和静态代理有很大区别,不需要实现具体接口,即没有类型,各种被对象都可以兼容

    public class Store implements InvocationHandler {
        private Object goods;
        public Object bind(Object object){
            this.goods = object;
            return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //增强方法
            storeLogo();
            method.invoke(goods,args);
            return null;
        }
    
        private void storeLogo(){
            System.out.println("专业微商,诚信代理");
        }
    }
    

    从上面可以看出,代理对象实现的方式需要以下格式:

    Java的动态代理主要涉及两个类,ProxyInvocationHandler

    实现动态代理的方法:

    1. 获取需要代理的类的实例。
    2. 实现 InvocationHandler 接口,实现 invoke 方法,通过 method.invoke(apple, args) 调用被代理类。
    3. 调用Proxy.newProxyInstance 方法,参数:实例类的构造器,实例类的接口,当前代理对象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  • 客户端调用类

    public static void main(String[] args) {
        Maotai maotai = new Maotai();
        SellWine sellWine =(SellWine) new Store().bind(maotai); 
        sellWine.sell();
        sellWine.stock();
    }
    

接下来,如果想再加一个能力,那么此时,代理类可以复用:

  • 代理接口

    public interface SellFood {
        public void sellFood();
    }
    
  • 具体被代理的对象

    public class Taco implements SellFood {
        @Override
        public void sellFood() {
            System.out.println("Taco Tuesday, gi gi gi gi gi gi......");
        }
    }
    
  • 客户端调用类

    public static void main(String[] args) {
        Taco taco = new Taco();
        SellFood sellFood = (SellFood) new Store().bind(taco);
        sellFood.sellFood();
    }
    

3 cglib动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib 和JDK 动态代理最大的区别就是

  • 使用JDK动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

使用方法

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。

  • 导入cglib的maven依赖。spring-core依赖自带该依赖

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
    
  • 不需要接口,直接提供被代理的对象

    public class UserDao {
        public void save(){
            System.out.println("操作DB,存储数据");
        }
    }
    
  • 然后直接使用cglib 实现代理对象

    public class ProxyFactory implements MethodInterceptor {
      
        private Object target;
        public ProxyFactory(Object target) {
            this.target = target;
        }
    
        /**
         * 为目标对象生成代理对象
         * @return
         */
        public Object getProxyInstance() {
            //工具类  设置回调函数  设置父类  创建子类对象代理
            Enhancer en = new Enhancer();
            en.setSuperclass(target.getClass());     
            en.setCallback(this);
            return en.create();
        }
    
        /**
         * 可重写该方法,进行代理功能增强,类似拦截方法
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("前置增强方法");
            Object returnValue = method.invoke(target, args);
            System.out.println("后置增强方法");
            return null;
        }
    }
    
  • client使用

        public static void main(String[] args) {
            //目标对象
            UserDao target = new UserDao();
            //代理对象
            UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
            //执行代理对象方法
            proxy.save();
        }
    

4 总结

  1. 静态代理实现简单,但静态代理只能为一个目标对象服务,如果目标对象过多,会产生很多代理类。
  2. JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
  3. 动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
  4. 静态代理在编译时产生class字节码文件,可以直接使用,效率高。
  5. 动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
  6. cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
上一篇:声明式事务


下一篇:Hibernate-实体详解