一文吃透 Spring 中的 AOP 编程
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:SSM 框架从入门到精通 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:一文吃透 Spring 中的 AOP 编程
文章目录 AOP 概述AOP 实现分类AOP 术语基于 Aspectj 实现 AOP 操作第一版:基于xml(aop:config)配置文件第二版:基于xml(aop:aspect)配置文件第三版:基于注解实现通知 AOP 概述AOP 为 Aspect Oriented Programming 的缩写,是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 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 识别对应实现的接口是:
前置通知:MethodBeforeAdvice返回通知:AfterReturningAdvice异常通知:ThrowsAdvice环绕通知: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+"课程线上教学"; } @Override @AnnoAdvice public String teachOffLine(Integer course) { System.out.println("TeacherA开始"+course+"课程线下教学"); return course+"课程线下教学"; } }4、实现接口定义切面类
首先在类上面添加 @Aspect 注解,将该类转化为切面类,再在类中的各个方法上面使用各自的 “通知” 注解即可实现。
package cn.kgc.spring05.advice; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class AllAdvice { @Pointcut("@annotation(AnnoAdvice)") public void point(){} @Before("point()") public void before(){System.out.println("------------前置通知--------------");} @AfterReturning("point()") public void afterReturning(){System.out.println("------------后置通知--------------");} @AfterThrowing("point()") public void afterThrowing(){System.out.println("------------异常通知--------------");} @After("point()") public void after(){System.out.println("------------最终通知--------------");} @Around("point()") public Object aroundAdvice(ProceedingJoinPoint joinPoint){ Object proceed = null; try { System.out.println("----------spring aop 环绕 前通知-----------"); proceed = joinPoint.proceed(); System.out.println("----------spring aop 环绕 后通知-----------"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("----------spring aop 环绕 异常通知-----------"); }finally { System.out.println("----------spring aop 环绕 最终通知-----------"); } return proceed; } }5、XML 配置方式
开启包扫描和aspectj自动代理
<?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" xmlns:context="http://www.springframework.org/schema/context" 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"> <!--开启包扫描--> <context:component-scan base-package="cn.kgc.spring05"></context:component-scan> <!--开启aspectj自动代理--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>6、测试
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-config3.xml") public class AllAdviceTest{ @Autowired Teacher teacher; @Test public void test01() { System.out.println(teacher.getClass()); String s = teacher.teachOnLine("html"); System.out.println("s = " + s); } }7、运行效果
码文不易,本篇文章就介绍到这里,如果想要学习更多Java系列知识,点击关注博主,博主带你零基础学习Java知识。与此同时,对于日常生活有困扰的朋友,欢迎阅读我的第四栏目:《国学周更—心性养成之路》,学习技术的同时,我们也注重了心性的养成。