Spring框架AOP学习

 说明 :AOP部分:参考网上课程,自己写的总结,如有侵权,联系删除。

目录

SpringAOP_AOP简介

SpringAOP_AOP相关术语 

SpringAOP_AOP入门

SpringAOP_通知类型

SpringAOP_切点表达式

SpringAOP_多切面配置

SpringAOP_注解配置AOP

SpringAOP_原生Spring实现AOP 

 SpringAOP_SchemaBased实现AOP


SpringAOP_AOP简介

 AOP的全称是Aspect Oriented Programming,即面向切面编程。 是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔 离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高 了开发效率。

  • 作用:在不修改源码的基础上,对已有方法进行增强。
  • 实现原理:动态代理技术。
  • 优势:减少重复代码、提高开发效率、维护方便
  • 应用场景:事务处理、日志管理、权限控制、异常处理等方面。

SpringAOP_AOP相关术语 

名称说明
Joinpoint(连接点)指能被拦截到的点,在Spring中只有方法能被拦截。(路上的车好多)
Pointcut(切点)指要对哪些连接点进行拦截,即被增强的方法。 (拦住一个车)
Advice(通知)指拦截后要做的事情,即切点被拦截后执行的方法。
Aspect(切面)切点+通知称为切面
Target(目标)被代理的对象
Proxy(代理)代理对象
Weaving(织入)生成代理对象的过程

SpringAOP_AOP入门

AspectJ是一个基于Java语言的AOP框架,在Spring框架中建议使用 AspectJ实现AOP。

接下来我们写一个AOP入门案例:dao层的每个方法结束后都可以打印一条日志:

引入依赖

<!-- spring -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.13</version>
</dependency>
<!-- AspectJ -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>
<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

编写连接点(就是好多方法,可以切的都是拦截点)

@Repository
public class UserDao {
    public void add(){
        System.out.println("用户新增");
   }
    public void delete(){
        System.out.println("用户删除");
   }
    public void update(){
        System.out.println("用户修改");
   }
}

编写通知类

public class MyAspectJAdvice {
    // 后置通知
    public void myAfterReturning() {
        System.out.println("打印日志...");
   }
}

配置切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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/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.dream"></context:component-scan>
    <!-- 通知对象 -->
    <bean id="myAspectJAdvice" class="com.dream.advice.MyAspectAdvice"></bean>
    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="myAspectJAdvice">
            <!-- 配置切点 -->
            <aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/>
            <!-- 配置通知,这个可以配置前置。。。多个 -->
            <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试

public class UserDaoTest {
    @Test
    public void testAdd(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.add();
   }
    @Test
    public void testDelete(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.delete();
   }
    @Test
    public void testUpdate(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.update();
   }
}

SpringAOP_通知类型

AOP有以下几种常用的通知类型:

通知类型描述
前置通知(before)在方法执行前添加功能
后置通知(after-returning)在方法正常执行后添加功能
异常通知(after-throwing)在方法抛出异常后添加功能
最终通知(after)无论方法是否抛出异常,都会执行该通知
环绕通知 (around)在方法执行前后添加功能

编写通知方法

// 通知类
public class MyAspectAdvice {
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.println("切点方法名:" +joinPoint.getSignature().getName());
        System.out.println("目标对象:" + joinPoint.getTarget());
        System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
   }
    // 前置通知
    public void myBefore() {
        System.out.println("前置通知...");
   }
    // 异常通知
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知...");
        System.err.println(ex.getMessage());
   }
    // 最终通知
    public void myAfter() {
        System.out.println("最终通知");
   }
    // 环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前");
        Object obj = proceedingJoinPoint.proceed(); // 执行方法
        System.out.println("环绕后");
        return obj;
   }
}

配置切面

<!-- 配置AOP -->
<aop:config>
    <!-- 配置切面 -->
    <aop:aspect ref="myAspectJAdvice">
        <!-- 配置切点 -->
        <aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/>
        <!-- 前置通知 -->
        <aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
        <!-- 后置通知 -->
        <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
        <!-- 异常通知 -->
        <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
        <!-- 最终通知 -->
        <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
        <!-- 环绕通知 -->
        <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
    </aop:aspect>
