一、需求
前段时间项目中有一个需求需要生成编码,编码规则中有一个4位数字需要顺序生成,从1开始,当增加到10000的时候回复成1。考虑到这个是顺序生成的,同时适用于多线程环境,所以就想到使用redis存储数据,同时使用Ression的Lock来实现多线程顺序增加。考虑到代码的可读性以及业务无关性,所以想到使用Aop把这个逻辑代码抽出去,利用注解的特点结合业务代码可插拔使用。过程很顺利,但是最终运行发现切面Aspect未生效,排查的时候发现是由于忘记了spring中代理类的生效规则,所以记录此贴,总结错误以便后面防止出现这些问题。
二、测试代码
2.1 测试的业务逻辑代码
@Component
public class MyService{
@MyAnnotation
public void serviceA(){
System.out.println("serviceA");
}
}
2.2 测试的注解
通过这个注解标识该方法是否需要被拦截
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Component
public @interface MyAnnotation{
}
2.3 切面
@Component
@Aspect
public class MyAspect{
//定义拦截点,由于我是通过注解标识该方法是否需要被拦截的,所以这里拦截点是方法时候被标注注解MyAnnotation
@Pointcut("@annotation(com.example.demo.MyAnnotation)")
public void pointcut(){
}
//拦截后执行的环绕通知
@Around(value = "pointcut()")
public Object sout(ProceedingJoinPoint point) throws Throwable {
System.out.println("run into aspect!");
//注意这里一定要执行被拦截的方法,否则就会导致只执行了拦截而没有执行原本的业务方法
return point.proceed();
}
}
2..4 测试方法
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication implements ApplicationContextAware {
static ApplicationContext ctx;
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
ServiceAinf = (ServiceA) ctx.getBean("serviceA ");
inf.ServiceA();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
}
2.5 测试结果如下
2021-07-27 14:25:02.623 INFO 23356 --- [ restartedMain] com.example.demo.DemoApplication : Starting DemoApplication using Java 1.8.0_161 on DESKTOP-APRM3G5 with PID 23356 (D:\IDEA-WROKPLACE\demo2\target\classes started by ColpoCharlie in D:\IDEA-WROKPLACE\demo2)
2021-07-27 14:25:02.625 INFO 23356 --- [ restartedMain] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-07-27 14:25:02.652 INFO 23356 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-07-27 14:25:03.068 INFO 23356 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2021-07-27 14:25:03.079 INFO 23356 --- [ restartedMain] com.example.demo.DemoApplication : Started DemoApplication in 0.701 seconds (JVM running for 1.239)
run into aspect!
serviceA
2.6 结果分析
可以看到方法确实被拦截下来了,而且执行完切面的around方法后,继续执行了原本的方法逻辑,同时也可以很明显的看到这个ServiceA的对象inf确实是采用了CGLIB生成了代理类的子类
三、原理探索
考虑到这是在原本的功能上动态添加了增强功能,所以基本上是采用了动态代理的方式,结合Spring的默认使用了CGLIB的代理方式,猜测应该是内部使用CGLIB生成了一个代理类,之后当所有方法调用的时候就是调用了代理类的增强后的方法
3.1 容器中的代理类是哪一步生成的
1、通过debug进入getBean的方法内部
//AbstractApplicationContext
//---------------------------------------------------------------------
// Implementation of BeanFactory interface
//---------------------------------------------------------------------
@Override
public Object getBean(String name) throws BeansException {
//判断BeanFactory是否可用
assertBeanFactoryActive();
//通过BeanFactory调用getBean方法获取Bean
return getBeanFactory().getBean(name);
}
2、进一步追踪到AbstractAutowireCapableBeanFactory的createBean方法,这里我们主要看到resolveBeforeInstantiation方法和doCreateBean方法,这个方法提供一个通过BeanPostProcessors的遍历调用生成代理类的途径,本次测试的是通过doCreateBean方法创建代理类的。
//AbstractAutowireCapableBeanFactory
//---------------------------------------------------------------------
// Implementation of relevant AbstractBeanFactory template methods
//---------------------------------------------------------------------
/**
* Central method of this class: creates a bean instance,
* populates the bean instance, applies post-processors, etc.
* @see #doCreateBean
* 用于创建一个Bean的实例,然后填充这个实例的属性
*/
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
//传递进来的beandefinition,这里是ServiceA对应的beandefinition
RootBeanDefinition mbdToUse = mbd;
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
//提供了通过调用这些BeanPostProcessor的方法,生成目标类的代理类
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
//本次测试的主要生成代理类的方法
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
3、我们看resolveBeforeInstantiation方法的逻辑定义在哪里
//AbstractAutowireCapableBeanFactory
/**
* Apply before-instantiation post-processors, resolving whether there is a
* before-instantiation shortcut for the specified bean.
* @param beanName the name of the bean
* @param mbd the bean definition for the bean
* @return the shortcut-determined bean instance, or {@code null} if none
*/
@Nullable
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
// Make sure bean class is actually resolved at this point.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
4、doCreateBean的内部逻辑是
//AbstractAutowireCapableBeanFactory
/**
* Actually create the specified bean. Pre-creation processing has already happened
* at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
* <p>Differentiates between default bean instantiation, use of a
* factory method, and autowiring a constructor.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args explicit arguments to use for constructor or factory method invocation
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
* @see #instantiateBean
* @see #instantiateUsingFactoryMethod
* @see #autowireConstructor
*/
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
//判断这个的beandifination是不是单例模式的
if (mbd.isSingleton()) {
//单例就从factoryBeanInstanceCache中移除
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
//假如为空则说明不是单例的或者缓存中不存在
if (instanceWrapper == null) {
//创建bean实例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//从生成的包装类中获取被包装的对象
Object bean = instanceWrapper.getWrappedInstance();
//从生成的包装类中获取被包装的对象的类型
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
//允许BeanDefinitionPostProcessor对BeanDefinition进行修改但是BeanDefinition只能被修改一次
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充Beandefinition
populateBean(beanName, mbd, instanceWrapper);
//初始化给定的Bean对象
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
5、初始化方法
//AbstractAutowireCapableBeanFactory
/**
* Initialize the given bean instance, applying factory callbacks
* as well as init methods and bean post processors.
* <p>Called from {@link #createBean} for traditionally defined beans,
* and from {@link #initializeBean} for existing bean instances.
* 这个方法是用于出初始化给定的Bean对象,这个方法会被factory在createBean方法或者initializeBean方法调用
* @param beanName the bean name in the factory (for debugging purposes)
* @param bean the new bean instance we may need to initialize
* @param mbd the bean definition that the bean was created with
* (can be null}, if given an existing bean instance)
* @return the initialized bean instance (potentially wrapped)
* 返回被包装的Bean对象
*/
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
//调用BeanPostProcessor的前置处理器对这个Bean对象进行初始化
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//调用这个Bean对象的构造器方法进行初始化
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
//调用BeanPostProcessor的后置处理器对这个Bean对象进行初始化
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
从上方的几个初始化方法的调用顺序可以知道,我们之前记忆的Bean的创建流程中显示前置处理器-》初始化方法-》后置处理器这样的而初始化逻辑是这里确定的。
而本次生成代理类是根据后置处理器生成的
6、applyBeanPostProcessorsAfterInitialization
//AbstractAutowireCapableBeanFactory
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//可以看到是获取所有的后置处理器然后遍历每个后置处理器,调用后置处理器初始化对象
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
每一个后置处理器都有自己的后置处理方法,根据BeanPostProcessor的实现类的不同分别调用这些后置处理器的方法。当一个后置处理器不重写覆盖默认的BeanPostProcessor的后置处理方法postProcessAfterInitialization,那么这个方法内部是直接返回传入对象的。
这里的生成代理类的后置处理器是AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,这个类定义了后置处理方法
//AbstractAutoProxyCreator
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* 这里有一个疑问这个类的名称是驼峰命名的,也就是serivceA,这样不会在不同包下面有相同的key吗
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果这个类有关联到切面,则为这个传进的Bean生成一个代理类并返回
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
可以看到这里有一个wrapIfNecessary方法,该内部有一个createProxy方法用于创建代理类后并返回。我们看看我们是否能够拿到这个代理类。
可以看到已经能够获得代理类了,而且尝试通过代理类调用方法已经能够被拦截下来的。
2021-08-03 16:22:58.329 INFO 29984 --- [ restartedMain] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-08-03 16:22:58.354 INFO 29984 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-08-03 16:22:59.046 INFO 29984 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2021-08-03 16:22:59.064 INFO 29984 --- [ restartedMain] com.example.demo.DemoApplication : Started DemoApplication in 1.013 seconds (JVM running for 1.485)
run into aspect!
ServiceA
Disconnected from the target VM, address: '127.0.0.1:52801', transport: 'socket'
四、容易踩坑的点
4.1、嵌套方法的增强
由于业务的复杂性,可能是多个方法一起互相调用的,所以存在很多方法嵌套的情况,例如事务的嵌套。对于这种方法的嵌套,假如说我希望这些互相调用的方法都需要被拦截到,对于不熟悉代理的人来说大部分人的代码是这样写的。
@Component
public class ServiceA {
@MyAnnotation
public void ServiceA(){
System.out.println("ServiceA");
ServiceB();
}
public void ServiceB(){
System.out.println("ServiceB");
}
}
或者是这样写的
@Component
public class ServiceA {
@MyAnnotation
public void ServiceA(){
System.out.println("ServiceA");
ServiceB();
}
@MyAnnotation
public void ServiceB(){
System.out.println("ServiceB");
}
}
我们来看一下这样写是否会有效果的,运行结果如下
2021-08-03 15:15:17.847 INFO 22684 --- [ restartedMain] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-08-03 15:15:17.873 INFO 22684 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-08-03 15:15:18.373 INFO 22684 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2021-08-03 15:15:18.384 INFO 22684 --- [ restartedMain] com.example.demo.DemoApplication : Started DemoApplication in 0.794 seconds (JVM running for 1.252)
inf = class com.example.demo.ServiceA$$EnhancerBySpringCGLIB$$b121f309
run into aspect!
ServiceA
ServiceB
Disconnected from the target VM, address: '127.0.0.1:58960', transport: 'socket'
可以看到对于嵌套的方法SericeB并没有被拦截到的,所以对于嵌套的那个方法ServiceB的调用就没有被增强。
4.2、代理失效原因
我们先来想一下无论是JDK的代理还是CGLIB的代理过程,都是为被代理的对象生成一个代理对象,而且这个代理对象还持有被代理对象的引用。当通过代理对象去调用方法的时候,基本都是先执行被增强的逻辑代码,然后再调用持有的被代理的对象的应用,通过这个引用去反射调用被代理对象的对应方法的,所以对于这个
inf.ServiceA()
调用的而动作来说,实际上在执行完增强逻辑后,还是使用了ServiceA对象来调用方法ServiceA()的,而不是ServiceAProxy来调用ServiceA()的。这就会导致一个问题。我们使用的是ServiceA.ServiceA(),也就是对于ServiceA()方法内部调用ServiceB()方法来说的话,就是ServiceA.ServiceB(),所以是无法被代理增强的。我们来看看实际上是不是这样的。
1、再进入方法调用前我们可以看到ServiceA对象经过Spring的处理已经变成了代理对象的,然后通过代理对象调用方法
由于我们的切面是定义了Before切面的,所以在进入方法前就应该执行了增强。
2021-08-03 15:27:01.083 INFO 22588 --- [ restartedMain] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-08-03 15:27:01.109 INFO 22588 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-08-03 15:27:01.807 INFO 22588 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2021-08-03 15:27:01.826 INFO 22588 --- [ restartedMain] com.example.demo.DemoApplication : Started DemoApplication in 1.128 seconds (JVM running for 1.623)
inf = class com.example.demo.ServiceA$$EnhancerBySpringCGLIB$$87ee8547
run into aspect!
ServiceA
可以看到确实是执行了增强,然后再进入了实际的ServiceA方法的调用,我们再仔细看一下这个方法调用的栈帧。
可以看到这里的栈帧是ServiceA的栈帧,所以这里的存在于ServiceA方法中的方法调用ServiceB()实际上调用的是ServiceA对象的ServiceB方法,也就是不是经过代理类调用的,无增强逻辑的方法,所以可以看到即使ServiceB方法的上方标注了注解,但实际上没有生效。
4.3、针对以上无法代理的修改方法
既然知道了为啥方法之间的调用代理不生效(由于方法的嵌套方法不是经过代理对象调用的),那么我们只要想办法让嵌套方法也是经过代理对象调用就可以增强嵌套方法的执行。
这里有两方法可以有效避免。
①注入ServiceA类对象,通过这个对象来调用嵌套方法的ServiceB就可以了
这里的原理是因为在生成ServiceA对象的时候,也就是调用getBean的方法的时候Spring发现这个类是需要被代理的Java类,那么内部就会默认(未指定Spring代理方式的话)使用CGLIB生成代理对象然后把这个对象以 (ServiceA的对应的key:ServiceA对象的代理对象)的形式存储在BeanFactory中,当我们使用自动注入ServiceA的对象的时候,这里注入的就是ServiceA的代理对象的。
②通过AopContext类获取存储在AopContext中的代理对象
这个方法需要在启动配置类上标注的@EnableAspectJAutoProxy(exposeProxy = true)注解里面设置exposeProxy属性设置为true,当这里设置为true的时候,在生成代理类的时候会把这个代理类存储在AopContext中,默认exposeProxy是false,只有设置为真才会把代理类暴露出来的。
可以看到以上两种方法都能够实现嵌套方法的代理。
五、总结
通过大概的代理对象的生成步骤大概了解了嵌套方法的代理失效的原因,还了解在Spring中如何获得一个类的代理对象
1、通过注入获得代理对象
2、通过AopContext获得代理对象
存在的疑惑
1、对于代理类的生成步骤不是很熟悉
2、对于代理类如何做方法增强的逻辑不明确