静态代理,JDK动态代理,Cglib动态代理的写法

使用代理模式可以在不改变被代理类的情况下,实现对被代理类的进行增强。
例如:在被代理类中有一个div()方法,方法中只是计算除法,然而想要对其进行增强,如添加异常捕获等。

class CalculateImp {
    
    
    public void div(int x, int y) {
    
    
        System.out.println("x/y="+(x/y));
    }
}

静态代理

通常静态代理类与被代理类要实现统一的业务接口。

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        CalculateImp calculateImp = new CalculateImp();
        AgencyCalculate calculate = new AgencyCalculate(calculateImp); 
        calculate.div(1,0);
    }
}
class CalculateImp implements Calculate{
    
    

    @Override
    public void div(int x, int y) {
    
    
        System.out.println("x/y="+(x/y));
    }
}
interface Calculate{
    
    
    void div(int x,int y);
}
class AgencyCalculate implements Calculate{
    
    

    private Calculate calculate;

    public AgencyCalculate(Calculate calculate) {
    
    
        this.calculate=calculate;
    }

    @Override
    public void div(int x, int y) {
    
    
        try{
    
    
            this.before();
            calculate.div(x,y);
            this.after();
        }
        catch (Exception e){
    
    
            System.out.println("出现错误");
        }
    }
    public void before(){
    
    
        System.out.println("前置增强");
    }
    public void after(){
    
    
        System.out.println("后置增强");
    }
}

效果:
在这里插入图片描述

这种方式是在静态代理类中虽然传递的是接口类型的对象,只要实现了此接口的对象都可以作为被代理类对象。但没有实现该接口的对象却不能通过这个代理类进行代理。为此还要再为其写一个静态代理类。那如果有多个互不相关的类需要代理增强呢?而使用动态代理类就可以以为每个类自动的生成一个代理类并加载到内存中直接使用,这样使得我们不用去自己编写代理类,只需要清楚被代理的对象是谁,进行哪些增强逻辑即可。

JDK动态代理

动态代理最为核心的就是两部分:
1、哪个类需要被代理?
使用Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)去指定具体哪个类。这个方法中有三个参数:

  • ClassLoader :被代理类的加载器,通过类的加载器可以将将生成的代理类加载到内存。类的加载器可以通过被代理类的反射获取。
  • Class<?>[] interfaces:被代理类的所有实现接口,可以使得生成的代理对象也实现了这些接口。所有实现接口可以通过被代理类的反射获取。
  • InvocationHandler:这是一个接口,需要传递它的实现类。类中重写了invoke()方法,这个方法就是具体的增强逻辑。这个接口的实现就是下面要考虑的第二个问题

2、具体的增强逻辑?
通过实现InvocationHandler接口,然后重写invoke(Object proxy, Method method, Object[] args)方法。只要生成的代理类对象调用方法就会被这个invoke()方法拦截,在这个方法中进行逻辑增强,然后调用被代理类的方法。这个方法中与三个参数:

  • proxy:自动的生成的代理对象
  • method:被代理的方法
  • args:向被代理类方法中传递的参数数组

然后将生成的实现InvocationHandler接口的对象传递到上面newProxyInstance()方法第三个实例中。

注意:这种通过JDK获取动态代理对象的方式必须要实现接口。
下面我将使用JDK动态代理的方式代理上面的Calculate类,实现相同的效果。

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
       // 被代理对象
        CalculateImp calculateImp = new CalculateImp();
	
        ProxyFactory proxyFactory = new ProxyFactory();
       
       // 通过这个类生成代理对象
        proxyFactory.setObject(calculateImp);
		// 返回的代理类类型不确定,但可以代理类一定也实现了Calculate接口,利用多态引用
        Calculate c1 = (Calculate) proxyFactory.getProxy();
		// 调用方法,这是就会被生成的代理类对象的invoke方法拦截,然后实现增强。思考:上面为什么不使用Object或其他类接收
        c1.div(1,1);
    }
}

