一文吃透 Spring 中的 AOP 编程

Posted Java Fans

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文吃透 Spring 中的 AOP 编程相关的知识,希望对你有一定的参考价值。

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。
🍎个人主页:Java Fans的博客
🍊个人信条:不迁怒,不贰过。小知识,大智慧。
💞当前专栏:SSM 框架从入门到精通
✨特色专栏:国学周更-心性养成之路
🥭本文内容:一文吃透 Spring 中的 AOP 编程

文章目录


AOP 概述

AOPAspect Oriented Programming 的缩写,是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOPOOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型

AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口

AOP 实现分类

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能,按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP

AOP 术语

  • 连接点(JointPoint)与切入点匹配的执行点,在程序整个执行流程中,可以织入切面的位置,方法的执行前后,异常抛出的位置

  • 切点(PointCut)在程序执行流程中,真正织入切面的方法。

  • 切面(ASPECT)切点+通知就是切面

  • 通知(Advice)切面必须要完成的工作,也叫增强。即,它是类中的一个方法,方法中编写织入的代码。
    前置通知 后置通知
    环绕通知 异常通知
    最终通知

  • 目标对象(Target)被织入通知的对象

  • 代理对象(Proxy)目标对象被织入通知之后创建的新对象

通知的类型

Spring 方面可以使用下面提到的五种通知工作:

通知描述
前置通知在一个方法执行之前,执行通知。
最终通知在一个方法执行之后,不考虑其结果,执行通知。
后置通知在一个方法执行之后,只有在方法成功完成时,才能执行通知。
异常通知在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知在一个方法调用之前和之后,执行通知。

基于 Aspectj 实现 AOP 操作

基于 Aspectj 实现 AOP 操作,经历了下面三个版本的变化,注解版是我们最常用的。

切入点表达式

作用:声明对哪个类中的哪个方法进行增强

语法:

execution([访问权限修饰符] 返回值 [ 类的全路径名 ] 方法名 (参数列表)[异常])

  • 访问权限修饰符:

    可选项,不写就是四个权限都包含

    写public就表示只包括公开的方法

  • 返回值类型

    必填项 * 标识返回值任意

  • 全限定类名

    可选项,两个点 … 表示当前包以及子包下的所有类,省略表示所有类

  • 方法名

    必填项 * 表示所有的方法 set*表示所有的set方法

  • 形参列表

    必填项

    ()表示没有参数的方法

    (…)参数类型和参数个数随意的方法

    (*)只有一个参数的方法

    (*,String) 第一个参数类型随意,第二个参数String类型

  • 异常信息

    可选项 省略时标识任何异常信息

第一版:基于xml(aop:config)配置文件

使用 Spring AOP 接口方式实现 AOP, 可以通过自定义通知来供 Spring AOP 识别对应实现的接口是:

  1. 前置通知:MethodBeforeAdvice
  2. 返回通知:AfterReturningAdvice
  3. 异常通知:ThrowsAdvice
  4. 环绕通知:MethodInterceptor

实现步骤:

1、定义业务接口

/**
 * 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
 */

package cn.kgc.spring05.entity;

public interface Teacher 
    String teachOnLine(String course);
    String teachOffLine(Integer course);

2、定义实现类

package cn.kgc.spring05.entity;

public class TeacherA implements Teacher
    @Override
    public String teachOnLine(String course) 
        System.out.println("TeacherA开始"+course+"课程线上教学");
        if(course.equals("java"))
            throw new RuntimeException("入门到放弃!");
        
        return course+"课程线上教学";
    

    @Override
    public String teachOffLine(Integer course) 
        System.out.println("TeacherA开始"+course+"课程线下教学");
        return course+"课程线下教学";
    


3、实现接口定义通知类

前置通知类

package cn.kgc.spring05.advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

//前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice 
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable 
        System.out.println("------------spring aop 前置通知------------");
    

后置通知类

package cn.kgc.spring05.advice;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class MyAfterReturnAdvice implements AfterReturningAdvice 
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable 
        System.out.println("------------spring aop 后置通知------------");
    

4、XML 配置方式

<?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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--托管通知-->
    <bean id="after" class="cn.kgc.spring05.advice.MyAfterReturnAdvice"></bean>
    <bean id="before" class="cn.kgc.spring05.advice.MyMethodBeforeAdvice"></bean>
    <bean id="teacherA" class="cn.kgc.spring05.entity.TeacherA"></bean>
    <!--AOP的配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="pt" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="pt"></aop:advisor>
        <aop:advisor advice-ref="after" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>

5、测试

package cn.kgc.spring05;

