Spring 笔记一:AOP基础概念与简单实现

1.AOP的概念:

在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想(范式)就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。

这里我们重点区分的一下两个流行的框架:Spring AOP和AspectJ。
两者最大区别在于—Spring AOP的运行时增强,而AspectJ是编译时增强。曾经以为AspectJ是Spring AOP一部分,是因为Spring AOP使用了AspectJ的Annotation。
Spring默认不支持@Aspect风格的切面声明,通过如下配置开启@Aspect支持:

<aop:aspectj-autoproxy/>

下面我们举的例子的AOP都是AspectJ风格的aop。

术语

切面切点和通知的集合,通知和切点共同定义了切面的功能
通知(advice)描述了切面所要完成的工作以及何时需要执行该工作。
连接点描述了切面所要完成的工作以及何处需要执行该工作。
切点定义通知被应用的位置(在哪些连接点)。
织入将切面应用到目标对象来创建的代理对象过程。

spring aop通知(advice)分成五类:

  1. 前置通知Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
  2. 正常返回通知After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
  3. 异常返回通知After throwing advice:在连接点抛出异常后执行。
  4. 后置通知After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
  5. 环绕通知Around advice:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。

五种通知的执行顺序

  • 在方法执行正常时:

    1. 环绕通知@Around
    2. 前置通知@Before
    3. 方法执行
    4. 环绕通知@Around
    5. 后置通知@After
    6. 正常返回通知@AfterReturning
  • 在方法执行抛出异常时:

    这个时候顺序会根据环绕通知的不同而发生变化:

    • 环绕通知捕获异常并不抛出:
      1. 环绕通知@Around
      2. 前置通知@Before
      3. 方法执行
      4. 环绕通知@Around
      5. 后置通知@After
      6. 正常返回通知@AfterReturning
    • 环绕通知捕获异常并抛出:
      1. 环绕通知@Around
      2. 前置通知@Before
      3. 方法执行
      4. 环绕通知@Around
      5. 后置通知@After
      6. 异常返回通知@AfterThrowing

2.简单举例:

1.导入jar包。aspectj-weaver…
2.在xml中加入命名空间 xmlns:aop=”http://www.springframework.org/schema/aop”
3.基于注解的方式

① 在配置文件中加入

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

在本例中,就是为CalculateImpl生成代理
② 把横切关注点的代码抽象到切面类中
Ⅰ. 切面是IOC容器中的Bean,即加入@Component注解
Ⅱ. 加入@Aspect注解
③ 在类中声明各种通知
Ⅰ. 声明一个方法
Ⅱ . 在方法前加入@ Before,After,AfterReturning,AfterThrowing,Around 注解
Ⅲ. 用AspectJ表达式作为注解值
④. 如果访问方法的细节(如函数名,参数等),在通知方法中加入类型为JoinPoint的参数

我们可以把切面看成是我们抽象出来的一个类(比如日志类),这个类包含的方法就是我们要插入到业务逻辑中的代码段;切点本身是一个空的方法体,要切入的位置信息用@Pointcut标识出来,见下例。

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.spring.aop.impl"></context:component-scan>

    <!--自动为匹配的类生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
package com.spring.aop.impl;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

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

/**
 * Created by kaixin on 2018/8/23.
 */

@Component
@Aspect
public class LogAspect {

    @Pointcut( "execution(* com.spring.aop.impl..*.*(int ,int ) )")
    public void init(){

    }

    @Before( value = "init()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodname=joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());

        System.out.println("计算方法"+methodname+ " 计算参数 "+args );
    }

    @After( value = "init()")
    public void after() {
        System.out.println("计算后执行...");
    }
}
public interface Calculate {
    int add(int i, int j);
    int sub(int i, int j);
    int multiply(int i, int j);
    int divide(int i, int j);
}
import org.springframework.stereotype.Component;

/**
 * Created by kaixin on 2018/8/23.
 */
@Component
public class CalculateImpl implements Calculate {
    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

    @Override
    public int multiply(int i, int j) {
        return i*j;
    }

    @Override
    public int divide(int i, int j) {
        return i/j;
    }
}
public class Main {
    public static void main(String[] args) {
        ApplicationContext ac =new ClassPathXmlApplicationContext("application-aop.xml");
        Calculate ccl = (Calculate) ac.getBean("calculateImpl");
        System.out.println(ccl.divide(1,4));
    }
}

上述代码定义了一个名为“LogAspect”的切点,它在@pointcut的注释中用切片表达式标识了切点的位置:

execution()表达式的主体;
第一个”*“符号表示返回值的类型任意;
com.spring.aop.implAOP所切的服务的包名
包名后面的”..“表示当前包及子包
第二个”*“表示类名,*即所有类。此处可以自定义
*(..)表示带任意参数的任意函数名的方法

如果含有多个切面,比如再添加一个关于验证的切面,可以用@Order(1)括号里的为数字,数字越小,优先级越高。

3.Execution表达式

用来找出合适插入代码片段的连接点;Spring只支持方法级别的连接点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。所以切点表达式作用的最小力度也就是方法了。

execution(                       // ? 表示可以省略
    modifiers-pattern?           //方法的修饰符
    ret-type-pattern             //方法的返回值
    declaring-type-pattern?      //方法所在的类的路径
    name-pattern(param-pattern)  //方法名和参数
    throws-pattern?              //抛出的异常
)

可以使用“”和“..”通配符,其中“”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。

在切点中引用Bean:

 @Pointcut( excecution(* com.company.service.WebService.updateWeb(..))  and bean('BaiduWeb') )

这表示将切面应用于WebService的updateWeb方法上,但是仅限于ID为”BaiduWeb”的Bean。
若想排除ID为”BaiduWeb”的Bean,只需在and 前加上 ’ ! ’ 。

其它切点描述符:

within

//匹配WebService类下的所有方法
@Pointcut("within(com.company.service.WebService)")
public void match(){ }
//匹配Service包及其子包下的所有类的方法
@Pointcut("within(com.company.service..*)")
public void matchPackage(){ }

this 和 target
Spring AOP是一个基于代理的系统,它区分代理对象本身(绑定到’this’)和代理后面的目标对象(绑定到’target’)。

//匹配DemoDAO接口的AOP代理对象方法
@Pointcut("this(com.company.DemoDao)")
//匹配DemoDAO接口的AOP目标对象方法
@Pointcut("this(com.company.DemoDao) ")

args参数匹配

//匹配任何只有一个Long参数的方法
 @Pointcut("within(com.company..*) && args(Long)")
 //匹配第一个参数为Long型的方法
 @Pointcut("within(com.company..*) && args(Long,..)")

4.基于xml的方式配置AOP

<!-- 配置AOP -->
<aop:config>

    <!-- 配置切点表达式 -->
    <aop:pointcut id="pointcutExpression"
    expression="execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))"/>

    <!-- 配置loggingAspect切面 -->
    <aop:aspect id="loggingAspect" ref="loggingAspectXml" order="1">
        <!-- 前置通知 -->
        <aop:before method="beforeMethod" pointcut-ref="pointcutExpression"/>
        <!-- 返回通知 -->
        <aop:after-returning method="afterReturningMethod"
            pointcut-ref="pointcutExpression" returning="result"/>
        <!-- 后置通知 -->
        <aop:after method="afterMethod" pointcut-ref="pointcutExpression"/>
        <!-- 异常通知 -->
        <aop:after-throwing method="afterThrowingMethod" 
            pointcut-ref="pointcutExpression" throwing="ex"/>
        <!-- 环绕通知 -->
        <aop:around method="aroundMethod" pointcut-ref="pointcutExpression"/>
    </aop:aspect>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值