</aop:config>

SpringAOP_切点表达式

使用AspectJ需要使用切点表达式配置切点位置,写法如下:

  • 标准写法:访问修饰符 返回值 包名.类名.方法名(参数列表)
  • 访问修饰符可以省略。
  • 返回值使用 * 代表任意类型。
  • 包名使用 * 表示任意包,多级包结构要写多个 * ,使用 *.. 表示任意包结构
  • 类名和方法名都可以用 * 实现通配。
  • 参数列表
    • 基本数据类型直接写类型
    • 引用类型写 包名.类名
    • * 表示匹配一个任意类型参数
    • .. 表示匹配任意类型任意个数的参数
  • 全通配: * *..*.*(..)

SpringAOP_多切面配置

我们可以为切点配置多个通知,形成多切面,比如希望dao层的每 个方法结束后都可以打印日志并发送邮件:

添加编写发送邮件的通知:

public class MyAspectJAdvice2 {
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.println("发送邮件");
   }
}

配置切面:

<!-- 通知对象 -->
<bean id="myAspectJAdvice" class="com.dream.advice.MyAspectAdvice"></bean>
<bean id="myAspectJAdvice2" class="com.dream.advice.MyAspectAdvice2"></bean>
<!-- 配置AOP -->
<aop:config>
    <!-- 配置切面 -->
    <aop:aspect ref="myAspectJAdvice">
        <!-- 配置切点 -->
        <aop:pointcut id="myPointcut" expression="execution(* *..*.*(..))"/>
        <!-- 后置通知 -->
        <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
    </aop:aspect>
    
    <aop:aspect ref="myAspectJAdvice2">
        <aop:pointcut id="myPointcut2" expression="execution(* com.dream.dao.UserDao.*(..))"/>
        <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut2"/>
    </aop:aspect>
</aop:config>

SpringAOP_注解配置AOP

Spring可以使用注解代替配置文件配置切面:

  1. Spring可以使用注解代替配置文件配置切面:
    <!-- 开启注解配置Aop -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  2. 在通知类上方加入注解 @Aspect
  3. 在通知方法上方加入注解@Before/@AfterReturning/@AfterThrowing/@After/@Around
    @Aspect
    @Component
    public class MyAspectAdvice {
        // 后置通知
        @AfterReturning("execution(* com.dream.dao.UserDao.*(..))")
        public void myAfterReturning(JoinPoint joinPoint) {
            System.out.println("切点方法名:" + joinPoint.getSignature().getName());
            System.out.println("目标对象:" + joinPoint.getTarget());
            System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
       }
        // 前置通知
        @Before("execution(* com.dream.dao.UserDao.*(..))")
        public void myBefore() {
            System.out.println("前置通知...");
       }
        // 异常通知
        @AfterThrowing(value = "execution(* com.dream.dao.UserDao.*(..))",throwing = "ex")
        public void myAfterThrowing(Exception ex) {
            System.out.println("异常通知...");
            System.err.println(ex.getMessage());
       }
        // 最终通知
        @After("execution(* com.dream.dao.UserDao.*(..))")
        public void myAfter() {
            System.out.println("最终通知");
       }
        // 环绕通知
        @Around("execution(* com.dream.dao.UserDao.*(..))")
        public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕前");
            Object obj = proceedingJoinPoint.proceed(); // 执行方法
            System.out.println("环绕后");
            return obj;
       }
    }

如何为一个类下的所有方法统一配置切点:

1.在通知类中添加方法配置切点(单独创建一个方法)

@Pointcut("execution(* com.dream.dao.UserDao.*(..))")
public void pointCut(){}

2.在通知方法上使用定义好的切点

@Before("pointCut()")
public void myBefore(JoinPoint joinPoint) {
    System.out.println("前置通知...");
}
@AfterReturning("pointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("后置通知...");
}

配置类如何代替xml中AOP注解支持?

