码迷,mamicode.com
首页 > 编程语言 > 详细

8.Spring系列之AOP

时间:2018-04-07 17:33:07      阅读:220      评论:0      收藏:0      [点我收藏+]

标签:规范   color   getc   spring   方法调用   pos   4.0   基本   null   

一、什么是AOP?


 AOP是面向切面编程(Aspect-Oriented Programming),它是一种新的方法论,是对传统的面向对象编程的一种补充,更具体的说是在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

引用知乎用户的描述:地址https://www.zhihu.com/question/24863332/answer/48376158
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。
从技术上来说,AOP基本上是通过代理机制实现的。

 

二、需求


从AOP角度来看,我们开发过程中有哪些现有的需求可以改造成以Spring AOP来实现?以下举个简单例子:

首先,定义一个计算器接口

public interface Calculator {

    // 加法接口
    int add(int x,int y);
    
    // 减法接口 
    int sub(int x,int y);
}

接着,定义一个计算器实现接口,并在其中加入操作日志

@Service
public class CalculatorImpl implements Calculator{

    @Override
    public int add(int x, int y) {
        System.out.println(String.format("接口接收参数,x=%s,y=%s", x,y));
        int z = x + y;
        System.out.println(String.format("接口执行结果,z=%s", z));
        return z;
    }

    @Override
    public int sub(int x, int y) {
        System.out.println(String.format("接口接收参数,x=%s,y=%s", x,y));
        int z = x - y;
        System.out.println(String.format("接口执行结果,z=%s", z));
        return z;
    }

}

然后,在IOC容器上添加注解扫描包

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <!-- 开启注解扫描包 -->
    <context:component-scan base-package="com.spring"></context:component-scan>
</beans>

最后,写个测试方法

public class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator cal = ctx.getBean(Calculator.class);
        cal.add(20, 10);
        cal.sub(20, 10);
        /**
         * 执行结果:
         * 接口接收参数,x=20,y=10
         * 接口执行结果,z=30
         * 接口接收参数,x=20,y=10
         * 接口执行结果,z=10
         */
    }
}

我们在每个接口执行真正的业务之前都添加了日志输出,在真正的业务执行之后也添加了日志输出,看着很简单,但如果一个接口内几百个乃至上千个接口呢,那么就得写好多日志;再者,如果你拼命的把日志写好了,发现里面有个错别字或者不符合规范,又

得重新改几百个乃至几千个日志输出的信息。

解决方案:

1.使用java动态代理来完成这个事情

2.使用Spring的AOP面向切面编程

 

三、动态代理


 首先,我们先将service上的打印日志输出注释掉,引入动态代理类:

public class CalculatorProxy {
    
    // 要代理的对象(注意:代理的是接口)
    private Calculator target;
    
    // 初始化
    public CalculatorProxy(Calculator target) {
        super();
        this.target = target;
    }

    // 返回代理对象
    public Calculator getLoggingProxy(){
        Calculator proxy = null;
        
        ClassLoader loader = target.getClass().getClassLoader();
        Class[] interfaces = new Class[]{Calculator.class};
        InvocationHandler handler = new InvocationHandler() {
            /**
             * proxy: 代理对象。 一般不使用该对象
             * method: 正在被调用的方法
             * args: 调用方法传入的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
                String methodName = method.getName();
                //打印日志
                System.out.println(String.format("接口接收参数,x=%s,y=%s", Arrays.asList(args).get(0),Arrays.asList(args).get(1)));
                //调用目标方法
                Object result = null;
                result = method.invoke(target, args);
                //打印日志
                int res = 0;
                if("add".equals(methodName)) {
                    res = Integer.parseInt(Arrays.asList(args).get(0).toString()) 
                        + Integer.parseInt(Arrays.asList(args).get(1).toString());
                }else {
                    res = Integer.parseInt(Arrays.asList(args).get(0).toString()) 
                        - Integer.parseInt(Arrays.asList(args).get(1).toString());
                }
                System.out.println(String.format("接口执行结果,z=%s", res));
                return result;
            }
        };
        
        /**
         * loader: 代理对象使用的类加载器。 
         * interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法. 
         * h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
         */
        proxy = (Calculator) Proxy.newProxyInstance(loader, interfaces, handler);
        return proxy;
    }
}

测试动态代理日志输出:

public class Main {

    public static void main(String[] args) {
        // 被代理对象,是一个接口实现类,即哪个实现类被代理
        Calculator calculator = new CalculatorImpl();
        // 返回代理对象
        calculator = new CalculatorProxy(calculator).getLoggingProxy();
        calculator.add(20, 10);
        calculator.sub(20, 10);
        /**
         * 执行结果:
         * 接口接收参数,x=20,y=10
         * 接口执行结果,z=30
         * 接口接收参数,x=20,y=10
         * 接口执行结果,z=10
         */
    }
}

 

四、AOP


 我们必须要清楚的几个概念:

①.切面(Aspect):  横切关注点被模块化的特殊对象,图示如下:
技术分享图片技术分享图片
 
即前置日志、后置日志两个需求是横切关注点,而抽取出横切关注点形成的就是切面
②.通知(Advice):切面必须要完成的工作,比如日志切面要完成的工作是日志,所以通知在这里就是日志
③.目标(Target):被通知的对象,即真正的业务方法,比如我们在calculator.add方法执行前添加日志,calculator.add方法就是目标
④.代理(Proxy):向目标对象应用通知之后创建的对象
技术分享图片
⑤.连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等,这就是连接点。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
例如 calculator.add() 方法执行前的连接点,执行点为 calculator.add();方位为该方法执行前的位置
⑥.切点(pointcut):每个类都拥有多个连接点:例如 calculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。

 

8.Spring系列之AOP

标签:规范   color   getc   spring   方法调用   pos   4.0   基本   null   

原文地址:https://www.cnblogs.com/Json1208/p/8733652.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!