草庐IT

day08-AOP-01

liyuelian 2023-04-16 原文

AOP

1.官方文档

AOP讲解:下载的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop

AOP APIs:下载的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop-api

2.动态代理

2.1案例说明

需求说明:

  1. 有Vehicle(交通工具接口,有一个run方法),下面有两个实现类Car,Ship

  2. 当运行Car对象的run()方法和Ship对象的run()方法时,输出如下内容,注意观察前后有统一的输出

  3. 请思考如何完成?

2.2传统方式解决

Vehicle接口:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 * 接口,定义了run方法
 */
public interface Vehicle {
    public void run();
}

Ship类,实现Vehicle接口:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 */
public class Ship implements Vehicle {

    @Override
    public void run() {
        System.out.println("交通工具开始运行了...");
        System.out.println("大轮船在水上 running...");
        System.out.println("交通工具停止运行了...");
    }
}

Car类,实现Vehicle接口:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 */
public class Car implements Vehicle {

    @Override
    public void run() {
        System.out.println("交通工具开始运行了...");
        System.out.println("小汽车在公路 running...");
        System.out.println("交通工具停止运行了...");
    }
}

TestVehicle测试类:

package com.li.proxy;

import org.testng.annotations.Test;

/**
 * @author 李
 * @version 1.0
 */
public class TestVehicle {
    @Test
    public void run() {
        Vehicle vehicle = new Car();//Vehicle vehicle = new Ship();
        vehicle.run();//动态绑定,根据实际运行类型调用run方法
    }
}

上面的方式,代码冗余,其实就是单个对象的调用,并没有很好的解决问题。

2.3动态代理方式解决

解决思路:在调用方法的时候,使用反射机制,根据方法去决定调用哪个对象方法

Vehicle接口不变:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 * 接口,定义了run方法
 */
public interface Vehicle {
    public void run();
}

Ship:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 */
public class Ship implements Vehicle {

    @Override
    public void run() {
        System.out.println("大轮船在水上 running...");
    }
}

Car:

package com.li.proxy;

/**
 * @author 李
 * @version 1.0
 */
public class Car implements Vehicle {

    @Override
    public void run() {
        System.out.println("小汽车在公路 running...");
    }
}

创建VehicleProxyProvider,该类返回一个代理对象:

package com.li.proxy;

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

/**
 * @author 李
 * @version 1.0
 * VehicleProxyProvider类可以返回一个代理对象
 */
public class VehicleProxyProvider {
    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //要求该对象的类实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }


    //编写一个方法,可以返回一个代理对象
    public Vehicle getProxy() {
        //(1)得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //(2)得到要代理的对象/被执行的对象 的接口信息,底层通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();

        //(3)创建一个调用处理对象
        /**
         *   public interface InvocationHandler {
         *      public Object invoke(Object proxy, Method method, Object[] args)
         *      throws Throwable;
         *   }
         *   invoke 方法在将来执行我们的 target_vehicle的方法时,会调用到
         */
        //如上,因为InvocationHandler是接口,不能直接实例化
        // 以匿名内部类的方式来获取 InvocationHandler 对象
        //这个对象有一个方法:invoke, 到时可以通过反射,动态调用目标对象的方法
        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke()方法,在将来执行我们的target_vehicle的方法时会调用到
             * @param proxy 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法
             * @param args 表示调用代理对象的方法时,传入方法的参数
             * @return 表示代理对象.方法(xx) 执行后的结果
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("交通工具开始运行了...");
                //这个地方的方法,就是你调用时动态传入的,可能是 run , 可能是 hi 等
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了...");
                return result;
            }
        };

        /*(4)
          public static Object newProxyInstance(ClassLoader loader,
                                                    Class<?>[] interfaces,
                                                    InvocationHandler h)
          1.Proxy.newProxyInstance() 可以返回一个代理对象
          2.ClassLoader loader :类加载器
          3.Class<?>[] interfaces:要代理的对象的接口信息
          4.InvocationHandler h:调用处理器/对象,该对象有一个非常重要的方法-invoke
         */
        //将上面的 loader, interfaces, invocationHandler构建一个 Vehicle的代理对象.
        Vehicle proxy =
                (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }

}

TestVehicle测试类:

@Test
public void proxyRun(){
    //创建Ship对象
    Vehicle vehicle = new Ship();

    //创建VehicleProxyProvider对象,并且传入要代理的对象
    VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);

    //获取代理对象,该对象可以代理执行方法
    //1.proxy 编译类型是Vehicle
    //2.proxy 运行类型是 代理类型: class com.sun.proxy.$Proxy3
    Vehicle proxy = vehicleProxyProvider.getProxy();
    System.out.println("proxy 的运行类型是="+proxy.getClass());

    proxy.run();
}

