手写Spring-第十五章-我也要注入?为代理对象注入属性,完善AOP

前言

我们上一章实现了用注解为对象注入属性。但是还有一个地方我们没有照顾到,那就是代理对象属性的注入。你可能会疑惑,我们的Bean,不都是通过cglib代理出来的吗?那不都是代理对象?但我们这里说的代理对象,并不是这些。还记得之前我们的AOP相关部分吗?那里我们实现AOP功能的时候,走的是特殊路径来创建代理bean,从而实现将我们的增强逻辑插入进去。但当时的实现过程存在一个隐患,那就是在创建原始对象的时候,是直接调用 newInstance()方法进行的。这样的原始方法,不仅有参构造会报错,而且也没法走正常Bean生命周期中对属性的注入过程。那么我们这一章,就要来解决这个问题,顺带实现用注解方式完成AOP功能,让我们的框架更加符合日常使用习惯。

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              ├─aop
│  │  │              │  │  AdvisedSupport.java
│  │  │              │  │  Advisor.java
│  │  │              │  │  BeforeAdvice.java
│  │  │              │  │  ClassFilter.java
│  │  │              │  │  MethodBeforeAdvice.java
│  │  │              │  │  MethodMatcher.java
│  │  │              │  │  Pointcut.java
│  │  │              │  │  PointcutAdvisor.java
│  │  │              │  │  TargetSource.java
│  │  │              │  │  
│  │  │              │  ├─aspect
│  │  │              │  │      Aspect.java
│  │  │              │  │      AspectJExpressionPointcut.java
│  │  │              │  │      AspectJExpressionPointcutAdvisor.java
│  │  │              │  │      Before.java
│  │  │              │  │  
│  │  │              │  ├─config
│  │  │              │  │      AopBeanRegistryPostProcessor.java
│  │  │              │  │  
│  │  │              │  └─framework
│  │  │              │      │  AopProxy.java
│  │  │              │      │  Cglib2AopProxy.java
│  │  │              │      │  JdkDynamicAopProxy.java
│  │  │              │      │  ProxyFactory.java
│  │  │              │      │  ReflectiveMethodInvocation.java
│  │  │              │      │  
│  │  │              │      ├─adapter
│  │  │              │      │      MethodBeforeAdviceInterceptor.java
│  │  │              │      │  
│  │  │              │      └─autoproxy
│  │  │              │              DefaultAdvisorAutoProxyCreator.java
│  │  │              │          
│  │  │              ├─beans
│  │  │              │  ├─exception
│  │  │              │  │      BeanException.java
│  │  │              │  │  
│  │  │              │  └─factory
│  │  │              │      │  Aware.java
│  │  │              │      │  BeanClassLoaderAware.java
│  │  │              │      │  BeanFactory.java
│  │  │              │      │  BeanFactoryAware.java
│  │  │              │      │  BeanNameAware.java
│  │  │              │      │  ConfigurableListableBeanFactory.java
│  │  │              │      │  DisposableBean.java
│  │  │              │      │  FactoryBean.java
│  │  │              │      │  HierarchicalBeanFactory.java
│  │  │              │      │  InitializingBean.java
│  │  │              │      │  ListableBeanFactory.java
│  │  │              │      │  PropertyPlaceholderConfigurer.java
│  │  │              │      │  
│  │  │              │      ├─annotation
│  │  │              │      │      Autowired.java
│  │  │              │      │      AutowiredAnnotationBeanPostProcessor.java
│  │  │              │      │      Qualifier.java
│  │  │              │      │      Value.java
│  │  │              │      │  
│  │  │              │      ├─config
│  │  │              │      │      AutowireCapableBeanFactory.java
│  │  │              │      │      BeanDefinition.java
│  │  │              │      │      BeanDefinitionRegistryPostProcessor.java
│  │  │              │      │      BeanFactoryPostProcessor.java
│  │  │              │      │      BeanPostProcessor.java
│  │  │              │      │      BeanReference.java
│  │  │              │      │      ConfigurableBeanFactory.java
│  │  │              │      │      DefaultSingletonBeanRegistry.java
│  │  │              │      │      InstantiationAwareBeanPostProcessor.java
│  │  │              │      │      PropertyValue.java
│  │  │              │      │      PropertyValues.java
│  │  │              │      │      SingletonBeanRegistry.java
│  │  │              │      │  
│  │  │              │      ├─support
│  │  │              │      │      AbstractAutowireCapableBeanFactory.java
│  │  │              │      │      AbstractBeanDefinitionReader.java
│  │  │              │      │      AbstractBeanFactory.java
│  │  │              │      │      BeanDefinitionReader.java
│  │  │              │      │      BeanDefinitionRegistry.java
│  │  │              │      │      CglibSubclassingInstantiationStrategy.java
│  │  │              │      │      DefaultListableBeanFactory.java
│  │  │              │      │      DisposableBeanAdapter.java
│  │  │              │      │      FactoryBeanRegistrySupport.java
│  │  │              │      │      InstantiationStrategy.java
│  │  │              │      │      SimpleInstantiationStrategy.java
│  │  │              │      │  
│  │  │              │      └─xml
│  │  │              │              XmlBeanDefinitionReader.java
│  │  │              │          
│  │  │              ├─context
│  │  │              │  │  ApplicationContext.java
│  │  │              │  │  ApplicationContextAware.java
│  │  │              │  │  ApplicationEvent.java
│  │  │              │  │  ApplicationEventPublisher.java
│  │  │              │  │  ApplicationListener.java
│  │  │              │  │  ConfigurableApplicationContext.java
│  │  │              │  │  
│  │  │              │  ├─annotation
│  │  │              │  │      ClassPathBeanDefinitionScanner.java
│  │  │              │  │      ClassPathScanningCandidateComponentProvider.java
│  │  │              │  │      Scope.java
│  │  │              │  │  
│  │  │              │  ├─event
│  │  │              │  │      AbstractApplicationEventMulticaster.java
│  │  │              │  │      ApplicationContextEvent.java
│  │  │              │  │      ApplicationEventMulticaster.java
│  │  │              │  │      ContextClosedEvent.java
│  │  │              │  │      ContextRefreshEvent.java
│  │  │              │  │      SimpleApplicationEventMulticaster.java
│  │  │              │  │  
│  │  │              │  └─support
│  │  │              │          AbstractApplicationContext.java
│  │  │              │          AbstractRefreshableApplicationContext.java
│  │  │              │          AbstractXmlApplicationContext.java
│  │  │              │          ApplicationContextAwareProcessor.java
│  │  │              │          ClasspathXmlApplicationContext.java
│  │  │              │      
│  │  │              ├─core
│  │  │              │  └─io
│  │  │              │          ClasspathResource.java
│  │  │              │          DefaultResourceLoader.java
│  │  │              │          FileSystemResource.java
│  │  │              │          Resource.java
│  │  │              │          ResourceLoader.java
│  │  │              │          UrlResource.java
│  │  │              │      
│  │  │              ├─stereotype
│  │  │              │      Component.java
│  │  │              │  
│  │  │              └─util
│  │  │                      ClassUtils.java
│  │  │                      StringValueResolver.java
│  │  │                  
│  │  └─resources
│  └─test
│      ├─java
│      │  └─com
│      │      └─akitsuki
│      │          └─springframework
│      │              └─test
│      │                  │  ApiTest.java
│      │                  │  
│      │                  └─bean
│      │                          IUserService.java
│      │                          UserDao.java
│      │                          UserService.java
│      │                          UserServiceBeforeAdvice.java
│      │                      
│      └─resources
│              application.yml
│              spring.xml

