手写Spring-第十四章-再次进化!用注解完成属性的注入
前言
上次我们的标题中用了【超进化】这个词,从配置文件升级到用注解来进行bean的注册,这确实可以称得上是超进化。但总觉得进化的不是那么完全,大概是从亚古兽进化到暴龙兽这样的程度(?)。因为我们还是在测试过程中,在配置文件中进行了一些Bean的配置。这些配置,不仅是为了测试两种Bean注册方式都可用,更多的是一种无奈之举。因为我们还没法用注解往bean中注册一些属性和依赖的bean。所以我们只能用原始的方式来注册。那么,这一章,我们就来完成这一块内容,让我们可以用注解完成所有的工作,彻底摆脱配置文件。
工程结构
├─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
│ │ │ │ │ AspectJExpressionPointcut.java
│ │ │ │ │ AspectJExpressionPointcutAdvisor.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
│ │ UserDao.java
│ │ UserService.java
│ │
│ └─resources
│ application.yml
│ spring.xml
如果你真的看了这个长的不行的工程目录(笑),你就可以看到,我们熟悉的 @Autowired
,终于来了。可以说,@Autowired
就是Spring!它是Spring之魂!
我已经等不及了,快点端上来罢!三大注解!
这次,我们准备完成三个注解的内容:@Value、@Autowired、@Qualifier。这三个注解,想必大家已经熟悉的不能再熟悉了。我们这就来实现它们。
package com.akitsuki.springframework.beans.factory.annotation;import java.lang.annotation.*;/*** @author ziling.wang@hand-china.com* @date 2022/12/12 10:28*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {String value();
}
package com.akitsuki.springframework.beans.factory.annotation;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/12 10:27*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface Autowired {
}
package com.akitsuki.springframework.beans.factory.annotation;import java.lang.annotation.*;/*** @author ziling.wang@hand-china.com* @date 2022/12/12 10:32*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {String value() default "";
}
注解本身没什么好说的,也不是很复杂,只是规定了注解可以加在哪些位置,以及有哪些属性而已。
接下来,我们按顺序来。首先,我们实现 @Value
的功能。我们知道,这个注解,是用来给Bean中的属性注入值的。我们之前实现这个功能,都是在配置文件中,用属性来定义的。关于占位符的处理,我们上一章已经实现了。我们这一次要对其进行一些改造,加入对 @Value
注解中,占位符的处理。
首先,我们要有一个字符串解析接口。这个接口专门用来解析处理字符串占位符
package com.akitsuki.springframework.util;/*** 字符串解析接口* @author ziling.wang@hand-china.com* @date 2022/12/12 9:36*/
public interface StringValueResolver {/*** 解析字符串* @param value* @return*/String resolveStringValue(String value);
}
然后,我们就要对上一章实现的 PropertyPlaceholderConfigurer
进行改造了。改造的内容呢,就是在原本的解析配置文件占位符并替换的基础上,将读取到的配置文件内容(被放在Properties中,还记得吗?),用字符串解析类包装起来,放到BeanFactory中备用。这么说可能会觉得有些乱,我们先来看代码。
package com.akitsuki.springframework.beans.factory;import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.annotation.Value;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanFactoryPostProcessor;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import com.akitsuki.springframework.core.io.DefaultResourceLoader;
import com.akitsuki.springframework.core.io.Resource;
import com.akitsuki.springframework.util.StringValueResolver;
import lombok.AllArgsConstructor;import java.io.IOException;
import java.util.Properties;/*** 处理spring配置中的占位符${xxx},替换为真正的值* @author ziling.wang@hand-china.com* @date 2022/12/9 9:59*/
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";/*** 保存变量值的配置文件位置*/private String location;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {try {//原有逻辑省略,处理Bean定义中的占位符//添加字符串解析器,供解析@Value注解使用StringValueResolver resolver = new PlaceholderResolvingStringValueResolver(properties);beanFactory.addEmbeddedValueResolver(resolver);} catch (IOException e) {throw new BeanException("加载配置时出错", e);}}/*** 处理占位符* @param value* @param properties* @return*/private String resolvePlaceholder(String value, Properties properties) {StringBuilder buffer = new StringBuilder(value);int startIdx = value.indexOf(DEFAULT_PLACEHOLDER_PREFIX);int stopIdx = value.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {String propKey = value.substring(startIdx + 2, stopIdx);String propVal = properties.getProperty(propKey);buffer.replace(startIdx, stopIdx + 1, propVal);}return buffer.toString();}@AllArgsConstructorprivate class PlaceholderResolvingStringValueResolver implements StringValueResolver {private final Properties properties;@Overridepublic String resolveStringValue(String value) {//这种写法等效于this,但这样写能够体现出resolvePlaceholder这个方法是PropertyPlaceholderConfigurer这个类的return PropertyPlaceholderConfigurer.this.resolvePlaceholder(value, properties);}}
}
好像看起来还是有点复杂?首先我们定义了一个内部类,实现了我们上面的字符串解析接口。这个类的具体实现呢,其实也就是调用外部类的 resolvePlaceholder
方法。这个方法的内容,也就是我们上次实现的对占位符的替换过程,这个核心逻辑是没有变的。然后我们在后置处理器的最后,注册了一个字符串处理器到BeanFactory中。这个过程是为了后面对 @Value
注解进行处理的时候,能拿到这个处理器。
这里的 embeddedValueResolver
之前可能没有见过,我们需要在 AbstractBeanFactory
中维护一个 StringValueResolver
类型的List,这里的 addEmbeddedValueResolver
实际上也就是往这个List中添加一条。这个方法放在了 ConfigurableBeanFactory
中进行定义,在 AbstractBeanFactory
进行实现。同时,这个接口还增加了另外一个方法 resolveEmbeddedValue
,就是真正处理的逻辑,也是在 AbstractBeanFactory
中进行实现。
/*** 添加一个StringValueResolver* @param valueResolver*/void addEmbeddedValueResolver(StringValueResolver valueResolver);/*** 处理StringValue* @param value* @return*/String resolveEmbeddedValue(String value);
@Overridepublic void addEmbeddedValueResolver(StringValueResolver valueResolver) {this.embeddedValueResolvers.add(valueResolver);}@Overridepublic String resolveEmbeddedValue(String value) {String result = value;for (StringValueResolver resolver : this.embeddedValueResolvers) {result = resolver.resolveStringValue(value);}return result;}
逻辑还是比较简单的。我们接下来开始关注对上面的三个注解的具体处理。
对于这些注解的处理,我们自然也是放在后置处理器中。但是用哪个后置处理器呢?答案是我们前不久实现的 InstantiationAwareBeanPostProcessor
。还记得这个处理器吗?之前是为了提供一种特殊的bean实例化过程,不走标准的实例化流程。这一次,我们要扩充它的功能,将对属性的处理,也添加到它的功能中。
package com.akitsuki.springframework.beans.factory.config;import com.akitsuki.springframework.beans.exception.BeanException;/*** @author ziling.wang@hand-china.com* @date 2022/12/6 15:46*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {/*** bean实例化之前处理** @param beanClass* @param beanName* @return* @throws BeanException*/Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException;/*** 处理属性值* @param pvs* @param bean* @param beanName* @return* @throws BeanException*/PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeanException;
}
接下来,我们就要实现我们的注解处理类了。
package com.akitsuki.springframework.beans.factory.annotation;import cn.hutool.core.bean.BeanUtil;
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.InstantiationAwareBeanPostProcessor;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import com.akitsuki.springframework.util.ClassUtils;import java.lang.reflect.Field;/*** @author ziling.wang@hand-china.com* @date 2022/12/12 10:35*/
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private ConfigurableListableBeanFactory beanFactory;@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeanException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {return bean;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException {return null;}@Overridepublic PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeanException {Class<?> clazz = bean.getClass();clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;Field[] fields = clazz.getDeclaredFields();for(Field field : fields) {resolveValueAnnotation(bean, field);resolveAutowiredAnnotation(bean, field);}return null;}/*** 处理@Value注解* @param bean* @param field*/private void resolveValueAnnotation(Object bean, Field field) {Value valueAnnotation = field.getAnnotation(Value.class);if (null != valueAnnotation) {String value = valueAnnotation.value();value = beanFactory.resolveEmbeddedValue(value);BeanUtil.setFieldValue(bean, field.getName(), value);}}/*** 处理@Autowired和@Qualifier注解* @param bean* @param field*/private void resolveAutowiredAnnotation(Object bean, Field field) {Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);if (null != autowiredAnnotation) {Class<?> fieldType = field.getType();String dependBeanName;Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);Object dependBean;if (null != qualifierAnnotation) {dependBeanName = qualifierAnnotation.value();dependBean = beanFactory.getBean(dependBeanName, fieldType);} else {dependBean = beanFactory.getBean(fieldType);}BeanUtil.setFieldValue(bean, field.getName(), dependBean);}}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeanException {this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;}
}
挺长的,我们来分析。
首先前面三个方法,都是我们用不上,但是接口规定了的。所以我们也就返回一些默认值即可。要返回对象的,就直接返回传入的对象。而对于需要实例化对象的方法,则直接返回null。
我们的重点在 postProcessPropertyValues
方法上。这个方法可以粗略的概括为:拿到bean的所有属性进行遍历,然后分别处理属性上的 @Value
注解和 @Autowired
注解。
先来看 @Value
注解,可以看到这里调用了BeanFactory中的 resolveEmbeddedValue
来处理,最后用反射重新设置进Bean中,还是比较简单的。
然后是 @Autowired
注解,这个就稍微复杂一点,还包括了 @Qualifier
的处理。我们概括一下内容,就是如果这个属性既有 @Autowired
注解,也有 @Qualifier
注解,则以 @Qualifier
中配置的值作为beanName,去向BeanFactory索要Bean。如果只有 @Autowired
注解,则直接通过Field的类型来查找bean。最后也是通过反射,将Bean进行注入。
这里多了按类型查找bean的方法,我们介绍一下。
这个方法我们新增在 BeanFactory
接口中,在 DefaultListableBeanFactory
中进行实现。
@Overridepublic <T> T getBean(Class<T> requiredType) throws BeanException {List<Map.Entry<String, BeanDefinition>> filteredDefinition = beanDefinitionMap.entrySet().stream().filter(x -> requiredType.isAssignableFrom(x.getValue().getBeanClass())).collect(Collectors.toList());if (1 == filteredDefinition.size()) {return getBean(filteredDefinition.get(0).getKey(), requiredType);}throw new BeanException(requiredType + "expect 1 single bean but found " + filteredDefinition.size() + " "+ filteredDefinition.stream().map(Map.Entry::getKey).collect(Collectors.toList()));}
可以看到,其实本质上我们还是按照名称进行查找。只不过流程是从Bean定义中,按照类型过滤出对应的Bean定义,如果只有一条,那证明我们找到了,之后就是拿到这个Bean定义对应的名称,去调用原有的按名称getBean即可。如果没有找到,或者找到了不止一条,那我们就得抛异常了。
因为我们在 BeanFactory
接口中新增了方法,那么我们同样实现经过多重继承实现了这个接口的Context,也要实现这个方法。最终,这个方法落实在了 AbstractApplicationContext
中。
@Overridepublic <T> T getBean(Class<T> requiredType) throws BeanException {return getBeanFactory().getBean(requiredType);}
嗯,甩手掌柜了属于是。直接让BeanFactory帮他干活。
到这里,我们的注解处理就算完成了。不过这里还有一点要注意,因为我们扩充了 InstantiationAwareBeanPostProcessor
的内容,所以我们之前关于AOP相关的 DefaultAdvisorAutoProxyCreator
,也要增加相应的方法,做个默认处理就行。
织入!将注解的解析融合进Bean生命周期
对于这一套,我想大家经过了这么多次练习,已经逐渐的熟悉了。一旦牵扯到后置处理器,就会有Bean生命周期的织入。由此也可以看出,后置处理器真的是Spring中很重要的部分。我们很多的逻辑扩充,都可以通过它来实现,然后再插入到Bean生命周期的某个过程中。
那么这次的织入,我们要放在什么节点呢?答案是在bean实例化完成之后,设置属性之前。其实也很好理解,这个时候属性中还是一堆占位符,我们要把它们替换成真正的值。对于引用的Bean,现在也只是个字符串,甚至只是个注解,所以我们也要在这一步进行处理。好了,我们又要来折磨 AbstractAutowireCapableBeanFactory
了。这回它的这个名字总算是有些能够理解了,不再是有名无实,不明所以,白白的挂着Autowire名号的一个类了。
//实例化beanbean = createBeanInstance(beanDefinition, beanName, args);//设置bean属性之前,允许beanPostProcessor修改属性值applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);//设置bean属性applyPropertyValues(beanName, beanDefinition, bean);//初始化bean,执行beanPostProcessor的前置和后置方法initializeBean(beanName, bean, beanDefinition);
protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {for(BeanPostProcessor processor : getBeanPostProcessors()) {if (processor instanceof InstantiationAwareBeanPostProcessor) {PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) processor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName);if (null != pvs) {for (PropertyValue pv : pvs.getPropertyValues()) {beanDefinition.getPropertyValues().addPropertyValue(pv);}}}}}
处理过程实在是有些乏善可陈,毕竟基本上后置处理器都是这么个流程,我们主要看这个过程插在哪里就可以了。
队长别开枪,是我!内部bean的注册
到这里其实我们的大体功能已经开发完毕了。但是在使用层面上还是有些问题。我们有一些内部bean,比如我们用来处理注解的后置处理器,还有用来处理占位符的bean等等。这些Bean交给用户去配置是不合适的,所以我们就要想办法在Spring框架运行起来的时候,就把它们给注册好。我们选择的节点是 ClassPathBeanDefinitionScanner
。还记得这个类吗?是之前用来做包扫描的,我们在扫描方法的最后,将内部的Bean给注册进去。
public void doScan(String... basePackages) {for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition beanDefinition : candidates) {String scope = resolveBeanScope(beanDefinition);if (StrUtil.isNotEmpty(scope)) {beanDefinition.setScope(scope);}registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);}}registerInnerBean();}/*** 注册Spring内部使用的bean*/private void registerInnerBean() {//注册处理注解的BeanPostProcessorregistry.registerBeanDefinition("autowiredAnnotationBeanPostProcessor", new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class));//注册处理属性占位符的beanBeanDefinition propertyPlaceholder = new BeanDefinition(PropertyPlaceholderConfigurer.class);PropertyValues propertyPlaceholderPv = new PropertyValues();propertyPlaceholderPv.addPropertyValue(new PropertyValue("location", "classpath:application.yml"));propertyPlaceholder.setPropertyValues(propertyPlaceholderPv);registry.registerBeanDefinition("propertyPlaceholderConfigurer", propertyPlaceholder);}
这样就完成了,用户可以直接使用注解和占位符,不需要额外的配置了。
解放双手,测试环节
终于,又一次来到了测试环节。这次我们要做的是测试注解的使用。大体上的测试类,和上一次没什么区别。不过这一次,我们要把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 InitializingBean, DisposableBean {@Value("${dummyString}")private String dummyString;@Value("${dummyInt}")private int dummyInt;@Autowiredprivate UserDao userDao;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));}}@Overridepublic void destroy() throws Exception {System.out.println("userService的destroy执行了");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("userService的afterPropertiesSet执行了");}
}
嗯,已经很有Spring的味道了对吧,@Component,@Autowired,@Value这些注解一加上去,熟悉的感觉就回来了。
然后我们看看现在的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.UserService;
import org.junit.Test;/*** @author ziling.wang@hand-china.com* @date 2022/11/15 13:58*/
public class ApiTest {@Testpublic void test() {ClasspathXmlApplicationContext context = new ClasspathXmlApplicationContext("classpath:spring.xml");context.registerShutdownHook();UserService userService = context.getBean("userService", UserService.class);userService.queryUserInfo(1L);}
}
依旧是老样子,没什么变化。
测试结果
执行UserDao的initMethod
userService的afterPropertiesSet执行了
dummyString:kamisama
dummyInt:114514
用户名:akitsuki
userService的destroy执行了Process finished with exit code 0
嗯,功能都很正常,这次的测试也圆满完成了~
相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring
,这里对应的代码是mini-spring-14
手写Spring-第十四章-再次进化!用注解完成属性的注入相关推荐
- 【正点原子STM32连载】第五十四章 手写识别实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- 《深入理解 Spring Cloud 与微服务构建》第十四章 服务链路追踪 Spring Cloud Sleuth
<深入理解 Spring Cloud 与微服务构建>第十四章 服务链路追踪 Spring Cloud Sleuth 文章目录 <深入理解 Spring Cloud 与微服务构建> ...
- 十年java架构师分享:我是这样手写Spring的
人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...
- 《Dreamweaver CS6 完全自学教程》笔记 第十四章:使用 CSS 设计网页
文章目录 第十四章:使用 CSS 设计网页 14.1 CSS 样式表简介 14.2 CSS 的基本语法 14.3 伪类.伪元素以及样式表的层叠顺序 14.3.1 伪类和伪元素 14.3.2 样式表的层 ...
- 天堂向左,深圳往右 第十三章第十四章
天堂向左,深圳往右 第十三章第十四章[@more@] 第十三章 周振兴是肖然见过的最严谨的人.此人一年四季打着领带,头发永远硬硬地顶在头上,绝不会有一根错乱,每天上班后都有个固定的程序:上厕所.擦桌子 ...
- 手写Spring DI依赖注入,嘿,你的益达!
手写DI 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBean ...
- 达芬奇密码 第七十四章
达芬奇密码 第七十四章[@more@] 第七十四章 "你怎么不说话呢?"兰登注视着"猎鹰者"号机舱对面的索菲说. "太累了.还有这首诗,我怎么也看不明 ...
- 【正点原子FPGA连载】第三十四章RGB-LCD触摸屏实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1
1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...
- 数学:确定性的丧失---第十四章 数学向何处去
发信人: paradax (秀树*冬眠中...), 信区: Philosophy 标 题: 数学:确定性的丧失(15) 发信站: 北大未名站 (2002年10月23日22:40:41 星期三), 转 ...
最新文章
- pytorch学习笔记(十二):详解 Module 类
- video怎么重新加载 vue_vue.js中vue-video-player中的怎么插入多个视频,视频可以同时播放的问题及解决办法...
- 小米KK:智能家居谁能破局?
- 您必须在sources.list中指定代码源_python如何从源代码构建lxml
- 做总账凭证FB50报错“错误调用功能模块 CHECK_PLANTS_ABROAD_ACTIVE”
- 前端学习(3105):react-hello-jsx语法规则
- mac 二进制安装mysql_在mac下安装mysql二进制分发版的方法(不是dmg的)
- NLP之路-实验nltk中的raw 和 words
- redis java操作
- 一、optimizer_trace介绍
- 史前技术:Mac使用SVN
- 疫情防控中小学开学错峰错时返校放学方案
- APP 基本框架设计
- 海马玩安卓模拟器-安装流程详解
- 维度打击,机器学习中的降维算法:ISOMAP MDS
- Java学习:从入门到精通week3
- OpenCV特征检测(三)SIFT,Surf及其引申的思考
- android 微信高仿,Android 高仿微信发朋友圈浏览图片效果(转)
- VS CODE Python 包路径报错解决方案could not be resolved
- python IO模块【一】:IO类
热门文章
- 真实的生活比戏剧更残酷
- HTTP协议报文解析
- EMBOSS的安装以及使用
- 荣誉丨国辰机器人荣获维科杯•OFweek 2021中国机器人行业年度优秀应用案例奖
- Mac锁屏的各种方法
- 《C++入门经典(第6版)》——2.2 程序的组成部分
- webview.addJavascriptInterface() doen not work on API 16+
- 瑞典留学硕士申请条件及流程
- Java:扑克牌发牌程序(除大小王)
- 论文阅读笔记:《PatchMatch Stereo - Stereo Matching with Slanted Support Windows》