Spring-8 AOP面向切面编程

我们学过面向对象编程,比如Java语言,围绕着类和对象展开。
那么在面向切面编程,切面是什么?面向切面来编程又是怎么一回事呢?

先来看一个例子:
做一个计算器,来算两个数的加减乘除,同是还需要在完成计算前后写日志;
按照需求,先这么写:

package com.csu.aop.helloworld;

public class CaculatorImpl implements Caculator{

    @Override
    public int add(int i, int j) {
        // TODO Auto-generated method stub
        sysout(*********);
        int result = i + j;
        sysout(*********);
        return result;

    }

    @Override
    public int sub(int i, int j) {
        // TODO Auto-generated method stub
        sysout(*********);
        int result = i - j;
        sysout(*********);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        // TODO Auto-generated method stub
        sysout(*********);
        int result = i * j;
        sysout(*********);
        return result;
    }

    @Override
    public int div(int i, int j) {
        // TODO Auto-generated method stub
        sysout(*****准备开始****);
        int result = i / j;
        sysout(****结束计算*****);
        return result;
    }

}

可以看到,每个计算方法中都需要在核心业务代码前后加上一串代码,看起来代码重复的相当多,而且当非业务代码加进来,代码变的庞大难维护;
现在,我们另有一个需求:让计算器只负责做运算,这个意思就是说要把写日志的任务给剥离开。
AOP可以做到,不过先用动态代理来模拟一下AOP的工作:
修改上面的代码:

package com.csu.aop.helloworld;

public class CaculatorImpl implements Caculator{

    @Override
    public int add(int i, int j) {
        // TODO Auto-generated method stub
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        // TODO Auto-generated method stub
        return i-j;
    }

    @Override
    public int mul(int i, int j) {
        // TODO Auto-generated method stub
        return i*j;
    }

    @Override
    public int div(int i, int j) {
        // TODO Auto-generated method stub
        return i/j;
    }

}

创建CalculatorLoggingProxy类:

package com.csu.aop.helloworld;

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

public class CaculatorLoggingProxy {

    //要为谁代理事务
    private Caculator target;   

    public CaculatorLoggingProxy(Caculator target)
    {
        this.target=target;
    }
    //获取代理类
    public Caculator getLoggingProxy() {
        Caculator proxy=null;
        //目标加载器,代理对象由哪个加载器负责
        ClassLoader loader=target.getClass().getClassLoader();
        //代理类是什么类型
        Class [] interfaces=new Class[]{Caculator.class};
        //当调用代理对象其中的方法时 该执行什么handler
        InvocationHandler handler = new InvocationHandler() {
        //proxy要返回的proxy,在invoke方法里不用,会造成死循环
            //method是正在被调用的方法
            //args调用方法时传进来的参数       
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // TODO Auto-generated method stub
                System.out.println("The method"+method.getName()+"begin with"+Arrays.asList(args));
                Object result=method.invoke(target, args);
                System.out.println("The method result"+method.getName()+result);        
                return result;
            }
        };
        proxy=(Caculator) Proxy.newProxyInstance(loader, interfaces, handler);
        return proxy;
    }
}

再来测试一下:

package com.csu.aop.helloworld;

public class MainAop {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Caculator target=new CaculatorImpl();
        CaculatorLoggingProxy proxy=new CaculatorLoggingProxy(target);

                // TODO Auto-generated method stub
        Caculator target=new CaculatorImpl();
        CaculatorLoggingProxy proxy=new CaculatorLoggingProxy(target);

        Caculator caculator=proxy.getLoggingProxy();
        int result=caculator.add(1, 2);
        System.out.println("result->"+result);  
    }

}

控制台打印出:
The methodaddbegin with[1, 2]
The method resultadd3
result->3

目的达到。
但是用动态代理,步骤繁杂,代码也难看懂,如果有AOP帮助,变得很简单。

AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
AOP 的好处:
每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
业务模块更简洁, 只包含核心业务代码.
这里写图片描述

切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作(验证,日志,切面里的每个方法)
目标(Target): 被通知的对象()
代理(Proxy): 向目标对象应用通知之后创建的对象()

