首页 专题 文章 代码 归档
Spring切面AspectJ
2020.01.26 18:34 2020.02.24 16:16

1. AspectJ

1.1. 介绍

  • AspsstJ是一个基于Java语言的AOP框架
  • Spring2.0以后新增了对AspectU切点表达式支持
  • @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。
  • 新版本Spring框架,建议使用AspectJ方式来开发AOP

1.2. 切入点表达式【重点】

1、语法

            访问修饰符          返回值类型(必填)     包和类               方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

修饰符:

  • public 公共方法
  • * 任意

返回值:

  • void 没有返回值
  • String 字符串
  • * 任意

包[可省略]

  • com.misiai.other 固定包
  • com.misiai.other.*.service other包下任意
  • com.misiai.other.. (两个点)other包下所有包[可无限子包]

类[省略]

  • UserServiceImpl 指定
  • User* User开头
  • *Impl Impl结尾
  • * 任意

方法名[不可省略]

  • AddUser 固定
  • add* add开头
  • *User User结尾
  • * 任意

(参数)

  • () 无参
  • (int) 一个整型
  • (int, int) 两个整型
  • .. 任意参数

throws

  • 一般不写

最常用:

execution(* com.misiai..*ServiceImpl.*(..))
//表示com.misiai包及其所有子包下所有的以ServiceImpl结尾的类生成代理对象。 

1.3. AspectJ通知类型

  • aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
  • aspecti 通知类型,只定义类型名称、以及方法格式。
  1. before:前置通知,在切入点方法的前面显示;
  2. after-returning:后置通知,在切入点方法的后面显示;
  3. around:环绕通知,十分强大,可以做任何事情,在切入点前后显示;可以实现其它4个的功能,主要掌握它。
  4. after-throwing:抛出异常通知,当出现异常时显示;
  5. after:最终通知,不管发生什么在切入点后面显示;

1.4. 导入jar包

  • aop联盟 规范
  • spring-aop
  • aspect 规范
  • spring-aspect

1.5. 基于XML

  1. 目标类:接口+实现类
  2. 切面类:编写多个通知,采用aspectJ,通知名称任意(方法名任意)
  3. aop编程,将通知应用到目标类。
  4. 测试
1.5.1. 目标类
package com.misiai.d_aspect.a_xml;
package com.misiai.d_aspect.a_xml;

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("d_aspect.a_xml:Add User");
        Integer a = 1 / 0;
    }

    @Override
    public String updateUser() {
        System.out.println("d_aspect.a_xml:Update User");
        return "更新成功";
    }

    @Override
    public void deleteUser() {
        System.out.println("d_aspect.a_xml:Delete User");
    }
}
1.5.2. 切面类
package com.misiai.d_aspect.a_xml;

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

import java.util.Arrays;

/**
 * 切面类,具有多个通知
 */
public class MyAspect {

    /**
     * 前置通知
     */
    public void before(JoinPoint joinPoint) {
        /*JoinPoint,传递参数,有一些类的信息
         * 比如获得方法名:joinPoint.getSignature().getName()
         * */
        System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
    }

    /*后置返回*/
    public void afterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("后置返回通知:" + ret);
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {


        /*这里可以放前置通知*/
        System.out.println("环绕方法-前置通知");
        //    手动执行目标方法

        Object proceed = joinPoint.proceed();

        System.out.println("环绕方法-后置通知");
        /*这里可以放后置/后置返回通知*/
        return proceed;
    }

    /**
     * 后置通知
     *
     * @param joinPoint
     */
    public void after(JoinPoint joinPoint) {
        /*比如这里还可以获得参数*/
        System.out.println("after 参数:" + Arrays.asList(joinPoint.getArgs()));
        System.out.println("后置通知 aspect");
        System.out.println("====================");
    }

    //抛出异常通知
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable) {
        System.out.println("抛出异常通知:"+throwable.getMessage());
    }
}
1.5.3. 配置文件
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--1.创建目标类-->
    <bean id="userService" class="com.misiai.d_aspect.a_xml.UserServiceImpl"/>

    <!--2.创建切面类,即通知-->
    <bean id="aspect" class="com.misiai.d_aspect.a_xml.MyAspect"/>

    <!--3.aop编程-->
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.misiai.d_aspect.a_xml.UserServiceImpl.*(..))"/>
        <aop:aspect ref="aspect">


            <!--
            3.1前置通知
            <aop:before method=""pointcut=""pointcut-ref=""/>
            method:通知,及方法名
            pointcut:切入点表达式,此表达式只能当前通知使用。
            pointcut-ref:切入点引用,可以与其他通知共享切入点。
            -->
            <!--<aop:before method="before" pointcut-ref="pointCut"/>-->
            <!--3.2后置返回通知-->
            <!--<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="ret"/>-->
            <!--3.3后置通知-->
            <!--<aop:after method="after" pointcut-ref="pointCut"/>-->
            <!--3.4环绕通知
            通知方法格式:public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
            返回值类型:Object
            方法名:任意
            参数:org.aspectj.lang.ProceedingJoinPoint
            抛出异常
            执行目标方法:Object obi=joinPoint.proceed();
            -->
            <aop:around method="around" pointcut-ref="pointCut"/>
            <!--3.5 抛出异常-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="throwable"/>
        </aop:aspect>
    </aop:config>