这一章新增和修改的内容都不算多,主要是一些结构的调整,还有就是对后置处理器的实际使用。

从源头开始

我们知道,在之前的AOP部分中,我们使用TargetSource来包装实际的Bean。在这里有一个getTargetClass方法,用于获取bean的所有接口。之前是直接调用class的getInterfaces方法来返回的,但我们的bean如果要走正常的Bean生命周期,就可能会是由cglib代理出来的bean。那么在这里我们就需要做一个判断,这个bean是否是代理过的,如果是代理过的,那就要经过特殊处理,才能够拿到真正的接口。

package com.akitsuki.springframework.aop;

import com.akitsuki.springframework.util.ClassUtils;

/**
 * 被代理的目标对象
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/12/5 11:11
 */
public class TargetSource {

    private final Object target;

    public TargetSource(Object target) {
        this.target = target;
    }

    public Class<?>[] getTargetClass() {
        Class<?> clazz = target.getClass();
        clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;
        return clazz.getInterfaces();
    }

    public Object getTarget() {
        return target;
    }
}

处理过程比较简单,如果是代理对象,取它的父类即可。因为cglib生成的代理对象,与原始对象是继承关系。

小改动,大功能:为AOP代理方法移动位置

我们要达成为代理对象注入属性,其实只需要让这个代理对象创建的过程走正常的bean生命周期就可以了。那一整套流程里面,自然会把属性注入给安排的明明白白的。之前创建代理对象的过程放在了 postProcessBeforeInstantiation中,这是走特殊流程创建bean的方法,我们要把里面的内容移到 postProcessAfterInitialization中。这一部分内容在 DefaultAdvisorAutoProxyCreator中。

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
        if (isInfrastructureClass(bean.getClass())) {
            return null;
        }
        Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();

        for (AspectJExpressionPointcutAdvisor advisor : advisors) {
            ClassFilter classFilter = advisor.getPointcut().getClassFilter();
            if (!classFilter.matches(bean.getClass())) {
                continue;
            }
            AdvisedSupport advisedSupport = new AdvisedSupport();
            TargetSource targetSource = new TargetSource(bean);

            advisedSupport.setTargetSource(targetSource);
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
            advisedSupport.setProxyTargetClass(false);

            return new ProxyFactory(advisedSupport).getProxy();
        }
        return bean;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException {
        return null;
    }

