GVKun编程网logo

静态代理、动态代理与Mybatis的理解(mybatis静态代理和动态代理)

11

这篇文章主要围绕静态代理、动态代理与Mybatis的理解和mybatis静态代理和动态代理展开,旨在为您提供一份详细的参考资料。我们将全面介绍静态代理、动态代理与Mybatis的理解的优缺点,解答my

这篇文章主要围绕静态代理、动态代理与Mybatis的理解mybatis静态代理和动态代理展开,旨在为您提供一份详细的参考资料。我们将全面介绍静态代理、动态代理与Mybatis的理解的优缺点,解答mybatis静态代理和动态代理的相关问题,同时也会为您带来Java 代理[1]-静态代理、JDK动态代理和Cglib动态代理的简单使用、java 代理模式-静态代理与动态代理、Java 动态代理与静态代理以及它能为我们做什么、Java 方法代理实例操作,静态代理、JDK动态代理、CGLIB动态代理的实用方法。

本文目录一览:

静态代理、动态代理与Mybatis的理解(mybatis静态代理和动态代理)

静态代理、动态代理与Mybatis的理解(mybatis静态代理和动态代理)

静态代理、动态代理与Mybatis的理解

这里的代理与设计模式中的代理模式密切相关,代理模式的主要作用是为其他对象提供一种控制对这个对象的访问方法,即在一个对象不适合或者不能直接引用另一个对象时,代理对象充当中介的作用。

现实生活中比较贴切的例子比如租房,被代理对象就是房东,代理对象就是中介,使用者就是租客,租客通过中介向房东租赁房屋,即使用者通过代理对象访问被代理对象。

一、直接调用

  • 一般我们通过new关键字初始化对象来调用类中的方法

  • 如下代码,创建Human接口,Student类实现了Human接口,在main函数中,通过new关键字来初始化Student对象来实现对Student类中say()方法的调用

interface Human{
    public void say();
}

class Student implements Human{
    @Override
    public void say() {
        System.out.println("I'm a Student");
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        Human human = new Student();
        human.say();
    }
}

//输出
//I'm a Student

二、静态代理

实现静态代理有以下三个步骤:

  • 创建接口,通过接口来实现对象的代理

  • 创建该接口的实现类

  • 创建Proxy代理类来调用我们需要的方法

interface Human{
    public void say();
}

class Student implements Human{

    @Override
    public void say() {
        System.out.println("I'm a Student");
    }
}

class StudentProxy implements Human{
    private Student student;

    public StudentProxy(){}

    public StudentProxy(Student student){
        this.student = student;
    }
    
    private void begin(){
        System.out.println("Begin");
    }
    
    private void end(){
        System.out.println("End");
    }
    
    @Override
    public void say() {
        begin();
        student.say();
        end();
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        Student student = new Student();
        StudentProxy studentProxy = new StudentProxy(student);
        studentProxy.say();
    }
}

//输出
//Begin
//I'm a Student
//End

在上述代码中,我们在没有修改Student类中say()方法的情况下,实现了在原来的say()方法前后分别执行sayHello()sayBye()方法。由此引出代理模式的主要作用:

  • 在不修改被代理对象的情况下,实现对被代理对象功能的增强

同时,静态代理也存在一些比较致命的缺点。想象这样一个场景:若新增一个Worker类实现了Human接口,我们应该如何去代理这个Worker类?比较容易想到的方法是扩大StudentProxy的代理范围,然后将Worker当作参数传入StudentProxy,然后继续使用StudentProxy类代理Worker对象。这样实现功能是没有问题的,但会存在如下问题:

  • 当Human接口的实例中方法增加时,代理类中代码会变得非常冗长
  • 当有其他不属于Human类的子类需要被代理时,需要新增一个新的代理类

由此引出动态代理

三、动态代理

使用动态代理时,我们不需要编写实现类,而是通过JDK提供的Proxy.newProxyInstance()创建一个Human接口的对象。

生成动态代理有以下几个步骤:

  • 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    • 使用的ClassLoader,通常是接口类的ClassLoader
    • 需要实现的接口数组,至少需要传入一个接口进去;
    • 用来处理接口方法调用的InvocationHandler实例。
  • 将返回的Object强制转型为接口。
interface Human{
    public void say();
}

class Student implements Human{

    @Override
    public void say() {
        System.out.println("I'm a Student");
    }
    