</beans>
1.5.4. 测试类
package com.misiai.d_aspect.a_xml;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAspectXml {

    @Test
    public void test01() {
        String xmlPath = "com/misiai/d_aspect/a_xml/beans.xml";
        ApplicationContext ap = new ClassPathXmlApplicationContext(xmlPath);

    //    获得目标类
        UserService userService = (UserService)ap.getBean("userService");
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();

    }


}

1.6. 基于注解

1.6.1. 替换bean
<!--1.创建目标类-->
<bean id="userService" class="com.misiai.d_aspect.b_ann.UserServiceImpl"/>

<!--2.创建切面类,即通知-->
<bean id="aspect" class="com.misiai.d_aspect.b_ann.MyAspect"/>

把上面两个替换掉,改用注解方式

截图-1580032624

添加xml

<!--1.扫描注解类-->
<context:component-scan base-package="com.misiai.d_aspect.b_ann"/>
<!--2.确定aop注解生效-->
<aop:aspectj-autoproxy/>
1.6.2. 替换xml配置文件中的aop
<aop:config>
....
</aop:config>

在各通知方法上增加注解:

@Before("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
public void before(JoinPoint joinPoint) {
    /*JoinPoint,传递参数,有一些类的信息
     * 比如获得方法名:joinPoint.getSignature().getName()
     * */
    System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}
1.6.3. 声明公共切入点表达式

前面发现,如果是加注解,那么每个注解的切入表达式都是一样的,那么怎么抽取公共?

1、随便声明一个方法,在其上面声明切入表达式

@Pointcut("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
public void myPointCut() {

}

2、使用

然后在需要使用的地方,加上value="方法名()"

@Before(value = "myPointCut()")
public void before(JoinPoint joinPoint) {
    /*JoinPoint,传递参数,有一些类的信息
     * 比如获得方法名:joinPoint.getSignature().getName()
     * */
    System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
}

2.1、如果有返回值的话,就使用:returning

@AfterReturning(value = "myPointCut()", returning = "ret")
public void afterReturning(JoinPoint joinPoint, Object ret) {
    System.out.println("后置返回通知:" + ret);
}
1.6.4. 最终代码

切面类

package com.misiai.d_aspect.b_ann;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 切面类,具有多个通知
 */
@Component
@Aspect
public class MyAspect {


    @Pointcut("execution(* com.misiai.d_aspect.b_ann.UserServiceImpl.*(..))")
    public void myPointCut() {

    }

    /**
     * 前置通知
     * 切入点当前有效
     */
    // @Before(value = "myPointCut()")
    public void before(JoinPoint joinPoint) {
        /*JoinPoint,传递参数,有一些类的信息
         * 比如获得方法名:joinPoint.getSignature().getName()
         * */
        System.out.println("前置通知 aspect," + joinPoint.getSignature().getName());
    }

    /**
     * 后置返回
     */
    // @AfterReturning(value = "myPointCut()", returning = "ret")
    public void afterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("后置返回通知:" + ret);
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    // @Around(value = "myPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {


        /*这里可以放前置通知*/
        System.out.println("环绕方法-前置通知");
        //    手动执行目标方法

        Object proceed = joinPoint.proceed();

        System.out.println("环绕方法-后置通知");
        /*这里可以放后置/后置返回通知*/
        return proceed;
    }

    /**
     * 后置通知
     *
     * @param joinPoint
     */
    @After(value = "myPointCut()")
    public void after(JoinPoint joinPoint) {
        /*比如这里还可以获得参数*/
        System.out.println("after 参数:" + Arrays.asList(joinPoint.getArgs()));
        System.out.println("后置通知 aspect");
        System.out.println("====================");
    }

    //抛出异常通知
    // @AfterThrowing(value = "myPointCut()", throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
        System.out.println("抛出异常通知:" + throwable.getMessage());
    }
}
1.6.5. 注解总结
@Aspect://作用是把当前类标识为一个切面供容器读取
@Pointcut://Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around://环绕增强,相当于MethodInterceptor
@AfterReturning://后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before://标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing://异常抛出增强,相当于ThrowsAdvice
@After: //final增强,不管是抛出异常或者正常退出都会执行
本节阅读完毕! (分享
二维码图片 扫描关注我们哟