前言
我们上一章实现了用注解为对象注入属性。但是还有一个地方我们没有照顾到,那就是代理对象属性的注入。你可能会疑惑,我们的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中拿到),就可以构建起这三个对象的依赖关系了。
MethodInterceptor
依赖用户定义的增强切面PointcutAdvisor
依赖MethodInterceptor
和表达式expression
接下来要做的,就是创建 MethodInterceptor
和 PointcutAdvisor
的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