10 - Spring AOP介绍与使用1 - 概念

1、AOP的概念

先引入一个例子:计算器

接口:Calculator

package com.zhoulz.service;

public interface Calculator {

    public Integer add(Integer i,Integer j) throws NoSuchMethodException;
    public Integer sub(Integer i,Integer j) throws NoSuchMethodException;
    public Integer mul(Integer i,Integer j) throws NoSuchMethodException;
    public Integer div(Integer i,Integer j) throws NoSuchMethodException;

    //运行时,会报“NoSuchMethodException”异常,怎么办?
    //:将上面的int类型全部改为 Integer
}

实现子类1:MyCalculator

package com.zhoulz.service;

import com.zhoulz.util.LogUtil;

import java.lang.reflect.Method;

public class MyCalculator implements Calculator {

    public Integer add(Integer i, Integer j) throws NoSuchMethodException {
        //现在,想添加日志的输出功能,怎么做?
        //最简单的做法:在每行代码里面都做一个最基本的输出 —— 但是这样做效率太低了
        //System.out.prIntegerln("add方法开始执行,参数是:" + i + "----" + j );
        //改进: —— 定义一个LogUtil类(包含start()方法和stop()方法)

        //通过反射
        /*Method add = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
        //然后add传入到下面的方法中 —— 下面的都同理
        LogUtil.start(add,i,j);*/
        Integer result = i + j;
        //System.out.prIntegerln("add方法执行结束,结果是:" + result);
        //LogUtil.stop(add,result);
        return result;
    }

    public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
        //System.out.prIntegerln("sub方法开始执行,参数是:" + i + "----" + j );
        /*Method sub = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);
        LogUtil.start(sub,i,j);*/
        Integer result = i - j;
        //System.out.prIntegerln("sub方法执行结束,结果是:" + result);
        //LogUtil.stop(sub,result);
        return result;
    }

    public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
        //System.out.prIntegerln("mul方法开始执行,参数是:" + i + "----" + j );
        //Method mul = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);
        //LogUtil.start(mul,i,j);
        Integer result = i * j;
        //System.out.prIntegerln("mul方法执行结束,结果是:" + result);
        //LogUtil.stop(mul,result);
        return result;
    }

    public Integer div(Integer i, Integer j) throws NoSuchMethodException {
        //System.out.prIntegerln("div方法开始执行,参数是:" + i + "----" + j );
        //这个地方有个异常,直接抛出去,见Calculator类中
        //Method div = MyCalculator.class.getMethod("div", Integer.class, Integer.class);
        //LogUtil.start(div,i,j);
        Integer result = i / j;
        //System.out.prIntegerln("div方法执行结束,结果是:" + result);
        //LogUtil.stop(div,result);
        return result;
    }

    /**
     * 上面,为了解决代码重复的问题,采取了一些措施(往上抽象),如:创建LogUtil类、引入了Method方法(获取具体的方法名称,这个必须要通过反射来做) 。。。
     * 但是发现,此时又多了一些重复的代码,如:Method add = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
     * 怎么办?继续往上抽象?但是可能又会引入一些重复的代码?又怎么办?
     * 即,不管怎么写,都(可能)变得很麻烦,怎么办? : 动态代理
     * 具体操作(以测试类为例):可以换一个更加简单的写法
     *  :在MyTest类中,给MyCalculator()加一个对应的代理类/对象,由这个代理类来完成具体的日志的添加功能。
     * (直接在测试类中写不方便,需要先写一个单独的(抽象的)类作为代理类,见包proxy)
     */
}

实现子类2:SecondCalculator —— 只是定义了,用来说明,没做具体实现。

package com.zhoulz.service;

//Calculator的第二个实现子类:SecondCalculator (MyCalculator是定义的第一个实现子类)
public class SecondCalculator implements Calculator {
    @Override
    public Integer add(Integer i, Integer j) throws NoSuchMethodException {
        return null;
    }

    @Override
    public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
        return null;
    }

    @Override
    public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
        return null;
    }

    @Override
    public Integer div(Integer i, Integer j) throws NoSuchMethodException {
        return null;
    }
}

为了简化代码,进行了封装,创建了 LogUtil 类:

package com.zhoulz.util;

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

public class LogUtil {
    //public void start(int i, int j){ }//需要传参,但是这样就只能传两个参数了,改进见下
    //再引入参数:Method method —— 可以获取到方法的名字 ,要结合反射,见MyCalculator类
    public static void start(Method method,Object ... objects){ // "..." 代表可变参数
        //System.out.println("方法开始执行,参数是:" + i + "----" + j );
        System.out.println(method.getName()+"方法开始执行,参数是:" + Arrays.asList(objects));
    }

    //为什么加static?? 通过“LogUtil.”可直接调用
    public static void stop(Method method,Object ... objects){
        //System.out.println("sub方法执行结束,结果是:" + Arrays.asList(objects));
        System.out.println(method.getName()+"方法执行结束,结果是:" + Arrays.asList(objects));
    }

    public static void logException(Method method,Exception e){
        System.out.println(method.getName()+"方法抛出异常:"+e.getMessage());
    }

    public static void logFinally(Method method){
        System.out.println(method.getName()+"方法执行结束。。。。over");
    }
}

再次进行简化,使用(动态)代理的方式,创建了 CalculatorProxy 类:

package com.zhoulz.proxy;