    @Override
    public void eat() {
        System.out.println("I eat something");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public MyInvocationHandler(){}

    public MyInvocationHandler(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Begin");
        Object invoke = method.invoke(object, args);
        System.out.println("End");
        return invoke;
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        MyInvocationHandler handler = new MyInvocationHandler(new Student());
        Human human = (Human) Proxy.newProxyInstance(
                Human.class.getClassLoader(),
                new Class[] {Human.class},
                handler);
        human.say();
        human.eat();
    }
}

当Human接口的实例中方法增加时,如新增eat()方法时,只需要在Student类中直接实例化该方法即可。

当有其他不属于Human类的子类需要被代理时,只需要将传入MyInvocationHandler()中的new Student()替换为需要被代理的子类即可。

综上所述,通过动态代理基本可以解决静态代理的痛点。

四、Mybatis中的动态代理

在Springboot项目中配置Mybatis时,我们仅编写了Mapper接口,并未编写Mapper接口的实现类,那么当我们调用Mapper接口中方法时,是如何生成方法体的呢?

首先,项目在启动时生成Mapperfactorybean对象,通过factory.getobject()方法获取mapper的代理对象





将上述过程与动态代理的步骤进行对比,我们最终获取的是一个类似于动态代理例子中Human的代理对象,这里是MapperProxy的代理对象。至此,一个Mapper代理对象就生成完毕。

然后,当我们完成项目中Mybatis的相关配置后,使用我们Mapper接口中的数据库相关方法时,将调用之前生成的MapperProxy代理对象中invoke()方法。类比动态代理的例子,即调用MyInvocationHandler类中的invoke()方法。

//83行代码含义:如果method为Object中定义的方法(toString()、hash()...)则直接执行,这里我们要执行的是Mapper接口中定义的方法,显然返回为false
Object.class.equals(method.getDeclaringClass())

于是执行cachedInvoker(method)invoke()方法

进入execute()方法,我们看到之前我们配置的mapper.xml在MapperMethod初始化时,被解析成了59行的command。在此处通过sqlSession对象实现了对数据库的操作。

至此,我们对Mybatis的数据库操作流程已经有了大致了解。回到开头的问题:为什么仅编写了Mapper接口,并未编写Mapper接口的实现类,仍然可以实现我们的功能?这与我们之前的动态代理例子有什么区别呢?

研究代码我们发现,我们并没有直接使用method.invoke()方法来调用实现类中的方法,而是调用了cachedInvoker(method)invoke()方法解析我们配置的Mapper.xml,并通过sqlSession实现了数据库操作,这个invoke()方法相当于Mybatis自定义的方法。因此,这里的invoke()方法具体执行的逻辑是根据Mapper.xml配置来生成的,这个Mapper.xml配置可以理解为Mapper接口的实现类。

Java 代理[1]-静态代理、JDK动态代理和Cglib动态代理的简单使用

Java 代理[1]-静态代理、JDK动态代理和Cglib动态代理的简单使用

  前段时间看了下 Mybatis , 顺便梳理下关于代理这一块的知识。 

  这里自己简单想了一个近期关于火车票的实例,可能很多地方还有不当之处,还请谅解。

//卖火车票
public interface TrainTicketSeller {
    void query();
    void sell();
}

//12306
public class Seller12306 implements TrainTicketSeller {
    
    //实际业务
 @Override public void query() { System.out.println("您好,您回家的票还有……0张"); } ​ //实际业务  @Override public void sell() { System.out.println("您已购买回家的票!"); } ​ }

  下面分别用3种代理来实现同样的功能。

一、静态代理

  

  第三方代理售票,比如美团、铁友啊

//第三方代理售票,加了漂亮的头和尾。。。>..
public class MeiTuanSeller implements TrainTicketSeller {
    private Seller12306 seller12306;
​
    public MeiTuanSeller(Seller12306 seller12306) {
        this.seller12306 = seller12306;
    }
​
    @Override
    public void query() {
        //额外功能操作
        System.out.println("*********************");
        //实际业务
        seller12306.query();
        //额外功能操作
        System.out.println("********************");
    }
​
    @Override
    public void sell() {
        System.out.println("*********************");
        seller12306.sell();
        System.out.println("********************");
    }
}

  使用

//调用
TrainTicketSeller seller = new MeiTuanSeller(new Seller12306());
seller.query();
seller.sell();
​
/**输出结果:
**********************
您好,您回家的票还有……1张
**********************
**********************
您已购买回家的票!
**********************
**/

 

问题1,不用代理可以么?

​   当然是可以的,就想买个票,直接上12306就行,不嫌弃美丑难用。

 

问题2,代理中新加的功能,不能直接修改被代理方或者在被代理方增加么?

​   当然也是可以的,但是。。为什么12306不改呢?就光卖个票都被喷成啥样了,还有心思至于网站好不好看交互怎么样?几亿人用了这么多年这个样式,说改就改貌似不太合适吧? 好,就算你改了。改完,立马被喷了大家都用不惯,那是回到原来的样式啊,还是再换个新的呢?不管怎么样还是得再改一次。(修改有成本和风险,甚至可能影响核心系统稳定)

​   但是如果第三方售票来做这个事情,第一12306不用动,第二如果我觉得第三方也不好看不好用,我还是能继续用原来的12306,第三如果美团做的不好看,旁边360、铁友貌似还挺好看的,我去试试。

  个人总结起代理的优点,本质上还是符合单一职责原则和开闭原则(对扩展开放,对修改封闭)。核心职责和非核心职责的的划分清晰,保持原系统的稳定性的同时增加了扩展性

​   可能会存在的缺点呢?如果被代理类功能繁杂多样,代理类只会更复杂臃肿。另一个问题是,代理类的新功能代码比较难复用。这里就可以引出动态代理了。

 

二、动态代理

  动态代理好处?本质上还是代码复用的问题。代理功能只用写一遍啊。Java 动态代理一般有两种,一种是通过接口来实现(Jdk动态代理),一种是通过继承来实现(cglib)。

1、JDK 动态代理

​   用 JDK 动态代理实现上面一样的功能。

//代理类不跟任何具体被代理有强绑定关系,理论上所有需要 doAfter() 和 doAfter()的都可以使用此代理
public class MtDyProxy implements InvocationHandler {
    /**
     * 被代理对象
     */
    private Object target;
​
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        Object invokeRes = method.invoke(target, args);
        doAfter();
        return invokeRes;
    }
​
    private void doAfter() {
        System.out.println("**********************");
    }
​
    private void doBefore() {
        System.out.println("*********************");
    }
​
}

