spring AOP 代理(静态与动态)

Posted 邦☆杠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring AOP 代理(静态与动态)相关的知识,希望对你有一定的参考价值。

一、没有代理模式

缺点:

1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。

2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。

3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。

4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。

假设需实现一个计算的类Math、完成加、减、乘、除功能,如下所示:

Math类:

public class Math {
    //
    public int add(int n1,int n2){
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;
    }
    
    
    //
    public int sub(int n1,int n2){
        int result=n1-n2;
        System.out.println(n1+"-"+n2+"="+result);
        return result;
    }
    
    //
    public int mut(int n1,int n2){
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        return result;
    }
    
    //
    public int div(int n1,int n2){
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        return result;
    }
}
View Code

现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码,如下所示:

public class Math {
    //
    public int add(int n1,int n2){
        //开始时间
        long start = getTime();
        delay();
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }
    //
    public int sub(int n1,int n2){
        //开始时间
        long start = getTime();
        delay();
        int result=n1*n2;
        System.out.println(n1+"-"+n2+"="+result);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    //
    public int mut(int n1,int n2){
        //开始时间
        long start = getTime();
        delay();
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    //
    public int div(int n1,int n2){
        //开始时间
        long start = getTime();
        delay();
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }
    public static long getTime() {
        return System.currentTimeMillis();
    }
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

测试运行:

 Math math = new Math();
        int n=100;
        int m=5;
        math.add(n,m);
        math.sub(n,m);
        math.mut(n,m);
        math.div(n,m);

 

二、静态代理

 1、定义抽象主题接口。

IMath:
public interface IMath {
    int add(int n1,int n2);
    int sub(int n1,int n2);
    int mut(int n1,int n2);
    int div(int n1,int n2);
}

2、实现接口:

MathImpl
public class MathImpl implements IMath{
    //
    public int add(int n1,int n2){
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;
    }

    //
    public int sub(int n1,int n2){
        int result=n1-n2;
        System.out.println(n1+"-"+n2+"="+result);
        return result;
    }

    //
    public int mut(int n1,int n2){
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        return result;
    }

    //
    public int div(int n1,int n2){
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        return result;
    }
}
View Code

3、代理类

MathProxy
public class MathProxy implements IMath {
    IMath math = new MathImpl();

    @Override
    public int add(int n1, int n2) {
        //开始时间
        long start = getTime();
        delay();
        int result = math.add(n1, n2);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    @Override
    public int sub(int n1, int n2) {
        //开始时间
        long start = getTime();
        delay();
        int result = math.sub(n1, n2);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    @Override
    public int mut(int n1, int n2) {
        //开始时间
        long start = getTime();
        delay();
        int result = math.mut(n1, n2);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    @Override
    public int div(int n1, int n2) {
        //开始时间
        long start = getTime();
        delay();
        int result = math.div(n1, n2);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }

    public static long getTime() {
        return System.currentTimeMillis();
    }
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

测试运行:

  IMath math = new MathProxy();
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
View Code

通过静态代理,是否完全解决了上述的4个问题:

已解决:

1、解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。

2、解决了“依赖倒转(DIP)”的问题,通过引入接口。

3、解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。

未解决:

4、如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。

如果要解决上面的问题,可以使用动态代理。

三、动态代理

1、接口不变:

2、实现类接口不变

3、修改代理类,实现接口InvocationHandler

DynamicProxy

 

package com.wbg.springAOP.springdynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
 * 动态代理类
 */
public class DynamicProxy implements InvocationHandler {
    IMath math = new MathImpl();
    //目标代理对象
    Object targetObject;
    public DynamicProxy(Object target){
        this.targetObject=target;
    }
    public DynamicProxy(){

    }
    /**
     * 获得被代理后的对象
     * @param object 被代理的对象
     * @return 代理后的对象
     */
    public Object getProxyObject(Object object){
        this.targetObject=object;
        return Proxy.newProxyInstance(
                targetObject.getClass().getClassLoader(), //类加载器
                targetObject.getClass().getInterfaces(),  //获得被代理对象的所有接口
                this);  //InvocationHandler对象
        //loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载
        //interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
        //h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
    }


    /**
     * 获取时间
     * @return
     */
    public static long getTime() {
        return System.currentTimeMillis();
    }

    /**
     * 延迟
     */
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口
     * proxy 被代理后的对象
     * method 将要被执行的方法信息(反射)
     * args 执行方法时需要的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start=getTime();
        delay();
        Object result=method.invoke(targetObject,args);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }
}

 

4、测试:

方式一:

 DynamicProxy dynamicProx = new DynamicProxy(new MathImpl());
        IMath math = (IMath) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{IMath.class},dynamicProx);
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);

方式二:

  //实例化一个MathProxy代理对象
      //通过getProxyObject方法获得被代理后的对象
      IMath math=(IMath)new DynamicProxy().getProxyObject(new MathImpl());
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);

运行结果

JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下:

 使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理。

如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。

、动态代理,使用cglib实现

CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗说cglib可以在运行时动态生成字节码。

1、引用cglib,通过maven

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

 2、实现DynamicProxyCgilb类 接口MethodInterceptor

package com.wbg.springAOP.springdynamic;

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

import java.lang.reflect.Method;
import java.util.Random;

/*
 * 动态代理类
 * 实现了一个方法拦截器接口
 */
public class DynamicProxyCgilb implements MethodInterceptor {

    //被代理对象
    Object targetObject;
    //动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例
    public Object getProxyObject(Object object){
        this.targetObject = object;
        //增强器,动态代码生成器
        Enhancer enhancer=new Enhancer();
        //回调方法
        enhancer.setCallback(this);
        //设置生成类的父类类型
        enhancer.setSuperclass(targetObject.getClass());
        //动态生成字节码并返回代理对象
        return enhancer.create();
    }
    // 拦截方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long start=getTime();
        delay();
        Object result=methodProxy.invoke(targetObject,objects);
        System.out.println("共用时:" + (getTime() - start));
        return result;
    }


    /**
     * 获取时间
     * @return
     */
    public static long getTime() {
        return System.currentTimeMillis();
    }

    /**
     * 延迟
     */
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

3、测试

  MathImpl math = (MathImpl) new DynamicProxyCgilb().getProxyObject(new MathImpl());
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
View Code

 使用cglib可以实现动态代理,即使被代理的类没有实现接口,但被代理的类必须不是final类。demo:https://github.com/weibanggang/springaopstaticanddynamic.git

 

 

以上是关于spring AOP 代理(静态与动态)的主要内容,如果未能解决你的问题,请参考以下文章

spring AOP 代理(静态与动态)

Spring_AOP的实现机制-动态代理

代理设计模式之静态代理与动态代理(超..)详解

AOP静态代理解析1-标签解析

Spring之静态/动态代理模式

Spring AOP 实现原理与 CGLIB 应用