【SSM -Spring篇05】使用AspectJ实现Spring AOP - (连接点(JoinPoint) - 通知(Advice))

AspectJ

  AspectJ是一个基于Java语言的AOP框架。从Spring 2.0以后引入了AspectJ的支持。目前的Spring框架,建议使用AspectJ实现Spring AOP。
  AOP面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性,同时提高了开发的效率

通知类型(通知(Advice):在方法执行前或执行后要做的动作)

  1. 环绕通知
    环绕通知(org.aopalliance.intercept.MethodInterceptor)是在目标方法执行前和执行后实施增强,可以应用于日志记录、事务处理等功能。
  2. 前置通知
    前置通知(org.springframework.aop.MethodBeforeAdvice)是在目标方法执行前实施增强,可应用于权限管理等功能。
  3. 后置返回通知
    后置返回通知(org.springframework.aop.AfterReturningAdvice)是在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。
  4. 后置(最终)通知
    后置通知(org.springframework.aop.AfterAdvice)是在目标方法执行后实施增强与后置返回通知不同的是,不管是否发生异常都要执行该通知,可应用于释放资源
  5. 异常通知
    异常通知(org.springframework.aop.ThrowsAdvice)是在方法抛出异常后实施增强,可以应用于处理异常、记录日志等功能。
  6. 引入通知
    引入通知(org.springframework.aop.IntroductionInterceptor)是在目标类中添加一些新的方法和属性,可以应用于修改目标类(增强类)。

使用AspectJ实现Spring AOP的方式有两种:

  1. 基于XML配置开发AspectJ
  2. 基于注解开发AspectJ。

1. 基于XML配置开发AspectJ

  注意:基于XML配置开发AspectJ是指通过XML配置文件定义切面、切入点及通知,所有这些定义都必须在applicationContext.xml的<aop:config>元素内。

案例实现

  1. pom.xml加入AspectJ的依赖
<!--AspectJ 实现Spring AOP  方式:  1、aspectJ基于XML开发  切面编程AOP  事物回滚使用-->
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
  1. 创建被代理类(目标类)
public interface UserDao {
    
    
    public void save();
    public void delete();
}
public class UserDaoImpl implements UserDao{
    
    
    @Override
    public void save() {
    
    
//        int i = 100/0;//测试异常抛出 虚拟机终止程序运行
        System.out.println("save user .....");
    }
    @Override
    public void delete() {
    
    
        System.out.println("delete user .....");
    }
}
  1. 创建切面类
/*切面类中定义的一个个方法叫通知
 advice 通知 --- 是增强的方法,应用于被代理类方法的额外功能
 */

/*
 AspectJ中的切入点匹配的执行点称作连接的(JoinPoint),
在通知方法中可以声明一个JoinPoint类型的参数。通过JoinPoint可以访问连接点的细节
    1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
    2.Signature getSignature() :获取连接点的方法签名对象;
    3.java.lang.Object getTarget() :获取连接点所在的目标对象;
    4.java.lang.Object getThis() :获取代理对象本身;
    
    ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
    5.java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
    6.java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的参数替换原来的参数。
* */

public class MyAspect {
    
    
//    前置通知  advice
    public void before(JoinPoint joinPoint){
    
    
//        被代理对象
        joinPoint.getTarget();
//        目标对象被增强的方法

        //获取方法签名对象   public void save()  【除了{}方法体以外都是方法签名】
        joinPoint.getSignature().getName();
        System.out.println("前置通知,用于权限控制。。"+ joinPoint.getTarget()+" ----methodName---"+joinPoint.getSignature().getName());
    }

    //后置通知
    public void afterReturning(JoinPoint joinPoint){
    
    
        System.out.println("后置通知,清除操作过的文件"+ joinPoint.getTarget()+" ----methodName---"+joinPoint.getSignature().getName());
    }
//  环绕通知 ProceedingJoinPoint是joinPoint的子类
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        System.out.println("环绕通知,方法执行之前,开启事务");
        joinPoint.proceed();  //让方法继续执行
        System.out.println("环绕通知,方法执行之后,关闭事务");
    }

    public void afterThrowing(Throwable e) throws Throwable {
    
    

        System.out.println("异常通知:方法出现异常调用,处理异常"+e.getMessage());

    }

    public void after(JoinPoint joinPoint) {
    
    
        System.out.println("最终通知  管理数据库连接等清理工作");

    }
}
  1. 创建配置文件applicationContext.xml,编写相关配置
<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 http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 被代理类 目标类 -->
    <bean id="userDao" class="com.xgf.aop.aspectj.xml.UserDaoImpl"/>

    <!--切面类  干什么事情-->
    <bean id="myAspect" class="com.xgf.aop.aspectj.xml.MyAspect"/>

    <!-- 基于XML配置开发AspectJ是指通过XML配置文件定义切面、切入点及通知,所
            有这些定义都必须在<aop:config>元素内。【***】 -->
    <aop:config>
        <!--引入切面类  -->
        <aop:aspect ref="myAspect">
            <!-- 执行的方法  在什么地方   pointcut切点
               execution(返回值类型 包名.类名.方法名(参数))
                (..)代表任意参数
            -->
            <aop:pointcut id="mycut" expression="execution(* com.xgf.aop.aspectj.xml.*.*(..))"/>

         <!-- 切入时机 -->
            <!--前置通知  方法执行之前-->
            <aop:before method="before" pointcut-ref="mycut"/>

            <!--后置返回通知 方法执行之后执行,方法正常结束才调用,出现异常不会调用-->
            <aop:after-returning method="afterReturning" pointcut-ref="mycut"/>

            <!--环绕通知  -->
            <aop:around method="around" pointcut-ref="mycut"/>

            <!--异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="mycut" throwing="e"/>

            <!-- 最终(后置)通知 不管方法是否出现异常都会执行,进行资源释放等清理工作 -->
            <aop:after method="after" pointcut-ref="mycut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. 编写测试类