2.4动态代理机制debug

如上所示,当执行proxy.run();时,成功输出了目标信息,那么代码是怎么执行到代理对象的invoke方法上的?

1.在proxy.run();旁打上断点,点击step into,光标跳转至VehicleProxyProvider的invoke方法:

2.点击step over,光标跳转到下一行,此时method,target_vehicle代表了什么?

如上,此时的method就是我们Vehicle接口中的run方法,target_vehicle对象的实际运行类型是Ship,因为我们没有传值进去所以args为null。

3.在这行点击step into,进入invoke方法的源码,点击step over跳到return ma.invoke(obj, args);

在这行点击step into:

可以看到,底层最终执行了invoke0()方法:

这里可以发现method 就是Vehicle的run方法,该方法会通过动态绑定,根据实际运行对象的类型(在这里即Ship),调用Ship中的run方法;var2就是我们调用代理对象的方法时传入的参数,这里为null:

4.在这行再次点击step into,可以看到光标跳转到了Ship对象的run方法中,此时调用了Ship中的run方法:

5.点击step over,因为底层代码执行完毕。光标层层返回到之前的method.invoke(target_vehicle,args)语句:

6.因为Ship的run方法返回void,因此result的值为null:

7.当执行到return result;时,相当于使用代理对象执行run方法执行完毕,因此返回上一层:

至此,总体的流程就执行完毕了。

总结梳理:

1.proxy的编译类型是Vehicle,因此可以调用run方法;proxy的运行类型是class com.sun.proxy.$Proxy3,所以当执行run方法时会执行到代理对象的invoke方法

2.invoke 方法使用反射机制来调用 run()方法(注意这个 run 方法也可以是Vehicle 的其它方法),这时就可以在调用 run()方法前,进行前置处理和后置处理

3.也就是说 proxy 的 target_vehicle 运行类型只要是实现了 Vehicle 接口,就可以去调用不同的方法,是动态的,变化的,底层就是使用反射完成的。

3.动态代理练习

3.1案例说明

需求说明:

有一个SmartAnimal接口,可以完成简单的加减法,要求在执行getSum()和getSub()时,输出执行前、执行过程、执行后的日志输出(参考如下),请思考如何实现

方法执行开始-日志-方法名-getSub-参数 [10.0, 2.0]
方法内部打印 result = 8.0
方法执行正常结束-日志-方法名-getSub-结果 result= 8.0
//可能的异常信息
方法最终结束-日志-方法名-getSub//finally的输出
======================
方法执行开始-日志-方法名-getSum-参数 [10.0, 2.0]
方法内部打印 result = 12.0
方法执行正常结束-日志-方法名-getSum-结果 result= 12.0
//可能的异常信息
方法最终结束-日志-方法名-getSum//finally的输出
  1. 请使用传统方法完成
  2. 请使用动态代理方式完成,并要求考虑代理对象调用方法(底层是反射调用)时,可能出现的异常

3.2传统方式解决

SmartAnimal接口:

package com.li.aop.proxy2;

/**
 * @author 李
 * @version 1.0
 */
public interface SmartAnimal {
    //求和
    float getSum(float a, float b);

    //求差
    float getSub(float a, float b);
}

SmartCat类实现接口SmartAnimal:

package com.li.aop.proxy2;

/**
 * @author 李
 * @version 1.0
 */
public class SmartCat implements SmartAnimal {
    @Override
    public float getSum(float a, float b) {
        System.out.println("日志-方法名-getSum-参数 " + a + " " + b);
        float result = a + b;
        System.out.println("方法内部打印 result = " + result);
        System.out.println("日志-方法名-getSum-结果result= " + result);
        return result;
    }

    @Override
    public float getSub(float a, float b) {
        System.out.println("日志-方法名-getSub-参数 " + a + " " + b);
        float result = a - b;
        System.out.println("方法内部打印 result = " + result);
        System.out.println("日志-方法名-getSub-结果result= " + result);
        return result;
    }
}

AopTest测试类:

package com.li.aop.proxy2;

import org.testng.annotations.Test;

/**
 * @author 李
 * @version 1.0
 */
public class AopTest {
    @Test
    public void testSmartAnimal() {
        SmartAnimal smartAnimal = new SmartCat();
        smartAnimal.getSum(10, 2);
        System.out.println("=======================");
        smartAnimal.getSub(10, 2);
    }
}

传统方法的特点:

优点:实现简单直接

缺点:日志代码维护不方便,代码复用性差

解决思路:

  1. 使用动态代理来更好地处理日志记录问题
  2. 其他比如封装函数,或者类的继承在这里都不是特别合适

3.3动态代理方法解决

SmartAnimal接口不变。