连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

接下来就用AOP来实现:
1.加jar包(主要是AspectJ框架)
* com.springsource.net.sf.cglib-2.2.0.jar
* com.springsource.org.aopalliance-1.0.0.jar
* com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
* spring-aspects-4.0.0.RELEASE.jar
2.xml文件配置
* 2.1 在配置文件中配置自动扫描的包: <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
* 2.2 加入使 AspjectJ 注解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.创建切面类:LoggingAspect

package com.csu.aop.helloworld;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Aspect
@Component
//切面类
public class LoggingAspect {
    //拼错单词execution 也是够了
    @Before("execution(public int com.csu.aop.helloworld.Caculator.*(int, int))")

    public void beforeMethod(JoinPoint joinpoint)
    {//横切关注点
        String methodName=joinpoint.getSignature().getName();
        List<Object> args=Arrays.asList(joinpoint.getArgs());
        System.out.println(methodName+"  Method begin:"+args);
    }

}

配置切面
* 1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
* 2 声明是一个切面: 添加 @Aspect
* 3 声明通知: 即额外加入功能对应的方法.
* 3.1 前置通知: @Before(“execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))”)
* @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体.
* @Before 里面的是切入点表达式:
*
4. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.

控制台打印出:
add Method begin:[1, 2]
result->3

出了在方法执行前的前置通知,还有后置通知(@After),返回通知(@AfterReturing),异常通知(@AfterThrowing):

package com.csu.aop.helloworld;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Aspect
@Component
//切面类
public class LoggingAspect {
    /**
     * 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码. 
     * 使用 @Pointcut 来声明切入点表达式. 
     * 后面的其他通知直接使用方法名来引用当前的切入点表达式. 
     */
    @Pointcut("execution(public int com.csu.aop.helloworld.Caculator.*(..))")
    public void declareJointPointExpression(){}


    //拼错单词 也是够了
    @Before("execution(public int com.csu.aop.helloworld.Caculator.*(int, int))")
    //前置通知  
    /**
     * 在 com.csu.aop.helloworld.Caculator 接口的每一个实现类的每一个方法开始之前执行一段代码
     */
    public void beforeMethod(JoinPoint joinpoint)
    {//横切关注点
        String methodName=joinpoint.getSignature().getName();
        List<Object> args=Arrays.asList(joinpoint.getArgs());
        System.out.println(methodName+"  Method begin:"+args);
    }

    //后置通知 
    /**
     * 无论方法有没有异常都会执行
     * 取不到方法执行的结果
     */
    @After("execution(public int com.csu.aop.helloworld.Caculator.*(int, int))")
    public void afterMethod(JoinPoint joinpoint) {
        String methodName=joinpoint.getSignature().getName();
        List<Object> args=Arrays.asList(joinpoint.getArgs());
        System.out.println(methodName+"  Method end:"+args);
    }

    /**
     * 在方法法正常结束受执行的代码
     * 返回通知是可以访问到方法的返回值的!
     */
     @AfterReturning(value="declareJointPointExpression()",
            returning="result")
    public void afterReturning(JoinPoint joinpoint,Object result) {
        String methodName=joinpoint.getSignature().getName();
        List<Object> args=Arrays.asList(joinpoint.getArgs());
        System.out.println(methodName+"  Method afterreturning:"+result);       
    }

    /**
     * 在目标方法出现异常时会执行的代码.
     * 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
     */
    @AfterThrowing(value="declareJointPointExpression()",
            throwing="ex")
    public void afterThrowing(JoinPoint joinpoint,ArithmeticException ex) {
        String methodName=joinpoint.getSignature().getName();
        List<Object> args=Arrays.asList(joinpoint.getArgs());
        System.out.println(methodName+"  Method afterThrowing:"+ex);        
    }

}

切面的优先级问题:
如果同时有多个切面作用于相同的目标,那么用@Order(x)注解来解决谁先执行的问题,x越小,优先级越高:
@Order(1)
@Aspect
@Component

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值