public class AspectJXMLTest {
    public static void main(String[] args) {

        ApplicationContext context =
                new ClassPathXmlApplicationContext("com/xgf/aop/aspectj/xml/applicationContext.xml");

        /* 代理类 */
        UserDao userDao = (UserDao) context.getBean("userDao");
        userDao.save();
        userDao.delete();
    }
}
  1. 运行结果
    在这里插入图片描述



2. 基于注解开发AspectJ【***】

  注意:一定要在applicationContext.xml中配置<aop:aspectj-autoproxy></aop:aspectj-autoproxy>才能让aop起作用。

AspectJ通知注解【***】

注解名称
@Aspect 用于定义一个切面,注解在切面类上
@Pointcut 用于定义切入点表达式。在使用时,需要定义一个切入点方法。该方法是一个返回值void,且方法体为空的普通方法
@Before 用于定义前置通知。在使用时,通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@AfterReturning 用于定义 后置返回通知。在使用时,通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@Around 用于定义 环绕通知。在使用时,通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@AfterThrowing 用于定义异常通知。在使用时,通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式。另外,还有一个throwing属性用于访问目标方法抛出的异常,该属性值与异常通知方法中同名的形参一致
@After 用于定义后置(最终)通知。在使用时,通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式

连接点(JoinPoint)【***】

程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理(和方法有关的前前后后都是连接点)

JoinPoint对象方法 描述
Signature getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object getTarget() 获取被代理的对象
Object getThis() 获取代理对象
Object[] getArgs() 获取传入目标方法的参数对象(输入的参数列表)
joinpoint.proceed() 在环绕通知around中使用,用于启动目标方法执行的(环绕通知=前置+目标方法执行+后置通知)
joinPoint.getSignature().getName() 获取目标对象被增强的方法

案例

  1. pom.xml导入依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
  1. 创建被代理类
public interface UserDao {
    
    
    public void save();
    public void delete();
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    
    
    @Override
    public void save() {
    
    
        //int i = 100/0;//异常抛出 虚拟机终止程序运行
        System.out.println("save 保存 ......");
    }
    @Override
    public void delete() {
    
    
        System.out.println("delete 删除 ......");
    }
}
  1. 创建切面类
// @Component配置 启动容器会实例化类 名字首字母小写myAspect
// @Aspect配置切面
@Aspect
@Component
public class MyAspect {
    
    
    /* 配置切点 - pointcut切点
         execution(返回值类型 包名.类名.方法名(参数))
         (..)代表任意参数
    */
    @Pointcut("execution(* com.xgf.aop.aspectj.annotation.*.*(..))")

    public void mycut(){
    
    // 切点的id
    }

//    通知  advice
    //@Before("mycut()")    //当有多个值时用value  只有一个属性的时候直接写
    @Before(value="mycut()")
    public void before(JoinPoint joinPoint){
    
    
        //JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.

//        获取被代理对象
        joinPoint.getTarget();
//        目标对象被增强的方法
        joinPoint.getSignature().getName();
        System.out.println("前置通知,用于权限控制  被代理对象: "+ joinPoint.getTarget()+" ----方法名methodName---"+joinPoint.getSignature().getName());
    }

    @AfterReturning("mycut()")
    public void afterReturning(JoinPoint joinPoint){
    
    
        System.out.println("后置返回通知,清除操作过的文件  被代理对象:"+ joinPoint.getTarget()+" ----方法名methodName---"+joinPoint.getSignature().getName());
    }

    @Around("mycut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        System.out.println("环绕通知,方法执行之前,开启事务");
        joinPoint.proceed();//启动目标方法执行
        System.out.println("环绕通知,方法执行之后,关闭事务");
    }

    @AfterThrowing(value = "mycut()",throwing = "e")
    public void afterThrowing(Throwable e) throws Throwable {
    
    
        System.out.println("异常通知:方法出现异常调用,处理异常"+e.getMessage());
    }

    @After("mycut()")
    public void after(JoinPoint joinPoint) {
    
    
        System.out.println("最终通知  管理数据库连接等清理工作");
    }
}
  1. 创建applicationContext.xml配置文件
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>一要有定,不然不起作用!
<?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">

    <!--基于注解开发  扫描包-->
    <!-- 容器启动的时候 加载配置文件xml 读取com.xgf.aop.aspectj.annotation包识别注解 -->
    <context:component-scan base-package="com.xgf.aop.aspectj.annotation"/>

    <!--使aop起作用-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
  1. 编写测试类
public class AspectJAnnotationTest {
    
    
    public static void main(String[] args) {
    
    

        ApplicationContext context =
                new ClassPathXmlApplicationContext("com/xgf/aop/aspectj/annotation/applicationContext.xml");
        
        UserDao userDao = (UserDao) context.getBean("userDao");
        //调用方法
        userDao.save();
        userDao.delete();
    }
}
  1. 执行结果
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40542534/article/details/108684941