SmartDog实现SmartAnimal接口:

package com.li.aop.proxy2;

/**
 * @author 李
 * @version 1.0
 */
public class SmartDog implements SmartAnimal {
    @Override
    public float getSum(float a, float b) {
        float result = a + b;
        System.out.println("方法内部打印 result = " + result);
        return result;
    }

    @Override
    public float getSub(float a, float b) {
        float result = a - b;
        System.out.println("方法内部打印 result = " + result);
        return result;
    }
}

MyProxyProvider类:

package com.li.aop.proxy2;

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

/**
 * @author 李
 * @version 1.0
 * 返回一个动态代理对象,可以执行被代理的对象的方法
 */
public class MyProxyProvider {

    //定义要执行的目标对象,该对象需要实现 SmartAnimal接口
    private SmartAnimal target_animal;

    //构造器
    public MyProxyProvider(SmartAnimal target_animal) {
        this.target_animal = target_animal;
    }

    //定义方法返回代理对象,该代理对象可以执行目标对象
    public SmartAnimal getProxy() {
        //(1)先得到类加载器对象
        ClassLoader classLoader = target_animal.getClass().getClassLoader();
        //(2)得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_animal.getClass().getInterfaces();
        //(3)使用匿名内部类 创建 InvocationHandler对象
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("方法执行开始-日志-方法名-" + method.getName() +
                            "-参数 " + Arrays.toString(args));//这里从AOP的角度看,就是一个横切关注点-前置通知
                    //使用反射真正调用方法
                    result = method.invoke(target_animal, args);
                    System.out.println("方法执行正常结束-日志-方法名-" + method.getName()
                            + "-结果 result = " + result);//也是一个横切关注点-返回通知
                } catch (Exception e) {
                    //如果反射出现异常,就会进入到catch块
                    System.out.println("方法执行异常-日志-方法名" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//也是一个横切关注点-异常通知
                    e.printStackTrace();
                } finally {//无论是否出现异常,最终都会执行到 finally{}
                    //也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }
                return result;
            }
        };

