文章目录
一、AOP简介
1. AOP概念
AOP,Aspect Oriented Programming ,面向切面编程,是对面向对象编程 OOP 的升华。 OOP 是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而 AOP 是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程。
AOP是指在程序的运行期间动态的将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。
2. AOP思想的实现方案
动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时,再嵌入执行其他增强逻辑或其他对象的方法
3. 模拟AOP的基础代码
其实在之前学习BeanPostProcessor 时,在 BeanPostProcessor 的 after 方法中使用动态代理对 Bean 进行了增强,实际存储到单例池 singleObjects 中的不是当前目标对象本身,而是当前目标对象的代理对象 Proxy ,这样在调用目标对象方法时,实际调用的是代理对象 Proxy 的同名方法,起到了目标方法前后都进行增强的功能,对该方式进行一下优化,将增强的方法提取到一个增强类中,且只对 com.itheima.service.impl 包下的任何类的任何方法进行增强。
// 自定义增强类
public class MyAdvice {
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
public void afterAdvice() {
System.out.println("afterAdvice...");
}
}
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
// 注入 Spring 容器对象
private ApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 获得 Advice 对象
MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);
String packageName = bean.getClass().getPackage().getName();
if("com.itheima.service.impl".equals(packageName)) {
// 对 Bean 进行动态代理,返回的是 Proxy 代理对象
Object proxyBean = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
// 执行增强对象的before方法
myAdvice.beforeAdvice();
// 执行目标对象的目标方法
Object result = method.invoke(bean, args);
// 执行增强对象的after方法
myAdvice.afterAdvice();
return result;
}
);
// 返回代理对象
return proxyBean;
}
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
4. AOP相关概念
二、基于XML方式配置的AOP
1. XML方式配置AOP
前面我们自己编写的AOP 基础代码还是存在一些问题的,主要如下:
- 被增强的包名在代码写死了
- 通知对象的方法在代码中写死了
通过配置文件的方式去解决上述问题
- 配置哪些包、哪些类、哪些方法需要被增强
- 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强配置方式的设计、配置文件(注解)的解析工作,Spring 已经帮我们封装好了
xml方式配置 AOP 的步骤:
- 导入 AOP 相关坐标;
- 准备目标类、准备增强类,并配置给 Spring 管理;
- 配置切点表达式(哪些方法被增强);
- 配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
(1)导入 AOP 相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
(2)准备目标类、准备增强类,并配置给 Spring 管理
(3)配置切点表达式(哪些方法被增强)
(4)配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)
<!--aop配置-->
<aop:config>
<!--配置切点表达式,目的是要指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(void com.itheima.service.impl.UserServiceImpl.show1())"/>
<!--配置织入,目的是要执行哪些切点与那些通知进行结合-->
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
<!--后置通知-->
<aop:after-returning method="afterAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
2. XML方式AOP配置详解
xml配置 AOP 的方式还是比较简单的,下面看一下 AOP 详细配置的细节:
- 切点表达式的配置方式
- 切点表达式的配置语法
- 通知的类型
- AOP配置的两种方式
切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))
其中:
- 访问修饰符可以省略不写;
- 返回值类型、某一级包名、类名、方法名可以使用 * 表示任意;
- 包名与类名之间使用单点. 表示该包下的类,使用双点 … 表示该包及其子包下的类;
- 参数列表可以使用两个点… 表示任意参数。
切点表达式举几个例子方便理解
// 表示访问修饰符为 public 、无返回值、在 com.itheima.aop 包下的 TargetImpl 类的无参方法 show
execution(public void com.itheima.aop.TargetImpl.show())
// 表述 com.itheima.aop 包下的 TargetImpl 类的任意方法
execution(* com.itheima.aop.TargetImpl.*(..))
// 表示 com.itheima.aop 包下的任意类的任意方法
execution(* com.itheima.aop.*.*(..))
// 表示 com.itheima.aop 包及其子包下的任意类的任意方法
execution(* com.itheima.aop..*.*(..))
// 表示任意包中的任意类的任意方法
execution(* *..*.*(..))
AspectJ的通知有以下五种类型
环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前的增强....");
proceedingJoinPoint.proceed();// 执行目标方法
System.out.println("环绕后的增强....");
}
<aop:around method="around" pointcut-ref="myPointcut"/>
异常通知,当目标方法抛出异常时,异常通知方法执行,且后置通知和环绕通知不再执行
public void afterThrowing(){
System.out.println("目标方法抛出异常时,后置通知和环绕通知不再执行");
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
最终通知,类似异常捕获中的finally ,不管目标方法有没有异常,最终通知都会执行
public void after() {
System.out.println("最终的增强....");
}
<aop:after method="after" pointcut-ref="myPointcut"/>
通知方法在被调用时,Spring 可以为其传递一些必要的参数
JoinPoint对象
public void 通知方法名称(JoinPoint joinPoint) {
// 获得目标方法的参数
System.out.println(joinPoint.getArgs());
// 获得目标对象
System.out.println(joinPoint.getTarget());
// 获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
}
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获得目标方法的参数
System.out.println(joinPoint.getArgs());
// 获得目标对象
System.out.println(joinPoint.getTarget());
// 获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
// 执行目标方法
Object result = joinPoint.proceed();
// 返回目标方法返回值
return result;
}
Throwable对象
public void afterThrowing(JoinPoint joinPoint, Throwable th) {
// 获得异常信息
System.out.println("异常对象是:" + th + ",异常信息是:" + th.getMessage());
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="th"/>
AOP的另一种配置方式,该方式需要通知类实现 Advice 的子功能接口
public interface Advice {
}
Advice的子功能接口
例如:通知类实现了前置通知和后置通知接口
public class Advices implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知..........");
}
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置通知...........");
}
}
切面使用 advisor 标签配置
<aop:config>
<!--将通知和切点进行结合-->
<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
又例如:通知类实现了方法拦截器接口
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前******");
// 执行目标方法
Object res = methodInvocation.getMethod().invoke(methodInvocation.getThis(), methodInvocation.getArguments());
System.out.println("环绕后******");
return res;
}
}
切面使用advisor 标签配置
<aop:config>
<!--将通知和切点进行结合-->
<aop:advisor advice-ref="myMethodInterceptor" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
使用 aspect 和 advisor 配置区别如下:
- 配置语法不同:
<!--使用 advisor 配置-->
<aop:config>
<!--advice-ref: 通知Bean 的 id-->
<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
<!--使用 aspect 配置-->
<aop:config>
<!--ref: 通知Bean 的 id-->
<aop:aspect ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
- 通知类的定义要求不同,advisor 通知类需要实现 Advice 的子功能接口:
public class Advices implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知..........");
}
}
aspect不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可,更加灵活方便:
public class Advices {
public void before() {
System.out.println("前置通知..........");
}
}
- 可配置的切面数量不同
- 一个advisor 只能配置一个固定通知和一个切点表达式;
- 一个aspect 可以配置多个通知和多个切点表达式任意组合,粒度更细。
- 使用场景不同
- 如果通知类型多、允许随意搭配情况下可以使用aspect 进行配置;
- 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用 advisor 进行配置;
- 在通知类型已经固定,不用人为指定通知类型时,可以使用 advisor 进行配置,例如后面要学习的 Spring 事务控制的配置;
由于实际开发中,自定义 aop 功能的配置大多使用 aspect 的配置方式,所以我们后面主要讲解 aspect 的配置,advisor 是为了后面 Spring 声明式事务控制做铺垫,此处大家了解即可。
3. XML方式AOP原理剖析
通过
xml 方式配置 AOP 时,我们引入了 AOP 的命名空间,根据前面章节讲解的,要去找 spring-aop 包下的 META -INF ,在去找 spring.handlers 文件
最终加载的是AopNamespaceHandler ,该 Handler 的 init 方法中注册了 config 标签对应的解析器
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
以ConfigBeanDefinitionParser 作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator,进入到 Spring 容器中,那该类作用是什么呢?看一下集成体系图
AspectJAwareAdvisorAutoProxyCreator的上上级父类 AbstractAutoProxyCreator 中的postProcessAfterInitialization 方法
// 参数 bean :为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 如果需要被增强,则 wrapIfNecessary 方法最终返回的就是一个 Proxy 对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
通过断点方式观察,当 bean 是匹配切点表达式时, this.wrapIfNecessary (bean, beanName , cacheKey 返回的是一个 JDKDynamicAopProxy
可以再深入一点,对 wrapIfNecessary 再剖析一下,看看是不是我们熟知的通过 JDK 的
Proxy.newProxyInstance ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 的方式创建的代理对象呢?经过如下一系列源码跟踪
动态代理的实现的选择,在调用getProxy () 方法时,我们可选用的 AopProxy 接口有两个实现类,如上图,这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib 的。
JDK的动态代理代码,之前已经写过了,下面看一下 Cglib 基于超类的动态代理
Target target = new Target(); // 目标对象
Advices advices = new Advices(); // 通知对象
Enhancer enhancer = new Enhancer(); // 增强器对象
enhancer.setSuperclass(Target.class); // 增强器设置父类
// 增强器设置回调
enhancer.setCallback((MethodInterceptor )(o, method, objects, methodProxy) -> {
advices.before();
Object result = method.invoke(target, objects);
advices.afterReturning();
return result;
});
// 创建代理对象
Target targetProxy = (Target) enhancer.create();
// 测试
String result = targetProxy.show("haohao");
三、基于注解方式配置的AOP
1. 注解方式配置AOP
Spring的 AOP 也提供了注解方式配置,使用相应的注解替代之前的 xml 配置, xml 配置 AOP 时,我们主要配置了三部分:目标类被 Spring 容器管理、通知类被 Spring 管理、通知与切点的织入(切面),如下:
目标类被Spring 容器管理、通知类被 Spring 管理
其实配置 aop 主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么
2. 注解方式AOP配置详解
各种注解方式通知类型
切点表达式的抽取,使用一个空方法,将切点表达式标注在空方法上,其他通知方法引用即可
3. 注解方式AOP原理剖析
之前在使用xml 配置 AOP 时,是借助的 Spring 的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了< aop:config> 标签,而该标签最终加载了名为 AspectJAwareAdvisorAutoProxyCreator 的 BeanPostProcessor,最终,在该 BeanPostProcessor 中完成了代理对象的生成。
同样,从aspectj-autoproxy 标签的解析器入手
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
而 AspectJAutoProxyBeanDefinitionParser 代码内部,最终也是执行了和 xml 方式 AOP 一样的代码
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class , registry, source);
如果核心配置使用的是配置类的话,需要配置注解方式的 aop 自动代理
@Configuration
@ComponentScan("com.itheima.aop")
@EnableAspectJAutoProxy // 第三步
public class ApplicationContextConfig {
}
查看@EnableAspectJAutoProxy 源码,使用的也是 @Import 导入相关解析类
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({
AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
使用@Import 导入的 AspectJAutoProxyRegistrar 源码,一路追踪下去,最终还是注册了AnnotationAwareAspectJAutoProxyCreator 这个类。
AspectJAutoProxyRegistrar 源码:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
AspectJAutoProxyRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// ...
}
}
进入AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 最终还是注册了AnnotationAwareAspectJAutoProxyCreator 这个类
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
xml方式与注解方式原理对比