这里并不是直接把代码粘贴过去就可以的,还是有一些小的地方需要改动。比如 new TargetSource的时候,之前是调用 newInstance方法来创建bean对象的,这里则是直接使用方法入参中的bean。而对于之前的方法,我们直接返回null即可。这样,AOP代理对象的创建,就融入Spring的生命周期了。还有一点需要注意的是,这里的 postProcessAfterInitialization,在bean的生命周期中,是在初始化过程中调用的。也就是 AbstractAutowireCapableBeanFactory中的 initializeBean方法。之前在 createBean方法中调用的时候,忽略了这里的返回值,这里要把返回值赋值给bean,也就是 bean = initializeBean(beanName, bean, beanDefinition),因为这里返回的是经过AOP代理过的对象,如果忽略掉的话,AOP就不生效了。

完善切面!变成我熟悉的样子吧!

其实为代理对象注入属性,到上面就已经完成了。所以其实这一章到这里应该是喜闻乐见的测试过程才对。但是不觉得这样有些不过瘾吗?好像只是改了一点点内容,一章就结束了?所以这里我们扩充一点东西。既然这一章的内容和AOP有关,我们就来修改AOP相关的内容。我们前面实现了AOP的功能,但这种实现方式总觉得和平时的做法有些不同。我们平时还是更加习惯注解的方式来实现AOP。所以,来动手吧,先写注解。

package com.akitsuki.springframework.aop.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/12/13 16:35
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
    String value() default "";
}



package com.akitsuki.springframework.aop.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/12/13 16:35
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {
    String value();
}


因为前面的AOP内容,我们实现了Before的内容,所以这次我们也只实现Before的注解。因为这里的实现和Spring中的不太一样,所以我们自己定义了这两个注解,实际上Spring中使用的是aspectj中的注解,方法入参等内容也有所不同,但我们为了配合前面的实现,所以进行了一定的改造,最终能够实现的效果是一样的。

有了注解,我们怎么使用呢?我们回忆一下上一章对 @Autowired@Value注解的使用,总体思路就是找到加了这些注解的类和方法,然后再做相应的处理。这个基本准则定下来了,接下来就要考虑,在什么时间点来进行这些操作。结合之前的AOP章节的内容,我们知道,要创建一个切面,是需要许多bean的配合的。来尝试列举一下,首先需要一个 MethodBeforeAdvice,这个是用户自己定义的增强内容。然后需要一个 MethodInterceptor,来拦截下方法的执行。还需要一个 PointcutAdvisor,来解析具体的表达式。这样除了用户自己定义的一个Bean,还要额外的两个bean来支持它运行。可以设想,如果系统中的切面多起来之后,这些Bean让用户来管理,是非常不合理的。作为用户,只需要关注真正的增强逻辑和要拦截的方法(表达式)就可以了。所以我们就需要来自动创建这些额外的Bean。回忆一下之前FactoryBean的章节,是不是也有类似的自动创建Bean的地方?嗯?在我们的极致青春版Mybatis与Spring结合的部分,好像的确是有在某个节点动态生成Bean定义并且注册进Spring的内容,是为了将动态生成的Mapper加入Spring。不记得的记得去翻一翻~那么这里,我们是不是也可以利用这个节点,来进行这些辅助bean的创建呢?答案是肯定的。这就需要我们的 BeanDefinitionRegistryPostProcessor出场了。