class CalculateImp implements Calculate{
    
    
    @Override
    public void div(int x, int y) {
    
    
        System.out.println("x/y="+(x/y));
    }
}

// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
    
    
    void div(int x,int y);
}
class ProxyFactory{
    
    
    // 通过set的方式获取被代理对象 ,使用Object可以生成任意对象代理
    private Object object;

    public void setObject(Object object) {
    
    
        this.object = object;
    }
    
    // 抽象出;两个增强的方法
    private void before(){
    
    
        System.out.println("前置增强");
    }
    private void after(){
    
    
        System.out.println("后置增强");
    }
    Object getProxy(){
    
    
        InvocationHandler invocationHandler = new InvocationHandler() {
    
      // 使用匿名内部类的方式直接实现接口,也可以使用外部类单独实现
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                before();
                try{
    
    
                    // 调用被代理对象的方法 ,代理对象调用哪个方法就会被invoke拦截,然后在这里调用被代理类的该方法
                    method.invoke(object,args);
                }
                catch (Exception e){
    
    
                    System.out.println("出现异常");
                }
                after();
               return null;  // 若代理类不为 void ,可以在这里返回最终的结果
            }
        };
        // 被代理类的类加载器,被代理类实现的接口,代理逻辑
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 	
        							  object.getClass().getInterfaces(),
        							  invocationHandler);
    }

}

使用动态代理类的好处就是它不用单独为某个类编写代理类,使用一个动态类就可以实现任意类的代理。若代理逻辑不同,可以将代码中ProxyFactory类的InvocationHandler接口对象作为属性暴露出来。

上面是演示使用动态代理类可以达到静态代理类相同的效果,下面将演示动态代理类可以自动生成不同的类的代理对象。例如:新增加一个House类,与Calculate类有相同的代理逻辑。只需要在上面的代理基础上增加:
在这里插入图片描述

效果:
在这里插入图片描述
使用JDK动态代理是运行时自动生成代理类,此代理类是在内存中真实存在的,但名字不固定,实现了被代理类所有的接口,然后JDK底层生成后,就会通过制定的类加载器自动的放入内存中。在使用时,只需要从内存中获取即可,虽然不知道类名,但此代理类实现了被代理类的所有接口,利用多态引用的方式即可。

思考:动态生成的代理类类名是什么?
生成的代理类对象通过反射获取类的全类名:代理类对象.getClass().getName() ,最终的格式是包名.$Proxy序号,如:
在这里插入图片描述

Cglib动态代理

Cglib不是JDK自带的代理方式,要想使用它需要先引入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

使用Cglib动态代理的方式,是使用继承的方式,因此,被代理类可不必实现接口,但被代理类一定不能设置为final。因为一旦加上Final底层就不能够继承被代理类,导致代理失败。

使用Cblib还是要考虑两个问题:
1、哪个类被代理
需要创建一个Enhancer对象,然后在调用setSuperclass(Class superclass)方法。方法的参数就是被代理类

2、具体的增强逻辑?
需要实现MethodInterceptor接口,重写intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)方法,该方法有4个参数

  • Object o:生成的代理类对象,代理类是自动生成且类名不固定
  • Method method:被代理类的方法
  • Object[] objects:方法的参数列表
    以上三个分别对应着JDK上InvokerHander实现类的invoke()方法的三个参数
  • MethodProxy methodProxy:底层进行动态创建代理类的对象

下面我将使用Cglib动态代理的方式代理上面的Calculate类,实现相同的效果。

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        //被代理类的对象
        CalculateImp calculateImp = new CalculateImp();
        // 生成代理类的对象
        ProxyFactory proxyFactory = new ProxyFactory();

        proxyFactory.setObject(calculateImp);

        // 同样生成的类不固定名,但这个类继承了被代理类,所以利用多态即可引用即可
        CalculateImp proxy = (CalculateImp) proxyFactory.getProxy();

        // 代理类调用此方法,会转到enhancer指定的回调对象的invoke方法,这个方法就是写代理逻辑的方法
        proxy.div(1,2);
    }
}
// 注意 一定不能为final,否则生成的代理类无法继承。至于接口,与Cglib动态代理无关,这里特意去掉接口
class CalculateImp {
    
    
 