        //(4)创建代理对象
        SmartAnimal proxy = (SmartAnimal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

测试方法:

@Test
public void testSmartAnimal() {
    SmartAnimal smartAnimal = new SmartDog();
    
    MyProxyProvider myProxyProvider
            = new MyProxyProvider(smartAnimal);
    
    SmartAnimal proxy = myProxyProvider.getProxy();
    
    proxy.getSum(10, 2);
    System.out.println("=======================");
    proxy.getSub(10, 2);

}

3.4问题提出

在MyProxyProvider类中,我们的输出语句功能比较弱,在实际开发中,我们希望是以一个方法的形式,嵌入到真正执行的目标方法前,怎么办?

有关day08-AOP-01的更多相关文章

  1. ruby-on-rails - rails : Find tasks that were created on a certain day? - 2

    我有一个任务列表(名称、starts_at),我试图在每日View中显示它们(就像iCal)。deftodays_tasks(day)Task.find(:all,:conditions=>["starts_atbetween?and?",day.beginning,day.ending]end我不知道如何将Time.now(例如“2009-04-1210:00:00”)动态转换为一天的开始(和结束),以便进行比较。 最佳答案 deftodays_tasks(now=Time.now)Task.find(:all,:conditio

  2. 什么是0day漏洞?如何预防0day攻击? - 2

    什么是0day漏洞?0day漏洞,是指已经被发现,但是还未被公开,同时官方还没有相关补丁的漏洞;通俗的讲,就是除了黑客,没人知道他的存在,其往往具有很大的突发性、破坏性、致命性。0day漏洞之所以称为0day,正是因为其补丁永远晚于攻击。所以攻击者利用0day漏洞攻击的成功率极高,往往可以达到目的并全身而退,而防守方却一无所知,只有在漏洞公布之后,才后知后觉,却为时已晚。“后知后觉、反应迟钝”就是当前安全防护面对0day攻击的真实写照!为了方便大家理解,中科三方为大家梳理当前安全防护模式下,一个漏洞从发现到解决的三个时间节点:T0:此时漏洞即0day漏洞,是已经被发现,还未被公开,官方还没有相

  3. ruby - Rails 比较 date.end_of_day.to_datetime 和 date.to_datetime.end_of_day 返回的日期对象值时返回 false - 2

    ruby1.9.3dev(2011-09-23修订版33323)[i686-linux]轨道3.0.20最近为什么在与DateTimeonRails相关的RSpecs项目上工作我发现在给定日期以下语句发出的值date.end_of_day.to_datetime和date.to_datetime.end_of_day虽然它们表示相同的日期时间,但比较时返回false。为了确认这一点,我打开了Rails控制台并尝试了以下操作1.9.3dev:053>monday=Time.now.monday=>2013-02-2500:00:00+05301.9.3dev:054>monday.cla

  4. Ruby,从 Date.day_fraction_to_time 获取小时、秒和时间 - 2

    我找到了这个方法here.start=DateTime.nowsleep15stop=DateTime.now#minutesputs((stop-start)*24*60).to_ihours,minutes,seconds,frac=Date.day_fraction_to_time(stop-start)我有以下错误:`':privatemethod`day_fraction_to_time'calledforDate:Class(NoMethodError)我检查了/usr/lib/ruby/1.9.1/date.rb并找到了它:defday_fraction_to_time(

  5. AT24C04、AT24C08、AT24C16系列EEPROM芯片单片机读写驱动程序 - 2

    一、概述在之前的一篇博文中,记录了AT24C01、AT24C02芯片的读写驱动,先将之前的相关文章include一下:1.IIC驱动:4位数码管显示模块TM1637芯片C语言驱动程序2.AT24C01/AT24C02读写:AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序本文记录分享AT24C04、AT24C08、AT24C16芯片的单片机C语言读写驱动程序。二、芯片对比介绍型号容量bit容量byte页数字节/页器件寻址位可寻址器件数WordAddress位数/字节数备注AT24C044k5123216A2A149/1WordAddress使用P0位AT24C088k1024

  6. Ruby strftime : Day without leading zero, %e 不工作 - 2

    我正在尝试用没有前导零的日期来格式化日期使用%d它工作正常,但前导零date_time.strftime("%d/%m/%y")result:04/01/11我搜索了一下,发现我应该使用%e而不是%d,但是执行以下操作会得到一个空字符串。date_time.strftime("%e/%m/%y")result:这跟Ruby的版本有关系吗?我在Windows机器上使用v1.8.7。更重要的是,是否有另一种方法可以在没有前导零的情况下完成一天(比gsub更方便)? 最佳答案 如果你想删除月份或日期的前导零,只需在格式前添加一个减号,如下

  7. ruby-on-rails - Rails : Date. 今天 - 1.day - 2

    使用rails控制台,我只是被这个咬住了:假设今天是12月11日。Date.today-1.day#December10(nospaces)Date.today-1.day#December10(aspaceonbothsidesoftheminussign)Date.today-1.day#December11whaaaat?Date.today-5.days#Stilldecember11!有人能解释一下这是怎么回事吗?我有点担心这在代码中很容易被遗漏。关于如何对此进行编码还有其他建议吗? 最佳答案 您看到的差异是由ruby​​

  8. day1-数组part01| 704. 二分查找、27. 移除元素 - 2

    数组理论基础数组是存放在连续内存空间上的相同类型数据的集合。数组下标从0开始数组内存空间的地址是连续的c++中vector和array的区别1、vector是顺序容器,其利用连续的内存空间来存储元素,但是其内存空间大小是能够改变的。2、array是顺序容器,其也是利用连续的内存空间来存储元素,但它的内存空间是固定大小的,申请之后就无法改变。3、vector的底层是array实现的二维数组二维数组在内存的空间地址是连续的704|二分查找思路1、把整个数组一分为二;2、判断目标值在左区间还是右区间,若在左区间,则修改右区间指针的位置;若在右区间,则修改新区间的左区间位置3、重复上述过程,直到lef

  9. day44|● 完全背包● 518. 零钱兑换 II ● 377. 组合总和 Ⅳ - 2

    518.零钱兑换II1.代码classSolution{public:intchange(intamount,vector&coins){vectorf(amount+1,0);f[0]=1;for(inti=0;i2.动规五部曲1.确定dp数组和其下标含义由题目说可知求选择钱票得到总和为target的方案数,dp[j]相当于选择物品体积相加为i的方案数2.递推公式每次加入物品,都有可能到达体积j,所以在每次加上这个物品到达j时加上这个方案数f[j]+=f[j-coins[i]];3.初始化因为在for循环和dp公式中没有确切的值,肯定需要初始化,初始化第一个就可以保证后面的推导出来了,f[0

  10. ruby-on-rails - Rails 时间奇数 : "x days from now" - 2

    当用户注册到我的网站之一进行免费试用时,我将他们的帐户到期时间设置为“14.days.from_now”。然后在主页上我显示他们还剩多少天,这是我得到的:(user.trial_expires-Time.now)/86400(因为一天有86400秒,即60*60*24)有趣的是,结果超过14,因此四舍五入为15。在控制台中进行更仔细的调查后,这种情况只会在未来两天发生(如果您明白我的意思)。例如>>Time.now=>FriOct2911:09:2601002010>>future_1_day=1.day.from_now=>Sat,30Oct201011:09:27BST01:00#

随机推荐