package com.akitsuki.springframework.aop.config;

import com.akitsuki.springframework.aop.aspect.Aspect;
import com.akitsuki.springframework.aop.aspect.AspectJExpressionPointcutAdvisor;
import com.akitsuki.springframework.aop.aspect.Before;
import com.akitsuki.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.BeanFactoryAware;
import com.akitsuki.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanDefinitionRegistryPostProcessor;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import com.akitsuki.springframework.beans.factory.support.BeanDefinitionRegistry;

import java.lang.reflect.Method;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/12/13 17:12
 */
public class AopBeanRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeanException {
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            Class<?> clazz = beanDefinition.getBeanClass();
            Aspect aspect = clazz.getAnnotation(Aspect.class);
            if (null != aspect) {
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    Before before = method.getAnnotation(Before.class);
                    if (null != before) {
                        BeanDefinition methodBeforeAdviceInterceptor = new BeanDefinition(MethodBeforeAdviceInterceptor.class);
                        PropertyValues pvs = new PropertyValues();
                        pvs.addPropertyValue(new PropertyValue("advice", beanFactory.getBean(beanDefinitionName)));
                        methodBeforeAdviceInterceptor.setPropertyValues(pvs);
                        registry.registerBeanDefinition(beanDefinitionName + "MethodBeforeAdviceInterceptor", methodBeforeAdviceInterceptor);

                        BeanDefinition pointcutAdvisor = new BeanDefinition(AspectJExpressionPointcutAdvisor.class);
                        pvs = new PropertyValues();
                        pvs.addPropertyValue(new PropertyValue("advice", beanFactory.getBean(beanDefinitionName + "MethodBeforeAdviceInterceptor")));
                        pvs.addPropertyValue(new PropertyValue("expression", before.value()));
                        pointcutAdvisor.setPropertyValues(pvs);
                        registry.registerBeanDefinition(beanDefinitionName + "PointcutAdvisor", pointcutAdvisor);
                    }
                }
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        return;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeanException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }
}

一眼看过去是不是有些密密麻麻?不急,我们慢慢来分析。

首先可以看到,这里实现了感知BeanFactory的接口,可以拿到BeanFactory。这里越来越能感受到这个感知接口的作用了吧~之前刚学到这里的时候,是不是觉得有些不明觉厉,只有真正起到作用了,才会感叹真香。

然后我们看主体逻辑,这里利用BeanFactory拿到了所有的Bean定义进行遍历,并且从中过滤出加了 @Aspect注解的类,以及用 @Before进行注解的方法。现在我们拿到了用户定义的增强切面(也就是当前遍历到的Bean定义),切面表达式(从 @Before注解的value中拿到),就可以构建起这三个对象的依赖关系了。

  1. MethodInterceptor依赖用户定义的增强切面
  2. PointcutAdvisor依赖 MethodInterceptor表达式expression

接下来要做的,就是创建 MethodInterceptorPointcutAdvisor的bean,然后注册到Spring中就可以了,顺带将依赖的内容作为 PropertyValue进行设置。都是流程化的内容,这里就不多赘述了。需要注意的是,这里的beanName,为了不重复,所以采取了一些规则,是以用户的增强切面BeanName,加上一些后缀组成的。

现在我们把插入时间点找到了,但是 AopBeanRegistryPostProcessor本身也要作为bean注册到Spring中。这么一个背后工作者,肯定不能交给用户来往配置文件里面写了,所以得想个主意把它自动注册进去。回忆一下上一章是不是有个地方被我们用来注册内部使用的bean了?嗯,就在 ClassPathBeanDefinitionScanner中,在那块注册了处理注解和占位符的bean。我们这次把处理这个后置处理器也加入进去。

    /**
     * 注册Spring内部使用的bean
     */  
    private void registerInnerBean() {
        //注册处理注解的BeanPostProcessor
        registry.registerBeanDefinition("autowiredAnnotationBeanPostProcessor"
                , new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class));
        //注册处理属性占位符的bean
        BeanDefinition propertyPlaceholder = new BeanDefinition(PropertyPlaceholderConfigurer.class);
        PropertyValues propertyPlaceholderPv = new PropertyValues();
        propertyPlaceholderPv.addPropertyValue(new PropertyValue("location", "classpath:application.yml"));
        propertyPlaceholder.setPropertyValues(propertyPlaceholderPv);
        registry.registerBeanDefinition("propertyPlaceholderConfigurer", propertyPlaceholder);
        //注册AOP相关的bean
        registry.registerBeanDefinition("aopBeanRegistryPostProcessor", new BeanDefinition(AopBeanRegistryPostProcessor.class));
        registry.registerBeanDefinition("defaultAdvisorAutoProxyCreator", new BeanDefinition(DefaultAdvisorAutoProxyCreator.class));
    }

