【Java基础】代理模式、静态代理、动态代理、JDK与CGLIB代理区别、在spring AOP中的应用

目录

代理模式

静态代理和动态代理

静态代理

动态代理

JDK动态代理

CGLIB动态代理

动态代理在Spring AOP中的应用场景


本人运行环境为JDK8,CGLIB版本为3.1。

在某些情况下,客户不想或者不能直接引用另一个对象,这时候可以创建一个目标对象的代理对象,在客户端和目标对象之间起到中介的作用。“代理”即为目标对象提供一种代理以控制对这个对象的访问。

代理模式

如果你学过设计模式,你应该会知道“代理模式”,即在不用修改原类代码的前提下对原类的一些方法进行功能增强,比如spring AOP中的事务管理,在调用原方法前开启事务,在调用后提交关闭事务或异常时回滚事务。

静态代理和动态代理

不管是动态代理和静态代理,它们的核心思想都是通过创建一个目标对象(接口)的代理子类(实现类)来替换原有对象,以此来增强原有对象的方法,只不过代理的过程有本质区别。这里代理类的创建有两个前提条件:定义方法增强规则(如事务的开启和提交、回滚)、确定要代理哪些对象的哪些方法(如事务管理的所有类)。

静态代理

代理类在程序编译运行前已经创建完毕,方法增强规则被程序员提前写好了,同时规则也已经适配到具体的类的方法中了,当然前提是程序运行之前你必须事先知道你要代理哪些类的哪些方法。比如下面:

public class ProxyStatic {

    ProxyClass pc = new ProxyClass();

    public static void main(String[] args) {
        ProxyStatic ps = new ProxyStatic();
        ps.method1Proxy();
        ps.method2Proxy();
    }

    void method1Proxy(){
        System.out.println("在要代理的method1方法之前做点什么");
        pc.method1();
    }

    void method2Proxy(){
        System.out.println("在要代理的method2方法之前做点什么");
        pc.method2();
    }
}

class ProxyClass{
    void method1(){
        System.out.println("要代理的方法1");
    }

    void method2(){
        System.out.println("要代理的方法2");
    }
}

打印结果如下:

在要代理的method1方法之前做点什么
要代理的方法1
在要代理的method2方法之前做点什么
要代理的方法2

动态代理

代理类不是提前创建好,而是在程序运行过程中通过Java的反射机制来创建的。方法规则被程序员提前写好,但要适配这些规则的类方法不确定。一般来说,就相当于定义了一个接口,你可以通过这个接口传入任何对象,只要你传入了对象,我就会创建一个新的类,这个新的类拥有你传入的类的所有同名方法,不过方法内容被重载了,在原方法的前后加上了我们定义的方法增强规则,这是动态代理的核心。

再回过头来看spring的事务管理,在spring的事务管理中,你并不需要传入要代理的对象,因为一般spring配置中已经指定了要代理的类,如下:

   <!-- 事务管理规则,匹配所有方法 --> 
 <tx:advice id="txAdvice" transaction-manager="txManager">     
  <tx:attributes>      
    <tx:method name="*"/>    
  </tx:attributes>    
  </tx:advice>    
      
  <!-- 我们定义一个切面,它匹配service包下所有类的所有操作 -->    
  <aop:config>    
    <!-- <aop:pointcut/>元素定义AspectJ的切面表示法,这里是表示x.y.service.FooService类下的任意方法。 -->  
  <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.*(..))"/>     
  <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>    
  </aop:config>    
      
  <!-- 数据元信息 -->    
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">    
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>    
  <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>    
  <property name="username" value="scott"/>    
  <property name="password" value="tiger"/>    
  </bean>    
    
  <!-- 管理事务的类-->    
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    
  <property name="dataSource" ref="dataSource"/>    
  </bean>     

上面是spring中很常见的事务管理器的配置的一部分,作用是对service包下的所有类的所有方法进行事务管理,这要就不需要你指定要代理的类或方法了。

动态代理目前有两种主流的实现方式,一种是JDK自带的代理机制,另一种是开源的CGLIB动态代理。

JDK动态代理

JDK动态代理只能代理目标对象实现的接口中的方法,所以目标对象中不是实现自接口的方法不能代理。

而且创建代理对象返回的只能是接口类型(如果是实现类的类型,会抛出异常),这就意味着如果目标对象实现了多个接口,你只能调用你返回的接口类型的代理类的方法,如果想调用其它接口的代理方法,你必须再创建代理类并返回其它接口类型的代理类。

详情见下面代码:

public class JDKProxyTest{

    static impl impl = new impl();

