概述
Spring
的两大核心: IoC
和 AOP
, IoC
作为 Spring
的根基,通过大量的扩展点让系统轻而易举的就可以实现良好的扩展性,而 AOP
和 IoC
结合在一起,类似于发生强大化学反应一样,将 Spring
的功能性又提高了一个层次。 Spring
中也有大量使用 AOP
场景,比如 @Configuration
、数据库事务、 mybatis mapper
接口注入等等。
AOP
全称 Aspect Oriented Programming
,即面向切面编程,其并非 Spring
独有,作为一种对 OOP
编程思想的补充,其也有自己的标准规范并有独立的组织进行维护。
根据织入时机的不同, AOP
又可以分为三类:
-
编译时织入:
ApectJ
主要采用的就是编译时织入方式,这种一般使用特定的编译器方式实现; -
类加载时织入:这种一般都是依赖
JVM Instruments
技术实现,Spring中也有对这种技术支持,具体可以了解下LoadTimeWeaver
; -
AOP Spring AOP JDK动态代理 CGLIB动态代理
AOP
标准规范是由独立的组织机构进行维护,其涉及到的核心概念主要如下:
-
JoinPoint AOP AOP
-
Pointcut Spring Pointcut
-
通知(
Advice
):在连接点处需要织入的增强代码逻辑封装; -
Aspect Advice Pointcut Spring Advisor
-
织入(
Weaving
):织入是在适当的位置将切面插入到应用程序代码中的过程,就是上面说的编译时织入、类加载时织入和动态织入; -
目标对象(
target
):AOP
代理增强的原生对象;
基础API
Spring AOP
很多人不能很好的理解、使用,一方面是因为 AOP
涉及的概念可能比较抽象,不容易理解;另外一方面你对 Spring AOP
涉及到的一些基础 API
不熟悉。下面我们就对 Spring AOP
中最核心的一些 API
,由底向上,由基础到高级方式一步步分析。
Enhancer
Spring AOP
主要使用的是动态代理方式实现,动态代理实现主要包括两种: jdk动态代理
和 cglib动态代理
。 jdk动态代理
方式比较熟悉,下面就来看下 cglib动态代理
如何实现。
Spring
中提供了一个 工具 类: Enhancer
, Spring
中主要就是利用该工具类创建 cglib动态代理
。下面我们通过一个案例看下其基本使用:
1、创建 Callback
回调接口类,该接口中就可以实现增强逻辑:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
before(method);//前置通知
Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行
after(method, ret);//后置通知
return ret;
} catch (Exception e) {
exception();//异常通知
} finally {
afterReturning();//方法返回通知
}
return null;
}
//前置增强
private void before(Method method) {
System.out.printf("before execute:%s\r\n", method.getName());
}
//后置增强
private void after(Method method, Object ret) {
System.out.printf("after execute:%s, ret:%s\r\n", method.getName(), ret);
}
//异常增强
private void exception() {
System.out.println("execute failure");
}
//after返回增强
private void afterReturning() {
System.out.println("execute finish");
}
}
2、编写测试:
//NoOp.INSTANCE:NoOp回调把对方法调用直接委派给这个方法在父类中的实现,即可理解为真实对象直接调用方法,没有任何增强
private static final Callback[] CALLBACKS = new Callback[] {
new MyMethodInterceptor(),
NoOp.INSTANCE
};
public void test() {
//创建Enhancer实例
Enhancer enhancer = new Enhancer();
//cglib是基于继承方式代理,superClass就是基于哪个类型父类进行增强,创建出来的对象就是该类型子类
enhancer.setSuperclass(UserServiceImpl.class);
//CallbackFilter主要用于过滤不同Method使用不同的Callback
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.getDeclaringClass() == Object.class) {
return 1;//使用Callback数组下标是1的
}
return 0;//使用Callback数组下标是0的
}
});
//设置Callback数组,Callback就是封装的增强逻辑
enhancer.setCallbacks(CALLBACKS);
//创建代理对象
UserService proxyObj = (UserService) enhancer.create();
System.out.println(proxyObj.say("zhangsan"));
}
通过上面 enhancer.create()
这条语句,就可以为目标类创建一个 cglib动态代理
,通过 Callback
回调方式将各种增强逻辑织入到代理实例中。
还可以使用 Enhancer.createClass()
方法只创建出代理类型,然后自己通过反射方式创建对象。这时,需要注意:
1、这时就不能使用 setCallbacks()
设置 Callback
数组,而是使用 setCallbackTypes()
设置 Callback
对应的类型;
2、 Enhancer.createClass()
执行完成后,再通过 Enhancer.registerStaticCallbacks(clz, CALLBACKS)
方式设置 Callback
数组;
enhancer.setInterfaces()
可用于设置生成的代理类必须实现的接口,比如你可以不设置 superclass
,只设置 interfaces
,这时也是可以正常创建出基于这个接口的动态代理实例,但是这时就要注意不能触发目标对象方法执行,如 methodProxy.invokeSuper
执行会报错,如下:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
before(method);//前置通知
//Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行
after(method, ret);//后置通知
return ret;
} catch (Exception e) {
exception();//异常通知
} finally {
afterReturning();//方法返回通知
}
return null;
}
基于接口创建的代理实例还是非常有用的,比如 mybatis mapper
就是一个没有实现类的接口,但是在 spring
中却可以依赖注入到 service bean
中,其中就是利用到上面基于接口创建动态代理的思想,注入进来的其实就是基于接口的动态代理,然后调用接口中方法时就可以进行拦截,获取到具体调用方法签名信息以及参数信息,基于这些数据进行业务逻辑处理。
invoke和invokeSuper方法区别
Callback#intercept()
回调方法中执行 methodProxy.invokeSuper()
和 methodProxy.invoke()
是有很大区别的,而且看不到执行流程,所以这里涉及的逻辑非常绕。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
Object ret = methodProxy.invokeSuper(obj, args);
Object ret = methodProxy.invoke(delete, args);
}
大致区别如下图:
客户端触发代理对象 say
方法调用,首先进入代理对象中的同名方法,然后进入方法拦截对象 MethodInterceptor
,这里会出现两种情况:
-
invokeSuper super super.say()
-
invoke target target.say
invokeSuper()
和 invoke()
方法都可以调用到目标对象方法,但是它们之间存在的一个本质区别:上下文环境不一样;或者更直接说:目标对象中 this
指向不一样。通过 super.say()
方式调用的目标对象, this
指向的是代理对象;而通过 target.say()
方式调用的,目标对象中 this
指向的就是目标对象本身。这会导致什么差异呢?
假如目标对象类型如下定义,然后使用 Enhancer
创建一个代理对象:
public class Target {
public void a() {
System.out.println(" a 方法");
b();
}
public void b() {
System.out.println(" b 方法");
}
}
客户端触发代理对象 a()
方法执行,如果拦截器中使用 invoke
方式调用目标对象:直接调用目标对象 a()
方法,这个方法中又会通过 this.b()
调用 方法b
,由于是目标对象本身内部调用,所以 b()
方法不会被拦截的。
客户端触发代理对象 a()
方法执行,如果拦截器中使用 invokeSuper()
方式调用目标对象:这里是通过 super.a()
方式调用目标对象中的 a()
方法,然后 a()
方法又会通过 this.b()
调用 方法b
,注意这时的 this
不是目标对象本身,而是代理对象,因为代理对象继承目标对象,代理对象会有重名方法覆写了目标对象方法。所以, this.b()
实际上会触发代理对象中 方法b
的执行,这时是会触发拦截器的。
所以, methodProxy.invokeSuper(obj, args)
这个 obj
是代理对象;而 methodProxy.invoke(obj, args)
这个入参 obj
是目标对象。搞清楚这些基本理解清楚应该使用 invoke
还是 invokeSuper
。
ProxyFactory
Enhancer
只能用于创建 cglib动态代理
, Spring
中还有一个更上层点的类 ProxyFactory
,可以用于创建 JDK动态代理
或 cglib动态代理
。
public void test02() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new UserServiceImpl());
/*
调用ProxyFactory.addInterface()或setInterfaces()表示对接口进行代理,一般会使用jdk动态代理,
除非setOptimize(true)或setProxyTargetClass(true)表示使用cglib代理
cglib代理类名称格式大致为:ServiceImpl$$EnhancerBySpringCGLIB$$f2952b94
*/
//setInterfaces设置这个,会基于接口代理,使用jdk动态代理方式
proxyFactory.setInterfaces(UserService.class);
//proxyFactory.setOptimize(true);
//proxyTargetClass=true:直接代理目标类,而不是接口,使用cglib
proxyFactory.setProxyTargetClass(true);
// 添加增强
proxyFactory.addAdvice(new MyBeforeAdvice02());
//内部使用了缓存,target不变时,getProxy()获取到的都是同一个
//只有target变化时,才会重新创建新的代理对象
Object proxy = proxyFactory.getProxy();
}
ProxyFactory
类是 AOP
底层实现中非常重要的一个类,另外 AnnotationAwareAspectJAutoProxyCreator
、 BeanNameAutoProxyCreator
、 DefaultAdvisorAutoProxyCreator
等一些高级 AOP
实现工具类都是通过在其父类 AbstractAutoProxyCreator
中借助 ProxyFactory
实现 AOP
逻辑织入的。所以,理解 ProxyFactory
的使用对理解 Spring AOP
至关重要。
ProxyFactory
类控制代理的创建过程,其内部委托给 DefaultAopProxyFactory
的一个实例,该实例又转而委托给 Cglib2AopProxy
或 JdkDynamicAopProxy
,用于创建基于 cglib
代理还是 jdk
代理,想了解这两种动态代理区别可以分析下这个类源码。
ProxyFactory
类 addAdvice()
方法将传入的通知封装到 DefaultPointcutAdvisor
( DefaultPointcutAdvisor
是 PointcutAdvisor
的标准实现)的一个实例中,并使用默认包含所有方法的切入点对其进行配置。为更加灵活细粒度的控制在哪些连接点上拦截通知,可以使用 addAdVisor()
方法添加一个带有切入点消息的 Advisor
。
可以使用相同的 ProxyFactory
实例来创建多个代理,每个代理都有不同的切面。为了帮助实现该过程, ProxyFactory
提供了 removeAdvice()
和 removeAdvisor()
方法,这些方法允许从 ProxyFactory
中删除之前传入的任何通知或切面,同时可以使用 boolean adviceIncluded(@Nullable Advice advice)
检查 ProxyFactory
是否附有特定的通知对象。
Advice
ProxyFactory
类 addAdvice()
和 addAdvisor()
两个方法分别引入了两个重要的类: Advice
和 Advisor
。首先,我们来看下 Advice
这个接口类,其可以看成需要织入增强的代码逻辑封装。 Advice
在 Spring
中 API
结构如下:
大致描述:
-
BeforeAdvice MethodBeforeAdvice @Before
-
AfterAdvice AfterReturningAdvice ThrowsAdvice @AfterReturning @AfterThrowing
-
MethodInterceptor
:可以实现环绕通知,对应注解@Around
;
Advisor
在 AOP
规范中有切面概念,在 Spring
中大概对应就是 Advisor
。 Advisor
有两个子接口: PointcutAdvisor
和 IntroductionAdvisor
:
其实真正使用比较多的是它的子类 PointcutAdvisor
,该接口关键就是如下两个方法:
public interface PointcutAdvisor {
Advice getAdvice();
Pointcut getPointcut();
}
PointcutAdvisor
从接口定义大概就可以看出,其就是对 Advice
和 Pointcut
的封装, Advice
代表的是横切面需要织入的代码,而 Pointcut
定义了如何去切的问题。从之前分析来看, Advice
也可以看出一种非常简单的切面,是对指定的类所有方法都进行切入,横切面太宽泛,灵活性不够, PointAdvisor
引入了 Pointcut
后显然比 Advice
更加灵活、强大。
PointcutAdvisor
主要有6个具体的实现类,分别介绍如下:
-
DefaultPointcutAdvisor Pointcut Advice
-
NameMatchMethodPointcutAdvisor
:通过该类可以定义按方法名定义切点的切面; -
RegexpMethodPointcutAdvisor
:使用正则表达式模式定义切点,其内部通过JdkRegexpMethodPointcut
构造出正则表达式方法名切点; -
StaticMethodMatcherPointcutAdvisor
:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类; -
AspecJExpressionPointcutAdvisor
:用于Aspecj
切点表达式定义切点的切面; -
AspecJPointcutAdvisor
:用于AspecJ
语法定义切点的切面;
其实,这些 Advisor
主要区别还是基于其内部封装的 Pointcut
实现类体现的,在实际工作中这些类使用的可能不多,这里的核心在于 Pointcut
如何定义切入点,所以实际开发中更多的可能会去定制 Pointcut
实现类,然后使用 DefaultPointcutAdvisor
将其包装成 Advisor
使用。
下面通过 RegexpMethodPointcutAdvisor
案例简单了解即可:
public void test1(){
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("aop.demo03");
UserServiceImpl target = new UserServiceImpl();
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(target);
proxyFactoryBean.setProxyTargetClass(true);
RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
//设置advisor的advice
advisor.setAdvice(new MyBeforeAdvice02());
//设置advisor的pointcut,aop.demo03包下所有类中已say开头的方法才会织入
advisor.setPattern("aop.demo03..*.say*");
proxyFactoryBean.addAdvisor(advisor);
proxyFactoryBean.setBeanFactory(context);
Object obj = proxyFactoryBean.getObject();
System.out.println(obj.getClass().getName());
UserServiceImpl userService = (UserServiceImpl) obj;
System.out.println(userService.say("haha"));
}
RegexpMethodPointcutAdvisor
表示通过正则表达式进行切点描述的切面,它有一个 pattern
属性用来指定增强要应用到哪些类的哪些方法,也可以通过 patterns
属性指定多个表达式进行匹配。有一个 advice
属性用来表示要应用的增强,这样就能表示一个完整的切面了。
Pointcut
Advisor
引入了一个核心接口 Pointcut
,其描述了对哪些类的哪些方法进行切入。 Pointcut
从其定义可以看出其由 ClassFilter
和 MethodMatcher
构成。 ClassFilter
用于定位哪些类可以进行切入,然后再通过 MethodMatcher
定位类上的哪些方法可以进行切入,这样 Pointcut
就拥有了识别哪些类的哪些方法能被进行切入的能力。
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
Pointcut
接口 API
结构见下:
其中这里比较常用的 AnnotationMatchingPointcut
基于注解进行切入,之前分析【Spring源码】- 09 扩展点之@Import注解一节就使用到该实现类,将标记有 @MyAsync
注解的方法都进行增强就是利用这个实现类:
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAsync.class);
Advice advice = new AsyncAnnotationAdvice(executor);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(bean);
if(!this.isProxyTargetClass()){
proxyFactory.setInterfaces(bean.getClass().getInterfaces());
}
proxyFactory.addAdvisor(advisor);
proxyFactory.copyFrom(this);
return proxyFactory.getProxy();
ProxyFactoryBean
ProxyFactoryBean
和 ProxyFactory
功能和使用其实差不多,底层逻辑也基本一致, ProxyFactoryBean
主要是融合了 IOC
功能。一方面 ProxyFactoryBean
类是 FactoryBean
的一个实现,更加方便注入 IoC
中,参照 mybatis
与 spring
整合一节,其中关键一步就是将扫描的 BeanDefinition
中 beanClass
由接口类替换成 FactoryBean
类型;另一点就是切面可以使用 IoC
容器 bean
。
下面通过一个案例简单看下 ProxyFactoryBean
使用:
package org.simon.ioc.demo1;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("-----洗手-----");
}
}
@Test
public void test1(){
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("org.simon.ioc.demo1");
UserService target = new UserServiceImpl();
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(target);
proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*");
proxyFactoryBean.setBeanFactory(context);
Object obj = proxyFactoryBean.getObject();
System.out.println(obj.getClass().getName());
UserService userService = (UserService) obj;
System.out.println(userService.say("haha"));
}
其中关键一步在: proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*");
,可以使用 IoC
中的 beanName
,同时还支持 通配符*
从 IoC
中查找对应 Bean
。
高级API
前面介绍的类、接口等都是 Spring AOP
中一些底层 API
,使用起来不太方便,感觉功能不太强大,不论是 ProxyFactory
还是 ProxyFactoryBean
创建织入切面的代理,每次只能硬编码一个具体的 Bean
,假如我想将某个包路径下符合一定规则的类的特定方法都进行织入代理怎么办?
使用前面那些 API
好像都不能实现这个需求,但是结合之前分析的 Spring
扩展点,很容易想到可以结合 BeanPostProcessor
扩展点实现这个需求, postProcessAfterInitialization()
这个方法回调时 Bean
依赖注入、初始化等都已经完成,这时就可以在这个方法中过滤出符合一定条件的 Bean
进行代理增强处理。
其实,在 Spring
中基于这种思想,已经为我们提供了三个实现类:
-
BeanNameAutoProxyCreator beanName setBeanNames(String... beanNames) beanName
-
DefaultAdvisorAutoProxyCreator Advisor Advisor Bean
-
AnnotationAwareAspectjAutoProxyCreator Bean AspectJ @Aspect @Before @Around AspectJ
下面我们就来通过 DefaultAdvisorAutoProxyCreator
了解下使用场景:
1、定义一个目标类,后续就是基于该类进行增强:
@Component
public class UserServiceImpl{
public String say(String name){
System.out.println("执行:==UserService#say===");
return "hello,"+name;
}
}
2、定义一个配置类:
@Configuration
@ComponentScan(basePackageClasses = AopConfig.class)
public class AopConfig {
@Bean
public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor(){
RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
advisor.setAdvice(new MyBeforeAdvice02());
//aop.demo03包下所有类中带有say开头方法进行增强
advisor.setPattern("aop.demo03..*.say*");
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
}
这个类有两个关键,一个是向 IoC
中注入 Advisor
,之前分析过 Advisor
包含两个功能:
-
通过
Pointcut
定位哪些类的哪些方法需求切入; -
通过关联的
Advice
指定切入增强逻辑;
另一个关键就是注入 DefaultAdvisorAutoProxyCreator
,这个就是一个 Spring
内置的实现 BeanPostProcessor
扩展类,其在 postProcessAfterInitialization()
方法中对 Bean
进行切入增强。
3、测试:
public void test1(){
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AopConfig.class);
System.out.println(context.getBean(UserServiceImpl.class).getClass());
}
context.getBean(UserServiceImpl.class)
从 IoC
容器中获取的就是使用 cglib
代理后的实例。
下面我们再来分析下 AnnotationAwareAspectjAutoProxyCreator
,平时如果项目中需要开启 AOP
功能,使用 @EnableAspectJAutoProxy
注解方式开启,我们来看下该注解干了什么?
1、 @EnableAspectJAutoProxy
注解使用 @Import
注解将 AspectJAutoProxyRegistrar
引入:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
2、 AspectJAutoProxyRegistrar
是 ImportBeanDefinitionRegistrar
接口实现类:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//注册一个SmartInstantiationAwareBeanPostProcessor类型的实现类:AnnotationAwareAspectJAutoProxyCreator
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
其中最关键一句 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
,就是向IoC容器中注入 AnnotationAwareAspectJAutoProxyCreator
。
这样我们就明白了 @EnableAspectJAutoProxy
注解方式开启 AOP
的本质就像向 IoC
中注入 AnnotationAwareAspectJAutoProxyCreator
,它利用 BeanPostProcessor
扩展点功能实现织入增强逻辑。
总结
首先,对 Spring AOP
底层一些最基础、最核心的 API
的分析梳理,相信你会对 Spring AOP
底层实现逻辑有了一个更加深入的理解。然后通过 Spring AOP
提供的高级 API
,理解了如何将 IoC
和 AOP
集成到一起实现强大功能,对 Spring
中 AOP
的整体实现思路也有了比较清晰的认识。
长按识别关注, 持续输出原创
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- axios 核心源码解读
- Disruptor 核心源码分析
- Netty 核心源码解读(开篇)
- HashMap: 通俗分析核心源码
- 读源码之Spring 核心内容
- ReactiveCocoa 源码解析之核心流程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Tomcat与Java Web开发技术详解
孙卫琴 / 电子工业出版社 / 2004-4-1 / 45.00元
《Tomcat与Java Web开发技术详解》编辑推荐:Jakarta Tomcat服务器是在SUN公司的JSWDK(JavaServer Web DevelopmentKit,SUN公司推出的小型Servlet/JSP调试工具)的基础上发展起来的一个优秀的Java Web应用容器,它是Apache-Jakarta的一个子项目。Tomcat被JavaWorld杂志的编辑选为2001年度最具创新的J......一起来看看 《Tomcat与Java Web开发技术详解》 这本书的介绍吧!