import cn.kgc.spring05.entity.Teacher;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Unit test for simple App.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class AppTest

    @Autowired
    Teacher teacher;

    @Test
    public void teachOnLine() 
        System.out.println(teacher.getClass());
        String s = teacher.teachOnLine("java");
        System.out.println("s = " + s);
    

6、运行结果

第二版:基于xml(aop:aspect)配置文件

基于 xml(aop:config) 配置文件的方式,增加几个通知,就会创建几个通知类,那我们能否将这些通知类写在一个类中呢?下面就让我来带你们找到解决之法!

配置 AspectJ 标签解读表

实现步骤:

1、定义业务接口

/**
 * 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
 */

package cn.kgc.spring05.entity;

public interface Teacher 
    String teachOnLine(String course);
    String teachOffLine(Integer course);

2、定义实现类

package cn.kgc.spring05.entity;

public class TeacherA implements Teacher
    @Override
    public String teachOnLine(String course) 
        System.out.println("TeacherA开始"+course+"课程线上教学");
        if(course.equals("java"))
            throw new RuntimeException("入门到放弃!");
        
        return course+"课程线上教学";
    

    @Override
    public String teachOffLine(Integer course) 
        System.out.println("TeacherA开始"+course+"课程线下教学");
        return course+"课程线下教学";
    


3、实现接口定义通知类

package cn.kgc.spring05.advice;

public class AllAdvice 
    public void before()System.out.println("------------前置通知--------------");

    public void afterReturning()System.out.println("------------后置通知--------------");

    public void afterThrowing()System.out.println("------------异常通知--------------");

    public void after()System.out.println("------------最终通知--------------");

    public void around()System.out.println("------------环绕通知--------------");

4、XML 配置方式

<?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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--托管通知-->
    <bean id="all" class="cn.kgc.spring05.advice.AllAdvice"></bean>
    <bean id="teacherA" class="cn.kgc.spring05.entity.TeacherA"></bean>
    <!--AOP的配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="pt" expression="execution(* *(String))"/>
        <aop:aspect ref="all">
            <aop:before method="before" pointcut-ref="pt"></aop:before>
            <aop:after-returning method="afterReturning" pointcut-ref="pt"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"></aop:after-throwing>
            <aop:after method="after" pointcut-ref="pt"></aop:after>
<!--            <aop:around method="around" pointcut-ref="pt"></aop:around>-->
        </aop:aspect>
    </aop:config>
</beans>

5、测试

package cn.kgc.spring05.advice;

import cn.kgc.spring05.entity.Teacher;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config2.xml")
public class AllAdviceTest

    @Autowired
    Teacher teacher;

    @Test
    public void test01() 
        System.out.println(teacher.getClass());
        String s = teacher.teachOnLine("java");
        System.out.println("s = " + s);
    

6、运行结果

第三版:基于注解实现通知

  • 常用 “通知” 注解如下:

    @Aspect 注解将此类定义为切面。
    @Before 注解用于将目标方法配置为前置增强(前置通知)。
    @AfterReturning 注解用于将目标方法配置为后置增强(后置通知)。
    @Around 定义环绕增强(环绕通知)
    @AfterThrowing 配置异常通知
    @After 也是后置通知,与 @AfterReturning 很相似,区别在于 @AfterReturning 在方法执行完毕后进行返回,可以有返回值。@After 没有返回值。

实现步骤:

1、定义业务接口

/**
 * 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
 */

package cn.kgc.spring05.entity;

public interface Teacher 
    String teachOnLine(String course);
    String teachOffLine(Integer course);

2、定义注解

package cn.kgc.spring05.advice;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoAdvice 

3、定义实现类

package cn.kgc.spring05.entity;

import cn.kgc.spring05.advice.AnnoAdvice;
import org.springframework.stereotype.Component;

@Component
public class TeacherA implements Teacher
    @Override
    @AnnoAdvice
    public String teachOnLine(String course) 
        System.out.println("TeacherA开始"+course+"课程线上教学");
        if(course.equals("java"))
            throw new RuntimeException("入门到放弃!");
        
        return course+&#

以上是关于一文吃透 Spring 中的 AOP 编程的主要内容,如果未能解决你的问题,请参考以下文章

Spring一文带你吃透AOP面向切面编程技术(下篇)

一文吃透 Spring 中的IOC和DI

从源码入手,一文带你读懂Spring AOP面向切面编程

从源码入手,一文带你读懂Spring AOP面向切面编程

面试必备:从源码入手,带你一文读懂Spring AOP面向切面编程

一文带你认识Spring AOP