    public static void main(String[] args) {
        // newProxyInstance的三个参数解释:
        // 参数1:代理类的类加载器,同目标类的类加载器
        // 参数2:代理类要实现的接口列表,同目标类实现的接口列表
        // 参数3:回调,是一个InvocationHandler接口的实现对象,当调用代理对象的方法时,执行的是回调中的invoke方法
        // 必须是Inter1 ,不能是Impl,否则抛出:$Proxy0 cannot be cast to Impl
        //##################返回第一个接口##################
        Inter1 proxy = (Inter1) Proxy.newProxyInstance(impl.getClass().getClassLoader(),
                impl.getClass().getInterfaces(), new InvocationHandler() {

                    @Override
                    // 参数proxy:被代理的对象
                    // 参数method:执行的方法,代理对象执行哪个方法,method就是哪个方法
                    // 参数args:执行方法的参数
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("这是inter1Method方法执行前的操作");
                        Object result = method.invoke(proxy, args);
                        return result;
                    }
                });
        //代理对象执行方法
        proxy.methodInter1();

        //##################返回第二个接口##################
        Inter2 proxy2 = (Inter2) Proxy.newProxyInstance(impl.getClass().getClassLoader(),
                impl.getClass().getInterfaces(), new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("这是inter2Method方法执行前的操作");
                        Object result = method.invoke(proxy, args);
                        return result;
                    }
                });
        proxy2.methodInter2();
    }
}

class impl implements Inter1,Inter2 {
    @Override
    public void methodInter1() {
        System.out.println("inter1Method");
    }

    @Override
    public void methodInter2() {
        System.out.println("inter2Method");
    }
}

interface Inter1 {    void methodInter1();    }

interface Inter2{    void methodInter2();    }

显然JDK动态代理是在创建代理对象时,通过传入一个重写了invoke方法的InvocationHandler对象来定义方法增强规则。上面代码打印结果如下:

这是inter1Method方法执行前的操作
inter1Method
这是inter2Method方法执行前的操作
inter2Method

CGLIB动态代理

CGLIB既可以代理类中的方法,也可以代理接口中的方法;而且只需要创建一个代理类就能调用所有类和接口中的代理方法。如果你想对所代理的方法进行区分,从而执行不同的增强规则,可以通过方法的名字来判断,具体代码如下:

public class CglibProxyTest {

    public static void main(String[] args) {
        final CglibIServicempl cglibIServicempl = new CglibIServicempl();
        // 创建cglib核心对象
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(cglibIServicempl.getClass());
        // 设置回调
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 当你调用目标方法时,实质上是调用该方法
             * intercept四个参数:
             * proxy:代理对象
             * method:目标方法
             * args:目标方法的形参
             * methodProxy:代理方法
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                    throws Throwable {
                System.out.println("所有代理方法执行之前的操作");
                if(method.getName().equals("methodInter1")){
                    System.out.println("方法methodInter1执行前的操作");
                }
                if(method.getName().equals("methodClass")){
                    System.out.println("方法methodClass执行前的操作");
                }
                if(method.getName().equals("methodInter2")){
                    System.out.println("方法methodInter2执行前的操作");
                }
                Object result = method.invoke(cglibIServicempl, args);
                return result;
            }
        });
        // 创建代理对象
        CglibIServicempl proxy = (CglibIServicempl) enhancer.create();
        proxy.methodInter1();
        proxy.methodClass();
        proxy.methodInter2();
    }
}

class CglibIServicempl implements CglibIService,CglibIService2 {
    public void methodClass(){                  // 自己类中的方法
        System.out.println("methodClass");
    }

    @Override
    public void methodInter1() {
        System.out.println("methodInter");
    }

    @Override
    public void methodInter2() {
        System.out.println("methodInter2");
    }
}

interface CglibIService{    void methodInter1();    }

interface CglibIService2{   void methodInter2();    }

CGLIB通过MethodInterceptor的interceptor方法来定义方法增强的规则。上面代码打印结果如下:

所有代理方法执行之前的操作
方法methodInter1执行前的操作
methodInter
所有代理方法执行之前的操作
方法methodClass执行前的操作
methodClass
所有代理方法执行之前的操作
方法methodInter2执行前的操作
methodInter2

显然,相比于JDK动态代理,CGLIB不仅能代理接口方法,也能够代理类的方法,而且使用起来也更方便。

动态代理在Spring AOP中的应用场景

场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
 

参考:

Spring AOP的实现原理及应用场景(通过动态代理)_阿顾同学的博客-CSDN博客_springaop应用场景

Java动态代理的作用及好处_艾米莉Emily的博客-CSDN博客_动态代理的作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值