zoukankan      html  css  js  c++  java
  • spring AOP 代理(静态与动态+使用cglib实现)

    一、没有代理模式

    缺点:

    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

     
  • 相关阅读:
    天平称重【递归解法】
    天平称重【三进制巧解】
    天平称重【暴力解】
    奇怪的捐赠
    日期问题
    承压计算
    python学习(3)关于交互输入及字符串拼接
    python学习(2)关于字符编码
    python学习(1)python的基本概念
    Spring整合kafka消费者和生产者&redis的步骤
  • 原文地址:https://www.cnblogs.com/weibanggang/p/10133965.html
Copyright © 2011-2022 走看看