这里我们还多注册了一个 DefaultAdvisorAutoProxyCreator,这也是一个AOP相关的后置处理器,但这个只需要一份就够了,不需要动态生成,所以也一并放在这里进行注册。这个处理器在前面的章节介绍过,主要是拿到所有的 AspectJExpressionPointcutAdvisor进行遍历处理,最终生成代理对象。可以说是AOP功能的启动点,所以还是很重要的。

测试

好了,终于来到了我们喜闻乐见的测试环节。因为这次我们用了AOP,所以还是需要一个接口。

package com.akitsuki.springframework.test.bean;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/12/13 15:52
 */
public interface IUserService {

    void queryUserInfo(Long id);
}

然后让我们的UserService来实现它

package com.akitsuki.springframework.test.bean;

import com.akitsuki.springframework.beans.factory.DisposableBean;
import com.akitsuki.springframework.beans.factory.InitializingBean;
import com.akitsuki.springframework.beans.factory.annotation.Autowired;
import com.akitsuki.springframework.beans.factory.annotation.Value;
import com.akitsuki.springframework.context.ApplicationContext;
import com.akitsuki.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:42
 */
@Getter
@Setter
@Component
public class UserService implements IUserService, InitializingBean, DisposableBean {

    @Value("${dummyString}")
    private String dummyString;
    @Value("${dummyInt}")
    private int dummyInt;
    @Autowired
    private UserDao userDao;

    @Override
    public void queryUserInfo(Long id) {
        System.out.println("dummyString:" + dummyString);
        System.out.println("dummyInt:" + dummyInt);
        String userName = userDao.queryUserName(id);
        if (null == userName) {
            System.out.println("用户未找到>_<");
        } else {
            System.out.println("用户名:" + userDao.queryUserName(id));
        }
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("userService的destroy执行了");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("userService的afterPropertiesSet执行了");
    }
}

现在的UserService,有种全副武装的感觉(笑),什么属性注入、依赖注入、初始化、销毁方法全来一套,就差一些感知接口了。然后是我们的切面增强类

package com.akitsuki.springframework.test.bean;

import com.akitsuki.springframework.aop.MethodBeforeAdvice;
import com.akitsuki.springframework.aop.aspect.Aspect;
import com.akitsuki.springframework.aop.aspect.Before;
import com.akitsuki.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/12/13 15:34
 */
@Component
@Aspect
public class UserServiceBeforeAdvice implements MethodBeforeAdvice {

    @Override
    @Before("execution(* com.akitsuki.springframework.test.bean.IUserService.*(..))")
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法执行了:" + method);
    }
}

是不是味道一下子就涌上来了?这才是我们熟悉的用法。

最后是配置文件spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <component-scan base-package="com.akitsuki.springframework.test.bean" />
</beans>

很好,做你该做的,什么bean注册之类的,都交给注解来,只配置一个包扫描就够了。

然后是我们的主要测试类,还是老样子。

package com.akitsuki.springframework.test;

import com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext;
import com.akitsuki.springframework.test.bean.IUserService;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/15 13:58
 */
public class ApiTest {

    @Test
    public void test() {
        ClasspathXmlApplicationContext context = new ClasspathXmlApplicationContext("classpath:spring.xml");
        context.registerShutdownHook();
        IUserService userService = context.getBean("userService", IUserService.class);
        userService.queryUserInfo(1L);
    }
}

测试结果

执行UserDao的initMethod
userService的afterPropertiesSet执行了
方法执行了:public abstract void com.akitsuki.springframework.test.bean.IUserService.queryUserInfo(java.lang.Long)
dummyString:kamisama
dummyInt:114514
用户名:akitsuki
userService的destroy执行了

Process finished with exit code 0

来分析一下,首先可以看到那个很长的一行,代表切面正确的执行了,UserService已经是AOP代理过的对象了,我们的注解也正常工作了。然后是dummyString和dummyInt,以及依赖的UserDao,也都正常工作,代表这一章的主题:为代理对象注入属性,顺利完成了~

相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring,这里对应的代码是mini-spring-15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值