    public void div(int x, int y) {
    
    
        System.out.println("x/y="+(x/y));
    }
}

// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
    
    
   public void div(int x,int y);
}

class ProxyFactory implements MethodInterceptor {
    
    
    private Object object;

    public void setObject(Object object) {
    
    
        this.object = object;
    }

    // 抽象出;两个增强的方法
    private void before(){
    
    
        System.out.println("前置增强");
    }
    private void after(){
    
    
        System.out.println("后置增强");
    }

    // 具体增强的逻辑
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        this.before();
        // 通过反射调用代理类的方法
        method.invoke(this.object,objects);
        System.out.println(methodProxy.getClass().getName());
        this.after();
        return null;
    }
    public Object getProxy(){
    
    

        //Enhancer :增强者
        Enhancer enhancer = new Enhancer();
        // 设置要代理哪个类,因为生成的代理类是继承被代理类,所以这个接口参数名为superclass
        enhancer.setSuperclass(CalculateImp.class);

        //设置回调 就是实现了MethodInterceptor接口的实现类对象,由于这里将enhancer写在了这类内部,所以this就可
        enhancer.setCallback(this);
        
        //最后返回生成的代理类对象
        return enhancer.create();
    }
}

思考:Cglib动态生成的代理类类名是什么?
生成的代理类对象通过反射获取类的全类名:代理类对象.getClass().getName() ,最终的格式是被代理类的全类名$$EnhancerByCGLIB$$编号,如:
在这里插入图片描述

小结

使用总结
例如:对类中的方法进行增强代理,在代理类中补充方法的执行时间。

使用JDK动态代理的方式

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
       	
        ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
      
       	业务接口 proxy = (业务接口) proxyFactory.getProxy();
        proxy.业务方法;
    }
}
class ProxyFactory{
    
    
    private Object object;

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

    private InvocationHandler getInvocationHandler(){
    
    
        InvocationHandler invocationHandler = new InvocationHandler() {
    
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                long before = System.currentTimeMillis();
                method.invoke(object,args);
                long after = System.currentTimeMillis();
                System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
                return null;
            }
        };
        return invocationHandler;
    }
    public Object getProxy() {
    
    
       return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
       								 object.getClass().getInterfaces(),
       								 getInvocationHandler());
    }
}

利用Cglib动态代理方式

public class Application {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        
        ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
        被代理类 proxy = (被代理类) proxyFactory.getProxy();
      	proxy.业务方法;
    }
}
class ProxyFactory implements MethodInterceptor {
    
    
    private Object object;

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


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        long before = System.currentTimeMillis();
        method.invoke(object,objects);
        long after = System.currentTimeMillis();
        System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
        return null;
    }
    public Object getProxy(){
    
    

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Test.class);
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

Cglib动态代理相较于JDK动态代理的优点

  • Cglib的性能更高
  • Cglib无需被代理类实现接口,代码入侵更少

Cglib动态代理相较于JDK动态代理的缺点

  • Cglib动态需要引依赖,而JDK动态代理原生支持,可随着JDK版本的升级进行平滑升级。而Cglib可能还要引入新版本Jar包
  • 被代理类不能被final修饰

JDK动态代理利用的Java反射机制,被代理类必须实现业务接口;Cglib是基于ASM机制实现,被代理类不能被final修饰。使用动态代理可以自动为被代理类生成代理类并将代理类对象加载到内存。

Supongo que te gusta

Origin blog.csdn.net/m0_52889702/article/details/128941930
Recomendado
Clasificación