Spring AOP 基于AspectJ

简介

AspectJ是一个基于Java语言的AOP框架,Spring2.0之后新增了对AspectJ切点表达式支持。由于Spring1.0的时候Aspectj还未出现;java

AspectJ1.5中新增了对注解的支持,容许直接在Bean类中定义切面。新版本的Spring框架建
议咱们都使用AspectJ方式来开发AOP,并提供了很是灵活且强大的切点表达式 ;spring

固然不管使用Spring本身的AOP仍是AspectJ相关的概念都是相同的;数据库

注解配置

依赖导入:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

通知类型

@AspectJ提供的通知类型:express

  1. @Before 前置通知 在原始方法执行前执行app

  2. @AfterReturning 后置通知 在原始方法执行前执行框架

  3. @Around 环绕通知 完全拦截原始方法的执行,执行先后均可以增长逻辑,也能够不执行原始方法函数

  4. @AfterThrowing抛出通知,执行原始方法出现异常时执行spa

  5. @After 最终final通知,无论是否异常,原始方法调用后都会执行代理

  6. @DeclareParents 引介通知,至关于IntroductionInterceptor (了解便可)code

定义切点

经过execution函数来定义切点

语法:execution(访问修饰符 返回类型 方法名 参数 异常)

表达式示例:

匹配全部类public方法:execution(public * *(..))第一个*表示返回值 ..表示任意个任意类型参数

匹配指定包下全部方法: execution(* cn.xxx.dao.*(..)) 第一个想*表示忽略权限和返回值类型

匹配指定包下全部方法:execution(* cn.xxx.dao..*(..))包含子包

匹配指定类全部方法: execution(* cn.xxx.service.UserService.*(..))

匹配实现特定接口全部类方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))

匹配全部save开头的方法: execution(* save*(..))

前置通知

pom依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

xml须要添加aop名称空间及xsd:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    启用aspectj    -->
    <aop:aspectj-autoproxy/>
<!--    目标-->
    <bean id="personDao" class="com.yh.demo1.PersonDao"/>
<!--    切面-->
    <bean class="com.yh.demo1.MyAspect"/>
</beans>

test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    PersonDao personDao;

    @Test
    public void test(){
        personDao.delete();
        personDao.update();
    }
}

切面类:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
      //表示PersonDao下全部方法都做为切点
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice(){
        System.out.println("before code run.....");
    }
}

当咱们须要获取切点信息(被加强的代码)时,能够在通知添加参数,想下面这样

@Aspect
public class MyAspect {
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice2(JoinPoint point){
        System.out.println("before code run2....." + point);
    }
}

后置通知:

//当须要获取原始方法的返回值时能够在注解中添加returning参数来指定参数名  Aspectj会自动将返回值放到参数中
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
    System.out.println("删除方法执行后 .....   返回值为:"+ result);
}

后置通知能够获取目标方法的返回值

环绕通知:

@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
    //code............
    System.out.println("环绕前置..");

    //执行原始方法 __当须要获取返回值时能够声明变量接收
    Object result = point.proceed();
    System.out.println("原始方法返回值: "+result);
    //code............
    System.out.println("环绕后置..");
}

环绕通知与其余通知最大的区别在于环绕通知能够控制是否调用原始方法

注意:参数类型必须为ProceedingJoinPoint,不然 没法执行原始方法,

异常通知

@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
    System.out.println(point + " 方法出现"+e.getMessage()+"异常");
}

当方法中出现时才会执行该通知,若须要获取异常信息,可在注解中添加throwing指定参数名称

咱们可使用环绕+异常通知来处理数据库事务,在环绕中开启事务以及提交事务,异常通知中回滚事务,固然Spring已经对事务进行了封装不须要本身写

最终通知

@After(value = "execution(* *delete(..))")
public void afterRun(){
    System.out.println("最终");
}

最终通知叫作after 即调用原始方法以后执行不管原始方法中是否出现异常

然后置叫作afterReturning表示在成功返回后才会执行执行

带有逻辑符的表达式:

在表达式中可使用户逻辑操运算符,与&&||!

示例:

/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))

execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))

!execution(* cn.xxx.service.UserDao.insert(..))
*/

切点命名

假设有多个通知应用在同一个切点上时,咱们须要重复编写execution表达式,且后续要修改切点时则多个通知都须要修改,维护起来很是麻烦,咱们能够经过给切点指定名称从而完成对切点的重复使用和统一操做,以提升开发维护效率;

//定义命名切点  方法名称即切点名称
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.save(..))")
private void savePointcut(){}

@Pointcut(value = "execution(* com.yh.demo1.PersonDao.delete(..))")
private void deletePointcut(){}

多个通知应用到同一个切点:

//使用命名切点
@Before(value = "savePointcut()")
public void beforeAdvice(){
    System.out.println("before code run.....");
}
//使用命名切点
@Around(value = "savePointcut()")
public void beforeAdvice2(ProceedingJoinPoint point) throws Throwable {
    System.out.println("环绕前");
    point.proceed();
    System.out.println("环绕后");

一个通知应用到多个切点

//同一个通知对应多个切点
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
    System.out.println("after code run.....");
}

XML配置

XML配置所需的jar 以及各个对象之间的依赖关以及表达式的写法都是同样的,仅仅是换种方式来写而已;

xml:

<!--目标-->
    <bean id="studentDao" class="com.yh.demo2.StudentDao"/>
        <!--通知-->
    <bean id="advices" class="com.yh.demo2.XMLAdvice"/>

        <!--织入信息-->
    <aop:config>
                <!--切点定义-->
        <aop:pointcut id="select" expression="execution(* com.yh.demo2.StudentDao.select(..))"/>
                <!--切面定义-->
        <aop:aspect ref="advices">
            <aop:before method="before" pointcut-ref="select"/>
            <aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
            <aop:after method="after" pointcut-ref="select" />
            <aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
            <aop:around method="around" pointcut-ref="select"/>
        </aop:aspect>
      
        <!--入侵式通知 即通知须要实现指定接口 两种不能同时使用 -->
        <aop:advisor advice-ref="advice2" pointcut-ref="select"/>
    </aop:config>
  <!--入侵式通知Bean-->
  <bean id="advice2" class="com.yh.demo2.XMLAdvice2"/>

通知类:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;

public class XMLAdvice {
    public void before(JoinPoint pointcut){ System.out.println("前置通知 切点:"+pointcut); }

    public void afterReturning(JoinPoint point,Object result){
        System.out.println("后置通知 切点:"+point);
    }

    public void after(JoinPoint point){ System.out.println("最终通知 切点:"+point); }

    public void exception(JoinPoint point,Throwable e){
        System.out.println("异常通知: " + e+"切点:"+point);
    }

    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕前");
        point.proceed();
        System.out.println("环绕后");
    }
}

你会发现 ,不管是XML仍是注解都不须要手动指定代理,以及目标对象,Aspectj会从切点中获取目标对象信息并自动建立代理;

AspectJ是目前更流行的方式,具体采用XML仍是注解须要根据项目具体状况,小组协做开发推荐xml;

相关文章
相关标签/搜索