  使用

TrainTicketSeller seller = (TrainTicketSeller) new MtDyProxy().bind(new Seller12306());
seller.query();
seller.sell();
​
/**输出结果:
**********************
您好,您回家的票还有……1张
**********************
**********************
您已购买回家的票!
**********************
**/

 

  假如现在另一个地方也需要此代理提供的功能,我的代理不需要做任何改动,只需要在调用的时候,更改bind()参数和强转类型即可。

ISubject subject = (ISubject) new MtDyProxy().bind(new ReadSubject());
subject.do();

  由于代理类是动态生成的,所以调用链不够清晰,不能一眼看出一步步是怎么走的。其实这点,可以将动态代理的生成的Class输出到本地,再反编译出来就OK了。这个点下次来说明。

  动态代理最大的缺点是,必须依赖接口。生成的代理类中只会有实现接口的方法。如果需要被代理的方法,没有实现接口,那很遗憾就无法使用 Jdk动态代理。这个时候,就需要 Cglib 代理了。

2、CGLIB 动态代理

  通过 CGLIB 来实现代理

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
public class CglibCommonProxy implements MethodInterceptor {
​
    /**
     * 被代理对象,供代理方法中进行真正的业务方法调用
     */
    private Object target;
​
    //相当于JDK动态代理中的绑定
    @SuppressWarnings("unchecked")
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        // 创建动态代理类对象并返回
        return enhancer.create();
    }
​
    // 实现回调方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        doBefore();
        //调用业务类(父类中)的方法
        Object result = proxy.invokeSuper(obj, args);
        doAfter();
        return result;
    }
​
    private void doAfter() {
        System.out.println("**********************");
    }
​
    private void doBefore() {
        System.out.println("**********************");
    }
​
}

  使用

@Test
public void cglibProxy() throws Exception {
    CglibCommonProxy cglib = new CglibCommonProxy();
    //注意这里,不需要依赖接口
    Seller12306 seller = (Seller12306) cglib.getInstance(new Seller12306());
    seller.query();
    seller.sell();
}
​
/**输出结果:
**********************
您好,您回家的票还有……1张
**********************
**********************
您已购买回家的票!
**********************
**/

  这里只是简单了做了下使用说明,比较深的东西希望自己能在下次给出来。

三、总结

  现在已经很少看到单独使用静态代理的了。。。

  前面也有提到,JDK动态动态代理是通过接口来实现的,而 Cglib 是通过继承来实现的。这也是 Cglib 相对于前者的一个优势。

四、补充

  • 代理模式和装饰器模式

    今天也看了不少有讨论这这两者区别异同的文章,一些统一的说法就是装饰为了增强,代理更偏控制。

    说说自己的看法,两者确实十分相似,但是其实没有必要说把两个东西完全区分开,本质上都是为了解耦易扩展。装饰也好代理也好,只是文字上的不同而已,不必要纠结许多年前的理论文字了。

    实际应用中,我相信没人说会在用的时候去纠结要用装饰还是代理。我们应该在意的点是,怎么更好的解决问题。设计模式只是一些经典的思想,思想应当是一直在进步的。况且现在 80% 的应用粗制滥造的程度,还达不到纠结设计模式的时候。

java 代理模式-静态代理与动态代理

java 代理模式-静态代理与动态代理

最近在研究SpringAOP,当然要学习AOP就要知道这么健硕、强大的功能的背后究竟隐藏着怎样不可告人的“秘密??

接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的,

我们作为一个有“内涵的”程序员就更应该掌握啦!(本文需要细心、带有审视的目光来甄别其中的内容)


在学习代理模式的时候我首先要提出几个问题,

  1、什么是代理模式?

    举个例子吧:我们生活中的租房问题。假如我们去租个房子,我们大多数情况下是不会知道房主(就是真正租房,一手货源)的,我们是不是都是先去某些租房平台,或者去找当地的中介去询问何时的房子。我们通过九牛二虎之力在中介那里找到了个物美价廉的房子后,你的租金是不是交给了中介,中介还会收取一些额外的推荐费啦,押金啦、手续费等之类的,那么好,这样的一小段,就已经出来了其中两大核心对象了。

    房主(把房子交给中介的人):被代理对象

    中介(租给你房子的人):代理对象

  2、代理模式有哪些作用? 

    1.可以隐藏目标的的具体实现(还是拿上面租房的例子来说,房主把房子交给了中介,并和中介谈好了价格我 7你3啊。然后当我们去租房子的时候,是中介正面把房子租给了我们,而真正背后卖房子的并未出面,这样就隐藏了背后人的信息和提高了背后人的安全)

    2.可以在不修改目标类代码的情况下,对其增加新的功能。(上面例子来说:房东把房子交给中介的时候价格可能只有1000,但是房东可以卖到5000,然后卖出去后在把1000给房东,自己收入4000,这样原房东不但收到了应由的钱,中介还收入了更多的额外费用)。

  3、代理模式有哪几种?分别都有什么不同?

    在我们Java程序中代理模式分为:静态代理和动态代理(动态代理又分为:JDK动态代理和CGLIB动态代理)

    至于什么不同,接下来正式我们着重要学习的内容

 


 