在配置类上方添加@EnableAspectJAutoProxy即可

@Configuration
@ComponentScan("com.dream")
@EnableAspectJAutoProxy
public class SpringConfig {
}

SpringAOP_原生Spring实现AOP 

除了AspectJ,Spring支持原生方式实现AOP。原生方式实现AOP只有四种通知类型

  1.  引入依赖
    <!-- 原生AOP -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.13</version>
    </dependency>
  2. 编写通知类
    // Spring原生Aop的通知类
    public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice,
    ThrowsAdvice, MethodInterceptor {
        /**
         * 前置通知
         * @param method 目标方法
         * @param args 目标方法的参数列表
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void before(Method method,Object[] args, Object target) throws Throwable {
            System.out.println("前置通知");
       }
        /**
         * 后置通知
         * @param returnValue 目标方法的返回值
         * @param method 目标方法
         * @param args 目标方法的参数列表
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args,
    Object target) throws Throwable {
            System.out.println("后置通知");
       }
        /**
         * 环绕通知
         * @param invocation 目标方法
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("环绕前");
            Object proceed =invocation.proceed();
            System.out.println("环绕后");
            return proceed;
       }
        /**
         * 异常通知
         * @param ex 异常对象
         */
        public void afterThrowing(Exception ex){
            System.out.println("发生异常了!");
       }
    }

    Spring原生方式实现AOP时,只支持四种通知类型:

    通知类型实现接口
    前置通知MethodBeforeAdvice
    后置通知AfterReturningAdvice
    异常通知ThrowsAdvice
    环绕通知MethodInterceptor
  3. 编写配置类
    <!-- 通知对象 -->
    <bean id="springAop" class="com.dream.advice.SpringAop"></bean>
    <!-- 配置代理对象 -->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 配置目标对象 -->
        <property name="target" ref="userDao"></property>
        <!-- 配置通知 -->
        <property name="interceptorNames">
            <list>
                <value>springAop</value>
            </list>
        </property>
        <!-- 代理对象的生成方式 true:使用CGLib ,false:使用原生JDK生成-->
        <property name="proxyTargetClass" value="true"></property>
    </bean>
  4. 测试类
    public class UserDaoTest2 {
        @Test
        public void testAdd(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
            UserDao userDao = (UserDao)ac.getBean("userDaoProxy"); // 获取的是代理对象
            userDao.update();
       }
    }

 测试的时候报错:就是不知道啥情况呢哎*********************

java.lang.ClassCastException: class com.sun.proxy.$Proxy27 cannot be cast to class com.dream.dao.UserDao (com.sun.proxy.$Proxy27 and com.dream.dao.UserDao are in unnamed module of loader 'app')

 SpringAOP_SchemaBased实现AOP

SchemaBased(基础模式)配置方式是指使用Spring原生方式定义通知,而使用AspectJ框架配置切面。

  1. 编写通知类
    public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {
        /**
         * 前置通知
         * @param method 目标方法
         * @param args 目标方法的参数列表
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("前置通知");
       }
        /**
         * 后置通知
         * @param returnValue 目标方法的返回值
         * @param method 目标方法
         * @param args 目标方法的参数列表
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("后置通知");
       }
        /**
         * 环绕通知
         * @param invocation 目标方法
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("环绕前");
            Object proceed =invocation.proceed();
            System.out.println("环绕后");
            return proceed;
       }
        /**
         * 异常通知
         * @param ex 异常对象
         */
        public void afterThrowing(Exception ex){
            System.out.println("发生异常了!");
       }
    }
    
  2. 配置切面

    <!-- 通知对象 -->
    <bean id="springAop2" class="com.dream.aop.SpringAop2"/>
    <!-- 配置切面 -->
    <aop:config>
        <!-- 配置切点-->
        <aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/>
        <!-- 配置切面:advice-ref:通知对象 pointcut-ref:切点 -->
        <aop:advisor advice-ref="springAop2" pointcut-ref="myPointcut"/>
    </aop:config>
    

 这个是可以的,感觉第三种也不太好用