AOP的核心概念,AOP配置的多种方式。
Spring AOP
- AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。
- 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
- AOP 其实就是代理模式的典型应用
相关概念
- 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
- 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。(被拦截到的方法)
- 通知(Advice): 在特定的连接点,AOP框架执行的动作。(增强连接点,拦截后要做的事情)
- 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。(指我们要对哪些连接点进行拦截的定义,所有的切入点都是连接点,但反之不成立,只有增强的连接点才是切入点)
- 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
- 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象
- AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理
- 织入(Weaving): 组装方面来创建一个被通知对象。(指把增强应用到目标对象来创建新的代理对象的过程)
AOP配置
基于 Java API 的方式
此配置方式需要实现相关的接口,例如 MethodBeforeAdvice 和 AfterReturningAdvice,并且在 XML 配置中定义相应的规则即可实现。
public class Person {
public Person findPerson() {
Person person = new Person(1, "JDK");
System.out.println("findPerson 被执行");
return person;
}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
private Integer id;
private String name;
}
再定义一个 advice 类,用于对拦截方法的调用之前和调用之后进行相关的业务处理
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("准备执行方法: " + method.getName());
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(method.getName() + " 方法执行结束");
}
然后需要在 application.xml 文件中配置相应的拦截规则
<!-- 定义 advisor -->
<bean id="myAdvice" class="org.springframework.advice.MyAdvice"></bean>
<!-- 配置规则,拦截方法名称为 find* -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="myAdvice"></property>
<property name="pattern" value="org.springframework.beans.*.find.*"></property>
</bean>
<!-- 定义 DefaultAdvisorAutoProxyCreator 使所有的 advisor 配置自动生效 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
测试类为
public class MyApplication {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application.xml");
Person person = context.getBean("person", Person.class);
person.findPerson();
}
}
准备执行方法: findPerson
findPerson 被执行
findPerson 方法执行结束
基于 @AspectJ 注解的方式
首先需要在项目中添加 aspectjweaver 的 jar 包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
然后开启自动代理功能,一共有两种方式
//配置Spring开启AOP注解的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
基于注解
Configuration
EnableAspectJAutoProxy
public class AppConfig {
}
之后我们需要声明拦截器的类和拦截方法,以及配置相应的拦截规则(定义切面和切点)
//将通知类bean通过注解注入IOC容器
@Componentl("logger")
//定义切面
@Aspect
public class MyAspectJ {
// 配置拦截类 Person(定义切点)
@Pointcut("execution(* org.springframework.beans.Person.*(..))")
public void pointCut() {
}
//定义通知
@Before("pointCut()")
public void doBefore() {
System.out.println("执行 doBefore 方法");
}
@After("pointCut()")
public void doAfter() {
System.out.println("执行 doAfter 方法");
然后在 application.xml 配置中添加注解类,最后添加一个需要拦截的方法
<bean class="org.springframework.advice.MyAspectJ"/>
package org.springframework.beans;
// 需要拦截的 Bean
public class Person {
public Person findPerson() {
Person person = new Person(1, "JDK");
System.out.println("执行 findPerson 方法");
return person;
}
// 获取其他方法
}
开启测试代码
public class MyApplication {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application.xml");
Person person = context.getBean("person", Person.class);
person.findPerson();
}
}
执行 doBefore 方法
执行 findPerson 方法
执行 doAfter 方法
基于 XML 标签的方式
把相关信息配置到 application.xml 中即可
<!-- 拦截处理类,即把通知bean对象也交给spring来管理 -->
<bean id="myPointcut" class="org.springframework.advice.MyPointcut"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 拦截规则配置,即切入点表达式 -->
<aop:pointcut id="pointcutConfig"
expression="execution(* org.springframework.beans.Person.*(..))"/>
<!-- 拦截方法配置
1. aspect标签中:id为切面的唯一标识
2. ref属性是指定通知类bean的id
3. method用于指定哪个方法是前置或后置通知
4. pointcut指定要拦截的方法
5. after-returning:后置,after-throwing:异常,after:最终
-->
<aop:aspect ref="myPointcut">
<aop:before method="doBefore" pointcut-ref="pointcutConfig"/>
<aop:after method="doAfter" pointcut-ref="pointcutConfig"/>
</aop:aspect>
</aop:config>
之后,添加一个普通的类来进行拦截业务的处理
public class MyPointcut {
public void doBefore() {
System.out.println("执行 doBefore 方法");
}
public void doAfter() {
System.out.println("执行 doAfter 方法");
}
}
拦截的方法和测试代码与第二种注解的方式相同
原理
Spring AOP 的原理其实很简单,它其实就是一个动态代理,我们在调用 getBean() 方法的时候返回的其实是代理类的实例,而这个代理类在 Spring 中使用的是 JDK Proxy 或 CgLib 实现的,它的核心代码在 DefaultAopProxyFactory.createAopProxy(…) 中
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否为接口
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
// 是接口使用 jdk 的代理
return new JdkDynamicAopProxy(config);
}
// 其他情况使用 CgLib 代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
// 忽略其他代码
}