静态代理

什么是静态代理呢?

  • 顾名思义,就是静态的,死的,一旦创建了就不允许修改了或者说很难修改(指的是程序运行期间)
  • 专业一些的解释
    • 如若代理类在程序运行之前就已经存在,那么这种代理方式被称为静态代理。这种情况下的代理类通常都是我们在Java代码中定义的。
    • 通常情况下静态代理类和目标类通常都会实现同一个接口或者派生自同一父类  

具体实现:

被代理类与代理类共同实现的接口

package cn.arebirth.staticproxy;

/**
 * 需要实现的共同接口
* 因为要保证代理类要不改变被代理类原来功能的基础上增加新的功能
*/ public interface RentalHouse { /** * 出租房子 */ void rent(); }

被代理类(房东)

package cn.arebirth.staticproxy;

/**
 * 房东(目标类、被代理类)
 */
public class Host implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房东,出租500平米的大房子");
    }
}

代理类(中介)

package cn.arebirth.staticproxy;

/**
 * 静态代理类(中介、代理类)
 * 注意:需要被代理类实现相同的接口
 */
public class StaticProxy implements RentalHouse {
    private RentalHouse rentalHouse;

    public StaticProxy(RentalHouse rentalHouse) {
        this.rentalHouse = rentalHouse;
    }

    @Override
    public void rent() {
        System.out.println("我是中介,收你500推荐费");
        //调用被代理类的租房方法
        rentalHouse.rent();
        System.out.println("我是中介,我又想收你1000块钱!");
    }
}

测试类

package cn.arebirth.staticproxy;

public class Test {
    public static void main(String[] args) {
        //创建被代理度下行
        Host host = new Host();
        /**
         * 创建代理对象,并把被代理对象传递给代理对象,
         * 因为他们都实现相同的接口,实现了相同的方法,这样的话传递的对象可以是房东1 房东2 ...
         */
        StaticProxy proxy = new StaticProxy(host);
        proxy.rent();
    }
}


输出结果:
    我是中介,收你500推荐费    
    我是房东,出租500平米的大房子
    我是中介,我又想收你1000块钱!

 

试想一下,如果有两个房东,三个,四个,甚至更多个房东的话,我们怎么写?

 

被代理类

package cn.arebirth.staticproxy;

/**
 * 房东1(目标类、被代理类)
 */
public class Host1 implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房东1,出租500平米的大房子");
    }
}


package cn.arebirth.staticproxy;

/**
 * 房东2(目标类、被代理类)
 */
public class Host2 implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房东2,出租500平米的大房子");
    }
}

 

代理类(中介)

package cn.arebirth.staticproxy;

/**
 * 静态代理类(中介、代理类)
 * 注意:需要被代理类实现相同的接口
 */
public class StaticProxy implements RentalHouse{
    //什么价位的房子
    private int moneuy;

    public Agent(int moneuy) {
        this.moneuy = moneuy;
    }

    @Override
    public void renting() {
        //出租房东的租房
        //中介调用的租房方法仍然是房东的租房方法
        System.out.println("收取50元推荐费");
        if (moneuy <= 800) {//金额小于等于800的时候
            Host1 host = new Host1();
            host.rent();
        } else {
            Host2 host = new Host2();
            host.rent();
        }

        System.out.println("收取500元押金费用");
    }
}

测试类

public class Test{
    public static void main(String[] args) {
        StaticProxy proxy= new StaticProxy(1000);
        proxy.renting();
    }
}


输出结果:
我是房东2,出租500平米的大房子

 

静态代理的缺点:

  我们仔细来观察下,随着我们的被代理对象的增多,也是就是房东越来越多,那我们的被代理类就会越来越冗余,中介的压力也就会越来越大。

 


 

动态代理

  常用的动态代理又分为JDK动态代理和CGLIB动态代理

那么两者的使用场景又是什么呢??

  如果目标对象实现了接口,就是上面我们举到的例子,房东和中介共同实现的接口类似,这样的话就采用JDK动态代理

  如果目标对象没有实现接口,必须采用CGLIB动态代理

 

具体实现:

 

要实现的共同接口

package cn.arebirth.jdkproxy;

/**
 * 需要实现的共同接口
 */
public interface RentalHouse {
    /**
     * 出租房子
     */
    void rent();
}

房东(被代理对象)

package cn.arebirth.jdkproxy;

/**
 * 房东(目标类、被代理类)
 */
public class Host implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房东,出租500平米的大房子");
    }
}

核心来了!(JDK动态代理实现类)package cn.arebirth.jdkproxy;

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

/**
 * JDK动态代理类
 */