import com.zhoulz.service.Calculator;
import com.zhoulz.util.LogUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
 * 动态代理:
 * 必须要有接口,如果没有接口,不能使用,这种方式是用jdk提供的reflect包下的类
 * 但是在生产环境中,我们不能保证每个类都有实现的接口,所以有第二种方式:cglib
 *
 * cglib在实现的时候,有没有接口都无所谓
 *
 * 在spring中使用了两种动态代理的方式,一种是jdk提供的(刚刚完成的),另外一种就是cglib
 *
 * 其实下面写死了,也可以不写死,如:
 * public static Object getCalculator(final Object object){} —— 这样就可以用不同的接口和实现类了(不然只能换实现子类)
 * */
public class CalculatorProxy {

    //为的是获取Calculator对象
    public static Calculator getCalculator(final Calculator calculator){ //但是要告诉它生成一个什么样的代理对象
                                                                   //所以,此时里面一定要写好/传入一个父类
        //ClassLoader是一个类加载器
        //因为是要生成上面 Calculator calculator 的代理类,所以此时你在传递值的时候应该用当前这个类的加载器,写法见下 (获取的时候还是通过反射)
        //即第1点:先获取被代理对象的 类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();

        //第2点:获取被代理对象的 所有接口(因为要求传入的是class的一个数组)
        Class<?>[] interfaces = calculator.getClass().getInterfaces();

        //第3点:InvocationHandler也是一个接口,里面就提供了一个invoke(Object var1, Method var2, Object[] var3)方法
        //对于这样的接口,只有一个对应的方法,那么直接写一个匿名的内部类就行了(为什么???)
        //(InvocationHandler:用来执行被代理类需要执行的方法!!!)
        InvocationHandler handler = new InvocationHandler() {  //然后里面的方法会自动实现,
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //System.out.println("invoke方法被执行了");//验证该方法是否被执行?执行了
                Object result = null; //因为result最后要做返回,所以这里给提出来

                try {
                    //验证成功后,加入对日志的处理(输出),参考原先的LogUtil:
                    //System.out.println(method.getName()+"方法开始执行,参数列表是:"+ Arrays.asList(args));
                    //或者在代理中,把之前写的LogUtil用过来 —— 用于日志的输出,下面同理
                    LogUtil.start(method,args);

                    //开始调用被代理类的方法 : method.invoke(),注意,invoke()里面是有参数的
                   /* Object*/ result = method.invoke(calculator,args);//会报错,必须把上面的Calculator calculator用final修饰,
                    //因为此时,你是在一个匿名内部类里面需要去调用外部方法的参数,所以此时必须要把当前这个参数修饰成final。
                    //不加final,就没办法调用(为什么???匿名内部类的特性)

                    //System.out.println(method.getName()+"方法执行完成,结果是:"+result);
                    LogUtil.stop(method,args);
                }catch (Exception e){
                    //System.out.println(method.getName()+"方法抛出异常:"+e.getMessage());
                    LogUtil.logException(method,e);
                    e.printStackTrace();
                }finally {
                    //System.out.println(method.getName()+"方法执行结束。。。。over");
                    LogUtil.logFinally(method);
                }
                //另外,上面 Object result = 还有可能出现异常,比如运算中,出现1除以0,所以要进行try-catch处理
                return result;
            }
        };
        //然后就可以进行下面生成代理的的操作了

        //然后要生成一个代理类,用Proxy。要传入的参数如下:
        //Proxy.newProxyInstance(loader,interfaces,InvocationHandler(起名叫handler即可))这些参数都还没有,先创建实例对象,见上面
        Object o = Proxy.newProxyInstance(loader, interfaces, handler);
        return (Calculator)o;  //该方法getCalculator()的返回值是Calculator

        //最后,就可以用了,见测试类
    }
}

测试类:

import com.zhoulz.proxy.CalculatorProxy;
import com.zhoulz.service.Calculator;
import com.zhoulz.service.MyCalculator;
import com.zhoulz.service.SecondCalculator;
import org.junit.Test;

public class MyTest {

    @Test   //使用junit单元测试,需要添加junit依赖
    public void test01() throws NoSuchMethodException {
        /*MyCalculator myCalculator = new MyCalculator();
        System.out.println(myCalculator.add(10, 20));
        System.out.println(myCalculator.div(10,2));*/
        //注释掉上面,换由代理对象来完成:
        //代理 -测试 :新建一个测试test02()
    }

    @Test
    public void test02() throws NoSuchMethodException {
        //使用代理之前,记得在MyCalculator中注销之前LogUtil的设计内容
        //使用代理:传入参数为 Calculator对象,但是 Calculator为接口,不能new,所以要用它的实现类MyCalculator
        //CalculatorProxy.getCalculator(new Calculator())
        Calculator calculator = CalculatorProxy.getCalculator(new MyCalculator());
        //Calculator calculator = CalculatorProxy.getCalculator(new SecondCalculator());
        //哪里体现动态了?
        //:MyCalculator是Calculator的一个实现子类,如果有其他的实现子类(如SecondCalculator),
        // 这里直接换成SecondCalculator即可,其他的都不用动
        calculator.add(1,1);
        calculator.sub(1,1);
        calculator.mul(1,1);
        calculator.div(1,0);
        System.out.println(calculator.getClass());//class com.sun.proxy.$Proxy7
        // 结果表明:calculator的类并不是 MyCalculator,
        // $Proxy7 是实际的代理对象,是在运行过程中动态生成的
        //CalculatorProxy 并不是代理对象
    }
}

附 pom 依赖: —— junit 的依赖

<dependencies>
        
        <!--添加junit的依赖-->
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值