三.spring面向切面编程(AOP)

    技术2026-03-16  7

    三.AOP

    1.简介

    Aspect Orientied Programming

    面向切面编程

    使得应用易于扩展

    开闭原则

    将应用中所需要使用的交叉业务逻辑提取出来封装成切面

    由AOP容器在适当的时机

    将这些切面织入到具体的业务逻辑中

    2.目的

    解耦合 将具体的核心业务逻辑与交叉业务逻辑相分离 切面的复用 一个切面被多次使用 独立模块化 例如原本交叉业务逻辑做的打印操作将打印操作统一更换为日志操作只要切面改了,所有的业务均跟着改变独立模块化的前提是切面的复用 在不改变原有功能的基础上,增加新的功能

    3.动态代理

    AOP是基于动态代理来实现的

    动态代理是根据目标类的类加载器、代理的接口、交叉业务逻辑

    生成对应的目标类的代理类

    public class LogInvocationHandler implements InvocationHandler { private Object target; public LogInvocationHandler(Object target) { this.target = target; } /** * 交叉业务逻辑 * @param proxy 代理对象,完全没用 * @param method 需要代理的目标方法 * @param args 目标方法的参数列表 * @return 目标方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+method.getName()+"方法"); return method.invoke(target,args); } } public static void main(String[] args) { SomeService someService = (SomeService) Proxy.newProxyInstance( SomeServiceImpl.class.getClassLoader(), SomeServiceImpl.class.getInterfaces(), new LogInvocationHandler(new SomeServiceImpl()) ); someService.doSome(); System.out.println("---------------------------"); someService.doOther(); System.out.println("----------------------------------------"); OtherService otherService = (OtherService) Proxy.newProxyInstance( OtherServiceImpl.class.getClassLoader(), OtherServiceImpl.class.getInterfaces(), new LogInvocationHandler(new OtherServiceImpl()) ); otherService.doSome(); System.out.println("----------------------"); otherService.doOther(); }

    4.AOP1.X

    倾入性

    4-1 POM依赖

    <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency>

    4-2 通知类型

    通知类型描述接口前置通知在执行核心业务逻辑之前执行MethodBeforeAdvice后置通知在执行核心业务逻辑之后执行AfterReturningAdvice异常通知在执行核心业务逻辑出现异常后执行ThrowsAdvice环绕通知包含以上三种MethodInterceptor

    4-3 前置通知

    public class LogAdvice implements MethodBeforeAdvice { /** * 前置通知 * @param method 目标方法 * @param args 目标方法的参数列表 * @param target 目标类 * @throws Throwable */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+target+"中的"+method.getName()+"方法"); } }

    4-4 后置通知

    public class WelcomeAdvice implements AfterReturningAdvice { /** * 后置通知 * @param returnValue 目标方法的返回值,当方法没有返回值的时候,返回null * @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(target+"中的"+method.getName()+"方法执行完成,返回值为:"+returnValue); } }

    4-5 异常通知

    public class ExceptionAdvice implements ThrowsAdvice { /** * 异常通知 * ThrowsAdvice接口中提供了四个方法 * 这四个方法存在方法重载 * 我们只需要实现其中的任意一个即可 * 因此,此处不存在提示,需要手动编写方法 * @param method 目标方法 * @param args 方法参数列表 * @param target 目标类 * @param ex 异常 */ public void afterThrowing(Method method, Object[] args, Object target, Exception ex){ System.out.println(target+"中的"+method.getName()+"方法执行出现了异常,异常为:"+ex); } }

    4-6 环绕通知

    public class AroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 获取目标类 Object targte = invocation.getThis(); // 获取目标方法 Method method = invocation.getMethod(); // 获取目标方法的参数列表 Object[] args = invocation.getArguments(); Object returnValue = null; // 计算执行目标方法所耗费的时间 try { System.out.println("环绕通知之前置通知"); long begin = System.currentTimeMillis(); returnValue = invocation.proceed(); long end = System.currentTimeMillis(); System.out.println("环绕通知之后置通知,执行方法共花费了"+(end-begin)+"毫秒"); } catch (Throwable ex) { System.out.println("环绕通知之异常通知"); } return returnValue; } }

    4-7 配置文件

    <!-- 配置目标类 --> <bean id="someServiceTarget" class="aop03.SomeServiceImpl"></bean> <bean id="otherServiceTarget" class="aop03.OtherServiceImpl"></bean> <!-- 配置通知类 --> <bean id="logAdvice" class="aop03.LogAdvice"></bean> <bean id="welcomeAdvice" class="aop03.WelcomeAdvice"></bean> <bean id="exceptionAdvice" class="aop03.ExceptionAdvice"></bean> <bean id="aroundAdvice" class="aop03.AroundAdvice"></bean> <!-- 配置代理类 通过Spring提供的FactoryBean帮我们将交叉业务逻辑织入到核心业务逻辑中 使用的是动态代理的机制 --> <bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 配置目标类,通过目标类可以获取目标类的类加载器 --> <property name="target" ref="someServiceTarget"></property> <!-- 配置实现的接口列表 其值是一个Class数组 由于此处的值只有一个,可以使用简单值进行装配 其值是接口的包名.类名 --> <property name="proxyInterfaces" value="aop03.SomeService"></property> <!-- 配置交叉业务逻辑 在此处即为通知类 其值是一个可变长字符串 字符串的值对应的是通知类的id值 --> <property name="interceptorNames"> <list> <!--<value>logAdvice</value>--> <!--<value>welcomeAdvice</value>--> <!--<value>exceptionAdvice</value>--> <value>aroundAdvice</value> </list> </property> </bean> <bean id="otherService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="otherServiceTarget"></property> <property name="proxyInterfaces" value="aop03.OtherService"></property> <property name="interceptorNames"> <list> <value>logAdvice</value> <value>welcomeAdvice</value> </list> </property> </bean>

    4-8 通知的优先级

    在AOP1.X中,通知的优先级是根据配置的位置而定的

    谁的配置在前,谁的优先级高

    优先级高的前置通知执行的时机更前

    优先级高的后置通知执行的时机更后

    5.AOP2.X

    5-1 特征

    非倾入性完全基于配置引入了新的命名空间引入了切点表达式

    5-2 POM依赖

    <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.4</version> </dependency>

    5-3 通知类型

    前置通知 在执行核心业务逻辑之前执行 后置通知 正常返回通知 当方法正常执行结束,没有遇到异常的时候执行 异常通知 当方法运行出现异常的时候执行 后置通知 不管方法是否遇到异常均会执行 环绕通知 包含以上所有

    5-4 无参通知

    public class LogAdvice { public void a(){ System.out.println("前置通知"); } public void b(){ System.out.println("正常返回通知"); } public void c(){ System.out.println("异常通知"); } public void d(){ System.out.println("后置通知"); } } <!-- 定义目标类 --> <bean id="someService" class="aop04.SomeServiceImpl"></bean> <bean id="otherService" class="aop04.OtherServiceImpl"></bean> <!-- 定义通知 --> <bean id="logAdvice" class="aop04.LogAdvice"></bean> <!-- 引入了新的命名空间AOP,用于定义AOP的相关配置 引入了切点表达式,用于表示当前切面应用于哪些业务逻辑 --> <aop:config> <!-- 定义切入点 表示需要使用对应交叉业务逻辑的核心业务逻辑有些哪些 简单来讲,即哪些类中的哪些方法需要使用交叉业务逻辑 id属性:当前切入点的唯一性标识符 expression属性:指定切点表达式 --> <!-- 匹配SomeServiceImpl中的所有方法 --> <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut> <!-- 定义切面,即通知 ref属性:定义当前通知所使用的通知类是谁 其属性值对应的是bean的id属性值 before子标签:配置前置通知 after-returning子标签:正常返回通知 after-throwing子标签:异常通知 after:后置通知 method属性:当前通知所对应方法是bean中哪一个 pointcut-ref属性:当前切面所使用的切入点是谁 --> <aop:aspect ref="logAdvice"> <aop:before method="a" pointcut-ref="pc1"/> <aop:after-returning method="b" pointcut-ref="pc1"/> <aop:after-throwing method="c" pointcut-ref="pc1"/> <aop:after method="d" pointcut-ref="pc1"/> </aop:aspect> </aop:config>

    5-5 切点表达式

    within 匹配某个类中的所有方法语法:within(包名.类名) execution 匹配你想要的一切可以是类,可以是方法语法:execution(返回值类型 包名.类名.方法名(参数列表))支持通配符用法 * 用法一:匹配0或者多个字符用法二:匹配一个单词 .. 表示匹配0或者多个参数 支持连接条件 连接条件可以使用within,也可以使用executionand:且的意思,多个条件必须同时满足or:或的意思,多个条件只要满足任意一个即可not:非的意思,匹配不满足条件的方法 使用not前面必须有空格 <!-- 匹配SomeServiceImpl中的所有方法 --> <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut> <!-- 匹配SomeServiceImpl中的所有无参无返回值的方法 --> <aop:pointcut id="pc1" expression="execution(void aop04.SomeServiceImpl.doSome())"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(java.lang.String aop04.SomeServiceImpl.doSome(java.lang.String))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(*))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(..))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.*.*(..))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut> <aop:pointcut id="pc2" expression="execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut> <!-- 匹配SomeServiceImpl中的所有无参方法 --> <aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*())"></aop:pointcut> <!-- 匹配SomeServiceImpl中除了doSome以外的所有方法 --> <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) and not execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut> <!-- 匹配SomeServiceImpl中的所有方法以及OtherServiceImpl中除了doOther以外的方法 --> <aop:pointcut id="pc1" expression=" not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut> <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) or within(aop04.OtherServiceImpl) and not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut> <!-- 匹配aop04包下所有除了doSome以外的方法 --> <aop:pointcut id="pc1" expression="execution(* aop04.*.*(..)) and not execution(* aop04.*.doSome(..))"></aop:pointcut>

    5-6 有参通知

    在AOP2.X中,如何获取与目标方法相关的信息

    此时需要使用有参通知来实现

    使用有参通知的时候,其参数必须是:JoinPoint

    public class LogAdvice { public void before(JoinPoint jp){ // 获取目标类 Object target = jp.getThis(); // 获取目标方法 Signature signature = jp.getSignature(); // 获取目标方法参数列表 Object[] args = jp.getArgs(); System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行"); } public void afterReturning(JoinPoint jp, Object returnValue){ System.out.println("正常返回通知:"+jp.getSignature().getName()+"方法执行完成,方法返回值为:"+returnValue); } public void afterThrowing(JoinPoint jp, Exception e){ System.out.println("异常通知:"+jp.getSignature().getName()+"方法执行出现了异常,异常为:"+e); } public void after(JoinPoint jp){ System.out.println("后置通知:"+jp.getSignature().getName()+"方法执行结束"); } } <bean id="someService" class="aop05.SomeServiceImpl"></bean> <bean id="logAdvice" class="aop05.LogAdvice"></bean> <aop:config> <aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/> <aop:aspect ref="logAdvice"> <aop:before method="before" pointcut-ref="pc1"/> <!-- returning属性:指定当前方法中的哪一个参数作为目标方法的返回值 --> <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/> <!-- throwing属性:指定当前方法中的哪一个参数作为接收目标方法异常的参数 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/> <aop:after method="after" pointcut-ref="pc1"/> </aop:aspect> </aop:config>

    5-7 环绕通知

    环绕通知对方法存在三个要求

    方法必须有返回值 返回值类型必须是Object表示的就是目标方法的返回值 方法必须有参数 参数为:proceedingJoinPoint该参数是JoinPoint的子类可以获取与目标方法相关的信息可以用于执行目标方法 方法必须抛出Throwable public class AroundAdvice { public Object around(ProceedingJoinPoint jp) throws Throwable{ Object returnValue = null; System.out.println("环绕通知之前置通知"); long begin = System.currentTimeMillis(); try { returnValue = jp.proceed(); long end = System.currentTimeMillis(); System.out.println("环绕通知之正常返回通知,执行方法共花费了"+(end-begin)+"毫秒"); } catch (Throwable throwable) { System.out.println("环绕通知之异常通知"); } finally { System.out.println("环绕通知之后置通知"); } return returnValue; } } <bean id="someService" class="aop05.SomeServiceImpl"></bean> <bean id="aroundAdvice" class="aop05.AroundAdvice"></bean> <aop:config> <aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/> <aop:aspect ref="aroundAdvice"> <aop:around method="around" pointcut-ref="pc1"/> </aop:aspect> </aop:config>

    5-8 优先级

    默认情况下,AOP2.X的优先级与AOP1.X一致

    根据配置的位置而定

    谁的配置在前,谁的优先级高

    优先级高的前置通知执行时机更前

    优先级高的后置通知执行时机更后

    在AOP2.X可以手动配置优先级

    在切面中进行配置,在aop:aspect标签中通过order属性定义优先级

    值越小,优先级越高

    <aop:aspect ref="logAdvice" order="2"> <aop:before method="before" pointcut-ref="pc1"/> <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/> <aop:after method="after" pointcut-ref="pc1"/> </aop:aspect> <aop:aspect ref="aroundAdvice" order="1"> <aop:around method="around" pointcut-ref="pc1"/> </aop:aspect>
    Processed: 0.014, SQL: 9