public class JdkProxy implements InvocationHandler {
  //接收要被代理的类 RentalHouse rentalHouse;
public JdkProxy(RentalHouse rentalHouse) { this.rentalHouse = rentalHouse; } /** * @return 执行该方法就会产生代理类 */ public Object getProxy() { /** * Proxy.getProxyInstance(param1,param2,para3); * 参数一:传入一个classLoader()对象 * 参数二:传入被代理类所实现的接口的数组,因为代理类底层要根据接口产生 * 参数三:参数类型是InvocationHandler即可,this即当前类,我们当前类实现了此接口 */ Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{RentalHouse.class}, this); return proxy; } /** * 这个方法类似于上面我所讲述的静态代理模式里面的中介类的rent()方法 * @param proxy 代理对象--中介 * @param method 代理对象中的方法 * @param args 代理对象方法中的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是中介,收你500推荐费"); Object invoke = method.invoke(rentalHouse, args); System.out.println("我是中介,我又想收你1000块钱!"); //返回执行方法后的返回值 return invoke; } }

测试类

package cn.arebirth.jdkproxy;

public class Test {
    public static void main(String[] args) {
        //创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去
        JdkProxy jdkProxy = new JdkProxy(new Host());

        //获得代理类 这里一定要写他们共同实现的接口  利用java多态的特性,如果直接写真实类型是会报错的
        RentalHouse  proxy= (RentalHouse)jdkProxy.getProxy();
        proxy.rent();
    }
}

输出结果:
  我是中介,收你500推荐费   我是房东,出租500平米的大房子   我是中介,我又想收你1000块钱!

 

我想当你看到这里一定会感到困惑,为什么我们获取到了代理对象后执行的执行代理对象的方法,明明是房东的方法,怎么显示的好像是JDK动态代理类里面的invoke()方法??或许还有其他的困惑,我们将在下边一一讲解如何实现的原理。

既然想知道如何实现的,那么我们就要从底层出发,来看看,底层的Proxy代理到底帮我们生成了一个怎样的代理类。

再开始之前我希望接下来的代码与操作你是跟着我同步进行的,这样子才会更深刻有更好的理解,当然不排除你是个人脑机器模拟器

开始吧!

 

我们想要看底层如何实现的,那么我们首先就要获得代理类的class文件

下面是我写的一个获取JDKProxy动态代理所生成的代理文件的工具类

 

package cn.arebirth.jdkproxy;

import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

public class ProxyUtil {
    /**
     * @param proxyName  生成文件代理类的名字
     * @param interClass 代理类所实现的接口的class数组形式
     * @param path       写出路径
     */
    public static void writeProxyClassToHardDisk(String proxyName, Class[] interClass, String path) {
        byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, interClass);

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path + File.separator + proxyName+".class");
            out.write(bytes);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然后我们在测试类里面使用它

package cn.arebirth.jdkproxy;

public class Test {
    public static void main(String[] args) {
        //创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去
        JdkProxy jdkProxy = new JdkProxy(new Host());

        //获得代理类
        RentalHouse proxy = (RentalHouse) jdkProxy.getProxy();
        proxy.rent();
        //写入JDK动态代理生成的代理类
        ProxyUtil.writeProxyClassToHardDisk(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces(), "F:\\Tools\\DevelopmentTools");
    }
}

 

最后生成出来的代理类是一个class字节码文件,我们需要使用反编译工具来查看,我使用的是Luten(这个工具打开方式特殊,百度自行查找)

