说明
AOP、Spring AOP、Aspectj
AOP
- 在我们原来的的代码的基础上,方法执行前,方法返回后,方法出现异常时,进行拦截处理或者叫做增强
Spring AOP
- 基于动态代理实现,默认如果使用接口,使用jdk动态代理。如果没有实现接口,则使用CGLIB实现 用法可参考
- Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
- Spring AOP 需要依赖于 IOC 容器来管理。
- Spring AOP 只能基于容器中的Bean实现代理增强
- Spring 提供了 AspectJ 的支持 注:这里的支持只是规范的支持 底层还是spring
Aspectj
- AspectJ来自于Eclipse基金会
- 属于静态织入 注入时机可以是:
-
-
- Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:
-javaagent:xxx/xxx/aspectjweaver.jar
。
-
3.AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。
Spring AOP
Spring AOP沿用了AspectJ的相关概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。
如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
<aop />
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做
@AspectJ
,但是这个和 AspectJ 其实没啥关系。
Spring 1.2 中的配置
例子1
1.新增2个Advice
/** * @Project spring * @Description 记录方法入参 */ public class LogArgAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(String.format("方法名字:%s,入参:%s",method.getName(), Arrays.toString(args))); } }
/** * @Description 方法返回拦截 */ public class ReturnAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(String.format("方法:%s,返回值:%s",method.getName(),returnValue)); } }
2.配置被代理类
public interface StudentService { public Integer del(Long id); public boolean delAll(); }
public class StudentServiceImpl implements StudentService { @Override public Integer del(Long id) { System.out.println(String.format("执行删除id为:%s的数据",id)); return 1; } @Override public boolean delAll() { System.out.println("删除所有数据"); return true; } }
3.xml配置
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--定义2个advice--> <bean name="logArgAdvice" class="org.springframework.lq.aspect.LogArgAdvice"></bean> <bean name="returnAdvice" class="org.springframework.lq.aspect.ReturnAdvice"></bean> <!--配置实现studentServiceImpl的代理--> <bean name="studentServiceImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--代理接口--> <property name="proxyInterfaces"> <list> <value>org.springframework.lq.service.StudentService</value> </list> </property> <!--代理对象--> <property name="target" ref="studentServiceImpl" ></property> <!--配置拦截器 这里可以配置advice advisor interceptor--> <property name="interceptorNames"> <list> <value>logArgAdvice</value> <value>returnAdvice</value> </list> </property> </bean>
4.测试
@Test public void lqTEST() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {LQCONTEXT}, getClass()); StudentService studentService=ctx.getBean("studentServiceImplProxy",StudentService.class); studentService.del(1L); studentService.delAll(); }
输出:
例子2
例子1有个缺点就是,粒度太大,所有方法都实现了拦截.如果只需要对delAll进行增强呢 使用Advisor
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--配置Advisor--> <bean name="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <!--指定advice--> <property name="advice" ref="logArgAdvice"/> <!--需要拦截的方法 可以配置多个,号隔开--> <property name="mappedNames" value="delAll"/> </bean> <!--定义advice--> <bean name="logArgAdvice" class="org.springframework.lq.aspect.LogArgAdvice"></bean> <!--配置实现studentServiceImpl的代理--> <bean name="studentServiceImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--代理接口--> <property name="proxyInterfaces"> <list> <value>org.springframework.lq.service.StudentService</value> </list> </property> <!--代理对象--> <property name="target" ref="studentServiceImpl" ></property> <!--配置拦截器 这里可以配置advice advisor interceptor--> <property name="interceptorNames"> <list> <value>logAdvisor</value> </list> </property> </bean>
例子3
Interceptor 如果是advisor和advice都是方法的增强 我理解 Interceptor更倾向于方法的控制
1.定义一个拦截器
public class OperationInterceptor implements MethodInterceptor { /** * 拦截的方法 */ List<String> refuseMethod= Arrays.asList("delAll"); @Override public Object invoke(MethodInvocation invocation) throws Throwable { if(refuseMethod.contains(invocation.getMethod().getName())){ throw new Exception("无权访问"); } return invocation.proceed(); } }
2.定义xml
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--配置Interceptor--> <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor"> </bean> <!--配置实现studentServiceImpl的代理--> <bean name="studentServiceImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--代理接口--> <property name="proxyInterfaces"> <list> <value>org.springframework.lq.service.StudentService</value> </list> </property> <!--代理对象--> <property name="target" ref="studentServiceImpl" ></property> <!--配置拦截器 这里可以配置advice advisor interceptor--> <property name="interceptorNames"> <list> <value>operationInterceptor</value> </list> </property> </bean>
3.测试
例子4
例子3的问题是,对于每个类型我们都要配置一个代理,如果我们有需求,对ServiceImpl结尾的类生成代理呢 之类我们使用BeanNameAutoProxyCreator
1.xml配置
beanNames支持正则表达式配置
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--配置Interceptor--> <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor"> </bean> <!--配置实现studentServiceImpl的代理--> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="*ServiceImpl"></property> <!--配置拦截器 这里可以配置advice advisor interceptor--> <property name="interceptorNames"> <list> <value>operationInterceptor</value> </list> </property> </bean>
2.测试
红色部分可以发现我们可以直接使用基础类型了
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {LQCONTEXT}, getClass()); StudentService studentService=ctx.getBean(StudentService.class); studentService.del(1L); studentService.delAll();
例子5
如果我们需要让del开头的方法进行方法控制呢 使用RegexpMethodPointcutAdvisor
1.xml配置
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--配置Advisor--> <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!--指定advice--> <property name="advice" ref="logArgAdvice"/> <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开--> <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/> </bean> <!--定义advice--> <bean name="logArgAdvice" class="org.springframework.lq.aspect.LogArgAdvice"></bean> <!--配置实现studentServiceImpl的代理--> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="*ServiceImpl"></property> <!--配置拦截器 这里可以配置advice advisor interceptor--> <property name="interceptorNames"> <list> <value>logAdvisor</value> </list> </property> </bean>
例子6
是否觉得配置BeanNameAutoProxyCreator 很麻烦呢 可以使用DefaultAdvisorAutoProxyCreator
<!--代理类--> <bean name="studentServiceImpl" class="org.springframework.lq.service.StudentServiceImpl"></bean> <!--配置Advisor--> <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!--指定advice--> <property name="advice" ref="logArgAdvice"/> <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开--> <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/> </bean> <!--定义advice--> <bean name="logArgAdvice" class="org.springframework.lq.aspect.LogArgAdvice"></bean> <!--配置自动代理--> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
Spring 2.0 @AspectJ配置
1.引入包
注:虽然引入了Aspectj包,但是还是使用的是Spring API @Aspectj跟 Aspectj没有任何关系,只是spring AOP是根据Aspectj规则实现,用到了他的注解
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.11</version> </dependency>
如果是使用 Spring Boot 的话,添加以下依赖即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.xml配置
<aop:aspectj-autoproxy/>
也可以通过注解开启
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect。
注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean,首先它需要是一个 bean。
如:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> <!-- configure properties of aspect here as normal --> </bean>
@Aspect public class NotVeryUsefulAspect { }
3.配置Pointcut 一般翻译为切点
@Pointcut("execution(* transfer(..))")// the pointcut expression private void anyOldTransfer() {}// the pointcut signature
@Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的,除了 execution,我们再看看其他的几个比较常用的匹配方式:
-
within:指定所在类或所在包下面的方法(Spring AOP 独有)
如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")
-
@annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。
如 @Pointcut("execution(.*(..)) && @annotation(com.javadoop.annotation.Subscribe)")
-
bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)
如 @Pointcut("bean(*Service)")
上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。
对于 web 开发者,Spring 有个很好的建议,就是定义一个 SystemArchitecture:
@Aspect public class SystemArchitecture { // web 层 @Pointcut("within(com.javadoop.web..*)") public void inWebLayer() {} // service 层 @Pointcut("within(com.javadoop.service..*)") public void inServiceLayer() {} // dao 层 @Pointcut("within(com.javadoop.dao..*)") public void inDataAccessLayer() {} // service 实现,注意这里指的是方法实现,其实通常也可以使用 bean(*ServiceImpl) @Pointcut("execution(* com.javadoop..service.*.*(..))") public void businessService() {} // dao 实现 @Pointcut("execution(* com.javadoop.dao.*.*(..))") public void dataAccessOperation() {} }
上面这个 SystemArchitecture 很好理解,该 Aspect 定义了一堆的 Pointcut,随后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref="")。
配置 pointcut 就是配置我们需要拦截哪些方法。
配置
Advice配置
@Aspect public class AdviceExample { // 这里会用到我们前面说的 SystemArchitecture // 下面方法就是写拦截 "dao层实现" @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... 实现代码 } // 当然,我们也可以直接"内联"Pointcut,直接在这里定义 Pointcut // 把 Advice 和 Pointcut 合在一起了,但是这两个概念我们还是要区分清楚的 @Before("execution(* com.javadoop.dao.*.*(..))") public void doAccessCheck() { // ... 实现代码 } @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } @AfterReturning( pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便 // ... 实现代码 } // 异常返回 @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... 实现代码 } @AfterThrowing( pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... 实现代码 } // 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况 @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // 通常就像 finally 块一样使用,用来释放资源。 // 无论正常返回还是异常退出,都会被拦截到 } // 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情 @Around("com.javadoop.aop.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } } @Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()") public void logArgs(JoinPoint joinPoint) { System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs())); }
Spring 2.0 schema-based配置
解析<AOP/> 相关的xml在org.springframework.aop.config.AopNamespaceHandle 中
<aop:config>
<aop:aspect ref="logArgsAspect">
<aop:pointcut id="internalPointcut"
expression="com.javadoop.SystemArchitecture.businessService()" />
</aop:aspect>
</aop:config>