【spring框架】AspectJ动态代理实现AOP
1 前言
在 spring2.0 以上版本中,可以使用基于 AspectJ 注解配置 AOP,常用的配置 AOP 的注解如下:
- @Aspect:标注一个类为切面
- @Order:标注切面优先级
- @Pointcut:标注一个公共切入点
- @Before:前置通知,作用于方法执行之前
- @After:后置通知,作用于方法的finally语句块,即不管方法有没有异常都会执行,通常用于关闭资源
- @AfterReturning:返回通知,作用于方法执行之后
- @AfterThrowing:异常通知,作用于方法抛出异常时
- @Around:环绕通知
注意:要想让 IOC 容器管理切面,还需要给切面和被代理类标注 @Component,其用法见通过注解配置bean
需要导入的包如下:
其中,com 开头的包为 AspectJ 动态代理核心包,下载如下:
com.springsource.net.sf.cglib-2.2.0、com.springsource.org.aopalliance-1.0.0、com.springsource.org.aspectj.weaver-1.7.2.RELEASE
2 案例
2.1 前置通知、后置通知、返回通知、异常通知
Comp.java
package com.compute;
public interface Comp {
public int add(int a,int b);
public int div(int a,int b);
}
CompImp.java
package com.compute;
import org.springframework.stereotype.Component;
@Component
public class CompImp implements Comp{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public int div(int a, int b) {
return a/b;
}
}
注意:CompImp类前加 @Component 注解,是为了将 bean 交给 IOC 容器管理
Logger.java
package com.compute;
import java.util.Arrays;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Logger {
//前置通知:作用于方法执行之前
@Before(value="execution(* com.compute.*.*(..))") //对com.compute包下的所有类的所有方法起作用
public void beforeMethod(JoinPoint joinPoint) {
Object[] args=joinPoint.getArgs(); //获取方法参数
String methodName=joinPoint.getSignature().getName(); //获取方法名
System.out.println("前置通知:method:"+methodName+", args:"+Arrays.toString(args));
}
//后置通知:作用于方法的finally语句块,即不管方法有没有异常都会执行,通常用于关闭资源
@After(value="execution(* com.compute.*.*(..))") //对com.compute包下的所有类的所有方法起作用
public void afterMethod() {
System.out.println("后置通知");
}
//返回通知:作用于方法执行之后
@AfterReturning(value="execution(* com.compute.*.*(..))",returning="result") //此处的result和下面方法的result必须同名
public void afterReturning(JoinPoint joinPoint,Object result) {
String methodName=joinPoint.getSignature().getName();
System.out.println("返回通知:method:"+methodName+", result:"+result);
}
//异常通知:作用于方法抛出异常时
@AfterThrowing(value="execution(* com.compute.*.*(..))",throwing="e") //此处e和下面方法的e必须同名
public void afterThrowing(Exception e) {
System.out.println("异常通知:"+e);
}
}
注意:Logger类前加 @Aspect 注解,是为了标注此类为切面;@Before、@After、@AfterReturning、@AfterThrowing中的 value 值用于指定该通知作用于具体类的具体方法,如果只需给 CompImp 类的 add 方法添加通知,如下:
@Before(value="execution(public int com.compute.CompImp.add(int, int))") //只对CompImp类的add方法起作用
comp.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"
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 http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.compute"></context:component-scan>
<aop:aspectj-autoproxy />
</beans>
注意:需要导入 context 和 aop 命名空间,context:component-scan 用于扫描组件,即对标有 @Compnent 的类生成 bean 交给 IOC 容器管理;aop:aspectj-autoproxy 用于开启AspectJ的自动代理功能。
Test.java
package com.compute;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
Comp comp=ac.getBean("compImp",Comp.class);
System.out.println(comp.getClass().getName()); //打印代理类的类名
int result=comp.add(1,2);
System.out.println(result);
result=comp.div(2,0);
System.out.println(result);
}
}
运行结果:
com.sun.proxy.$Proxy11
前置通知:method:add, args:[1, 2]
后置通知
返回通知:method:add, result:3
3
前置通知:method:div, args:[2, 0]
后置通知
异常通知:java.lang.ArithmeticException: / by zero
拓展延伸
当有很多通知作用于同一切入点时,可以通过 @Pointcut 标注公共切入点,如下:
Logger.java
package com.compute;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Logger {
@Pointcut(value="execution(* com.compute.*.*(..))") //对com.compute包下的所有类的所有方法起作用
public void pointcut() {} //方法命名可以随意
//前置通知:作用于方法执行之前
@Before(value="pointcut()")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args=joinPoint.getArgs();
String methodName=joinPoint.getSignature().getName();
System.out.println("前置通知:method:"+methodName+", args:"+Arrays.toString(args));
}
//后置通知:作用于方法的finally语句块,即不管方法有没有异常都会执行,通常用于关闭资源
@After(value="pointcut()")
public void afterMethod() {
System.out.println("后置通知");
}
//返回通知:作用于方法执行之后
@AfterReturning(value="pointcut()",returning="result")
public void afterReturning(JoinPoint joinPoint,Object result) {
String methodName=joinPoint.getSignature().getName();
System.out.println("返回通知:method:"+methodName+", result:"+result);
}
//异常通知:作用于方法抛出异常时
@AfterThrowing(value="pointcut()",throwing="e")
public void afterThrowing(Exception e) {
System.out.println("异常通知:"+e);
}
}
2.2 环绕通知
本节仅介绍 Logger.java 其他类和配置文件见2.1节
Logger.java
package com.compute;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Logger {
//环绕通知:作用于方法抛出异常时
@Around(value="execution(* com.compute.*.*(..))") //对com.compute包下的所有类的所有方法起作用
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
Object result=null;
try {
System.out.println("前置通知");
result=joinPoint.proceed(); //执行方法
System.out.println("返回通知");
return result;
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
}finally {
System.out.println("后置通知");
}
return -1;
}
}
运行结果:
com.sun.proxy.$Proxy7
前置通知
返回通知
后置通知
3
前置通知
异常通知
java.lang.ArithmeticException: / by zero
at com.compute.CompImp.div(CompImp.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
at com.compute.Logger.aroundMethod(Logger.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy7.div(Unknown Source)
at com.compute.Test.main(Test.java:13)
后置通知
-1
2.3 切面优先级
当有多个切面时,可以通过 @Order 注解定义切面的优先级。
本节仅介绍LoggerA.java、LoggerB.java以及Test.java,Comp.java、CompImp.java、comp.xml见2.1节。
LoggerA.java
package com.compute;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(2) //值越小,优先级越高,默认值为2147483647(2^31-1)
public class LoggerA {
@Before(value="execution(* com.compute.*.*(..))")
public void beforeMethod() {
System.out.println("前置通知:LoggerA");
}
}
LoggerB.java
package com.compute;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(1) //值越小,优先级越高,默认值为2147483647(2^31-1)
public class LoggerB {
@Before(value="execution(* com.compute.*.*(..))")
public void beforeMethod() {
System.out.println("前置通知:LoggerB");
}
}
Test.java
package com.compute;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
Comp comp=ac.getBean("compImp",Comp.class);
int result=comp.add(1,2);
System.out.println(result);
}
}
运行结果:
前置通知:LoggerB
前置通知:LoggerA
3
相关文章
- 面试题_Spring高级篇
- spring学习笔记(6)AOP前夕[1]jdk动态代理实例解析
- spring学习笔记(13)基于Schema配置AOP详解
- spring学习笔记(9)AOP基本概念
- Spring4.1新特性——Spring MVC增强
- Spring之bean二生命周期
- Spring之bean一基础
- spring: 使用Aspectj代理EnabelAspectjAutoProxy
- Spring -- 自定义转换器
- Spring AOP源码分析(二)JDK动态代理和CGLIB介绍
- [Spring学习笔记 3 ] spring 注解详解,完全注解,常用注解
- spring boot:使用mybatis访问多个mysql数据源/查看Hikari连接池的统计信息(spring boot 2.3.1)
- Spring AOP编程-aspectJ代理方式选择
- 通过JMX监控Spring Boot应用
- Spring读源码系列之AOP--07---aop自动代理创建器(拿下AOP的最后一击)
- Spring事务王国概览
- Spring读源码系列之AOP--08--aop执行完整源码流程之自动代理创建器导入的两种方式
- Spring中的代理模式
- Spring多数据源分布式事务管理/springmvc+spring+atomikos[jta]+druid+mybatis
- 使用CXF和spring搭建webService服务
- Spring+SpringMVC+Mybatis(开发必备技能)04、mybatis自动生成mapper_dao_model(包含工具与视频讲解) 纯绿色版本、配套使用视频,100%运行成功
- 【多数据源动态切换】Spring Boot中实现多数据源动态切换效果(2):通过开源项目Dynamic Datasource Spring Boot Starter实现
- Spring Boot 之spring.factories
- 学习Spring Boot:(十四)spring-shiro的密码加密
- A Comparison Between Spring and Spring Boot
- 009-Spring Boot 事件监听、监听器配置与方式、spring、Spring boot内置事件
- Spring CGLlB动态代理(附带实例)
- 【Spring源码学习】spring IOC容器管理
- 【java】Spring Boot --spring boot项目整合xxl-job