import cn.arebirth.jdkproxy.*;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy implements RentalHouse
{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final void rent() {//这个就是编译器自动为我们实现了的接口中的方法
        try {
            super.h.invoke(this, $Proxy0.m3, null); //并调用了invoke
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
//在类第一次加载的时候,给全局变量进行初始化,看看里面有没有我们稍微眼熟的东西
static { try { $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); $Proxy0.m3 = Class.forName("cn.arebirth.jdkproxy.RentalHouse").getMethod("rent", (Class<?>[])new Class[0]);//这里就是我们所实现的接口中的方法 $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]); $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]); } catch (NoSuchMethodException ex) { throw new NoSuchMethodError(ex.getMessage()); } catch (ClassNotFoundException ex2) { throw new NoClassDefFoundError(ex2.getMessage()); } } }

在看到这里的时候,我们大概能了解到,原来我们在使用我们代理类调用租房的方法的时候,它里面并没有我们写的代码而是执行了一个

 super.h.invoke(this, $Proxy0.m3, null);

那么super是谁?就是它自动继承的Proxy类

那么h是什么?就是Proxy类的InvocationHandler

 

 那么我们看这个InvocationHandler类是不是感觉有那么一丢丢的眼熟啊,回过头看,这就是我们在写JDK动态代理类的时候实现的那个接口。

!!!!!!!!!!!!!

然后它又调用了invoke(this, $Proxy0.m3, null);

第一个参数:代理类对象

第二个参数:要执行的代理类对象的方  Method对象,这个Method对象的值已经由我们的编译器帮我们构建好了,我们只需要第一个次加载这个类的时候他就会自动赋值了(static代码块)

第三个参数:方法里面的参数,当没有参数的时候就是NULL

 

 

带着你的疑惑,从头在开始好好敲一遍代码,捋一遍,相信我,你将有更大的收货!!

下面是简略画的思路图,试着用画图来画出你的思维

 

 

 

那么说完了这个JDK动态代理,我们有没有发现什么缺点??找茬时间开始

  是不是这个JDK动态代理必须要依赖接口才能实现,如果没有接口的话,那么JDK动态代理也就凉凉了对吧,!

 


 

那么好,接下来我们将说一种更加强大的动态代理方式CGLIB,它的实现原理只要我们懂了JDK动态代理,那么下面的就是小儿科啦哈哈~

 

CGLIB的代理将不再需要接口也可以生成代理类,但是它需要导包!

 

 当然,你也可以选择其他版本的JAR包!

===

开始

 

被代理类

package cn.arebirth.cglibproxy;

/**
 * 被代理类
 */
public class Host {
    public void rent() {
        System.out.println("Host:rental house");
    }
}

生成代理对象的cglib类

package cn.arebirth.cglibproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    /**
     * 获取代理类
     *
     * @return
     */
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();

        //设置被代理对象
        enhancer.setSuperclass(Host.class);
        //设置回调方法  当前对象
        enhancer.setCallback(this);

        //创建代理对象
        Object o = enhancer.create();
        return o;
    }

    /**
     * @param o           被代理对象
     * @param method      被代理对象方法
     * @param objects     被代理对象方法中的参数
     * @param methodProxy 代理类中的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("中介收取手续费");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("中介收取中介费");
        return o1;
    }
}

测试类

package cn.arebirth.cglibproxy;

public class Test {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Host proxy = (Host) cglibProxy.getProxy();//强制转换为我们的房主
        proxy.rent();
    }
}

输出结果:
  中介收取手续费
  Host:rental house
  中介收取中介费

 

我们动态代理的最大好处就是,可以在没有接口的情况下,只有一个类,我们就可以动态的代理,在程序运行的时候动态的为他创建代理类


 

 

最后让我们大概的总结下:

 

代理模式:
静态代理
动态代理:JDK动态代理 CGLIB动态代理

代理模式的三个要素
A.抽象的类或接口 完成一件怎样的事情
B 被代理对象 事情操作具体内容
C 代理对象 帮助我们完成事情的同时 可以增加其他的东西

具体的列子:我们找中介租房子
A 抽象的类或者接口 租房子
B 被代理对象 房东
C 代理对象 中介

代理模式的好处
A 房东可以安心的做自己的事情 (被代理对象可以做自己的事情)
B 我们有了问题可以直接找中介 (被代理对象变得比较安全)
C 可以增强代码的扩展性

JDK动态代理和CGLIB动态代理的使用场景

我们可以这样记一下,只要被代理类没有实现接口,我们就必须使用CGLIB动态代理

 

原文出处:https://www.cnblogs.com/arebirth/p/javaproxyschema.html

Java 动态代理与静态代理以及它能为我们做什么

Java 动态代理与静态代理以及它能为我们做什么

  相信我们在网上和平时学习和工作中或多或少都接触过 Java 的代理模式,经常听到什么静态代理、动态代理的一些名词。但我们是否真的很清楚这些呢?至少我在面试时,发现很多人并不很清楚。

  首先代理比较好理解,就是帮一个人,或者一类人做一些事情。迁移到面向对象的程序设计中,代理就是帮一个类去做一些事情,而这个代理的工具我们就称为代理类。

  通过代理的方式去做事有什么好处呢?这就好比工厂和分销商做的事情一样,工厂可以直卖一些自己的产品,分销商同样也可以卖工厂生产的产品,那么为什么还有分销商的存在呢?因为分销商可以提供一些额外的服务,或者在销售的过程中能够完成一些其他的事情,比如组合销售、根据本地情况做活动等,而这些可能是工厂不想关心或者也管不过来的。这样的功能和角色承包给代理商就会使得分工比较明晰,并且又能够提供一些额外或者定制的服务。

 

静态代理

  Java 中的代理方式可以分为静态代理和动态代理。静态代理的含义是代理类 / 对象在我们关心的程序运行前就已经确定或存在。静态代理比较好理解,我们在日常工作中也是经常用到,比如一个已经存在的接口,我们不期望去更改它,但是现在要在原逻辑上新加一些逻辑或功能,比如原接口方法调用完成后发送一个消息之类的。于是我们可以创建一个类,同样实现原接口,并且把之前存在的接口当做成员变量注入进来,调用其中的方法,并添加我们需要的功能。

  静态代理的类图如下所示,需要被代理的实现类和代理类都实现了抽象接口 AbstractInterface,而 InterfaceProxy 和 InterfaceImpl 间是聚合关系。

  

 

 

 

   来看一段示例代码,ProductAuditCallbackService 是我们已有的一个接口,出于某些原因,这个接口不能继续对外使用,我们需要定义一个新的接口并且名称还要一样(主要是方便客户理解和对应原接口),但是我们需要添加一点 “新逻辑”。因此我们可以同样实现 ProductAuditCallbackService,ProductAuditCallbackServiceProxy 就是我们的代理类,之后外部调用就可以实例化我们的代理类,调用同名方法就好了。

 

 1 public class ProductAuditCallbackServiceProxy implements ProductAuditCallbackService {
 2 
 3     @Resource
 4     private ProductAuditCallbackService productAuditCallbackService;
 5 
 6     @Override
 7     public Result<Void> auditProduct(ProductAuditRequest request, String auditStatus) {
 8         if (auditStatus == "DELETED") {
 9             return new Result<>();
10         }
11         return productAuditCallbackService.auditProduct(request, auditStatus);
12     }
13 
14 
15 ...
16 }

 

 

 

 

动态代理

  动态代理的作用和静态代理一样,主要的区别就在于需要在运行时生成代理类。在使用动态代理时,我们还需要定义一个在代理类和委托类之间的中介类,并且中介类需要实现 java.lang.reflect.InvocationHandler 接口。

 1 package java.lang.reflect;
 2 
 3 /**
 4  * {@code InvocationHandler} is the interface implemented by
 5  * the <i>invocation handler</i> of a proxy instance.
 6  *
 7  * <p>Each proxy instance has an associated invocation handler.
 8  * When a method is invoked on a proxy instance, the method
 9  * invocation is encoded and dispatched to the {@code invoke}
10  * method of its invocation handler.
11  *
12  * @author      Peter Jones
13  * @see         Proxy
14  * @since       1.3
15  */
16 public interface InvocationHandler {
17 
18     public Object invoke(Object proxy, Method method, Object[] args)
19         throws Throwable;
20 }

  

  动态代理在框架类的代码中用到的频率并不低,而且能够使我们的代码看起来更高级一些,所以何乐而不为呢? 让我们来看一些实际的例子。

  MethodInvocationHandler 是一个中介类,实现了 InvocationHandler 接口,MethodMonitor 这个类的功能就是要统计我们的委托类的对象 business 中的方法被调用的次数和耗时,由于其主要功能不是我们关注的主要内容,所以忽略其实现。

 1 public class MethodInvocationHandler implements InvocationHandler {
 2 
 3     //被代理对象
 4     private Object business;
 5 
 6     private final MethodMonitor methodMonitor;
 7 
 8     public MethodInvocationHandler(MethodMonitor methodMonitor) {
 9         this.methodMonitor = methodMonitor;
10     }
11 
12     /**
13      * 代理方法
14      */
15     @Override
16     public Object invoke(Object proxy, Method method, Object[] args)
17             throws Throwable {
18 
19         long startTime = System.currentTimeMillis();
20 
21         Object result = method.invoke(this.business, args);
22 
23         //方法调用统计
24         this.methodMonitor.methodCount(this.business.getClass().getSimpleName() + POINT + method.getName(), startTime);
25         return result;
26     }
27 
28 }

  其余示例代码及外部调用示例如下,我们的 Business 类里面拥有三个方法。MethodSampleClient 则是一个封装起来的客户端。我们不想让外部客户端感知我们的实现以及和 Business 的关系,于是我们在 MethodSampleClient 中定义了一个成员变量 proxy,当外部需要 Business 提供的一些功能时,我们通过 proxy 为其提供。Proxy.newProxyInstance () 则是我们实例化一个代理类的方式,哟,这还是个工厂模式,可以阅读一些这个方法的说明,需要传入的三个参数依次是:需要被代理的类的 ClassLoader,被代理类需要被代理的接口的集合,中介处理类的实例。

  这里 Business 我写的是一个确定的类,其实真正在实际开发工作中,我们往往定义的抽象的接口或抽象类,知道运行时才会确定到底是哪个实现类的实例,这样可能更容易理解一些:运行时确定委托类的实现类,运行时生成代理类,并调用对应的委托类的方法。

 

 1 public class Business {
 2 
 3     public void createJob() {
 4         System.out.println("test createJob");
 5     }
 6 
 7 
 8     public void processJob() {
 9         System.out.println("test processJob");
10     }
11 
12     public void closeJob() {
13         System.out.println("test closeJob");
14     }
15 
16 }
17 
18 
19 
20 public class MethodSampleClient {
21 
22     private Business business;
23 
24     @Getter
25     private Object proxy;
26 
27     private InvocationHandler invocationHandler;
28 
29 
30     public void init() {
31         this.business = new Business();
32         this.invocationHandler = new MethodInvocationHandler(new MethodMonitor());
33         this.proxy = bind(this.business, invocationHandler);
34     }
35 
36     /**
37      * 绑定对象, 直接初始化并返回代理类供客户端使用
38      */
39     public Object bind(Object business, InvocationHandler invocationHandler) {
40         return Proxy.newProxyInstance(
41                 //被代理类的ClassLoader
42                 business.getClass().getClassLoader(),
43                 //要被代理的接口,本方法返回对象会自动声称实现了这些接口
44                 business.getClass().getInterfaces(),
45                 //代理处理器对象
46                 invocationHandler);
47     }
48     
49 }    
50 
51 
52 /**
53 *  A simple client test class
54 */
55 public class Test {
56 
57     public void main(String[] args) {
58         MethodSampleClient methodSampleClient = new MethodSampleClient();
59         methodSampleClient.init();
60 
61         methodSampleClient.getProxy().createJob();
62         methodSampleClient.getProxy().processJob();
63         methodSampleClient.getProxy().closeJob();
64     }
65 
66 }

   

  为了说清楚这个过程,竟然还真的写了不少代码,看起来比较繁琐。总结一下,动态代理无非按照下面的步骤来编写代码:

  • 首先明确需要被代理的委托类。
  • 实现 InvocationHandler 接口,定义一个中介类。
  • 用 Proxy.newProxyInstance () 实例化代理类,并在客户端代码中直接使用。

  好了,大概差不多了,最重要的是能够在实际工作中有意识地去使用并体会其作用 —— 软件开发是经验驱动不是知识驱动。

 

 

 

 

  

 

Java 方法代理实例操作,静态代理、JDK动态代理、CGLIB动态代理

Java 方法代理实例操作,静态代理、JDK动态代理、CGLIB动态代理

方法的代理可以在调用方法时进行其它的相关操作,并减少代码的入侵和偶合。很多框架都用到了动态代理,并提供了减化代理操作,如:Spring 的 AOP。

静态代理

以电脑为模型,我们都知道,电脑是由 CPU、GPU、DISK 多个设备组装的,它们都是通过接口相连接。现在模拟一台电脑(Computer)通过接口设备(Device),代理(Proxy)组装不同的设备(CPU、GPU),并运行(run)。

接口设备Device

/*
 * File:Device.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:49:31
 */

package com.ichochy.proxy;

public interface Device {

    public void run();
}

设备 CPU

/*
 * File:CPU.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:52:31
 */

package com.ichochy.proxy;

public class CPU implements Device {


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

}

设备 GPU

/*
 * File:GPU.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:52:31
 */

package com.ichochy.proxy;

public class GPU implements Device {

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

}

电脑Computer

Computerstart方法代理执行接口类方法

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

public class Computer {

    private Device device;

    public Device start(){
        System.out.println("Start Computer");
        device.run();
        return device;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(Device device) {
        this.device = device;
    }
}

运行电脑

通过类Computerstart方法代理执行接口类方法

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import com.ichochy.proxy.GPU;

public class App {
    public static void main(String[] args) {
        Computer proxy = new Computer();
        proxy.setDevice(new CPU());
        proxy.start();
        proxy.setDevice(new GPU());
        proxy.start();
    }
}

运行情况

Start Computer
Game
Start Computer
Display

小结

静态代理可以代理某个方法,实现AOP操作,代理需求变更只需修改代理类,实现了解偶的效果。但不同的接口多个方法就要重复的编写代理类,来实现方法代理操作。

JDK动态代理

实现接口InvocationHandlerinvoke方法,通过ProxynewProxyInstance方法,构建代理接口实例。相比静态代理更加灵活,动态代理不同的接口和接口中的方法。

改进电脑Computer

实现接口InvocationHandlerinvoke方法,运用反射,动态执行代理方法

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

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

public class Computer implements InvocationHandler {

    private Device device;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Start...");
        //运用反射,动态执行代理方法,并返回方法执行结果
        Object result = method.invoke(device, args);
        System.out.println("End");
        return result;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(Device device) {
        this.device = device;
    }


}

动态运行电脑

通过类ProxynewProxyInstance方法构建代理接口类,实现方法的代理执行

/*
 * File:App.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/09/01 12:46:01
 */

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import com.ichochy.proxy.Device;

import java.lang.reflect.Proxy;

public class App {

    public static void main(String[] args) throws Exception {
        CPU cpu = new CPU();
        Computer computer = new Computer();
        computer.setDevice(cpu);
        //获取代理接口实例
        Device device = (Device) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), cpu.getClass().getInterfaces(), computer);
        device.run();
    }
}

运行情况

Start... 
Game
End

小结

运用反射,动态代理可以代理不同的接口的多个方法,不必修改代码。但只能用于接口方法的代理,无法实现所有类方法。

CGLIB动态代理

CGLIB库是用于生成和转换Java字节码的高级API,它允许运行时对字节码进行修改和动态生成,通过继承方式实现动态代理。

引入CGLIB

通过Maven库管理引入第三方CGLIB

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

改进电脑Computer

实现接口MethodInterceptorintercept方法,运用反射,动态执行代理方法(原父类方法)

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Computer implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Start...");
        //运用反射,动态执行代理方法,并返回方法执行结果
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("End");
        return result;
    }
}

动态运行电脑

通过类Enhancercreate方法构建代理类,实现方法的代理执行

/*
 * File:App.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/09/01 12:46:01
 */

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import net.sf.cglib.proxy.Enhancer;

public class App {

    public static void main(String[] args) throws Exception {
        Computer computer = new Computer();
        Enhancer enhancer = new Enhancer();
        //设置要代理超类
        enhancer.setSuperclass(CPU.class);
        //设置回调处理类
        enhancer.setCallback(computer);
        //构建代理类
        CPU cpu = (CPU)enhancer.create();
        cpu.run();
    }
}

运行情况

Start... 
Game
End

小结

通过CGLIB库可以很方便的实现方法的动态代理,实现AOP操作。CGLIB库构建代理类的子类,并重写代理父类的方法,通过执行子类方法实现动态代理操作。

总结

当我们要对一类方法或所有方法进行相同操作时,运用方法代理可以很好实现我们的需求,并不用去重写以前的业务方法,如:事务处理、日志监控、权限管理、异常捕捉及处理。

总结:方法代理,实现AOP操作。

相关文章

  • Java 反射实例操作 2020/08/23

  • Java 中的重写(Override)与重载(Overload) 2020/08/20

  • 免费申请 JetBrains 开源开发许可证,包含 IDEA 2020/08/19

  • Java 开发环境的搭建,开启你的编程之旅 2020/08/15

源文:https://ichochy.com/posts/20200824/

我们今天的关于静态代理、动态代理与Mybatis的理解mybatis静态代理和动态代理的分享就到这里,谢谢您的阅读,如果想了解更多关于Java 代理[1]-静态代理、JDK动态代理和Cglib动态代理的简单使用、java 代理模式-静态代理与动态代理、Java 动态代理与静态代理以及它能为我们做什么、Java 方法代理实例操作,静态代理、JDK动态代理、CGLIB动态代理的相关信息,可以在本站进行搜索。

本文标签: