前言
上次我们的标题中用了【超进化】这个词,从配置文件升级到用注解来进行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;
@Override
public 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();
}
@AllArgsConstructor
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final Properties properties;
@Override
public 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);
@Override
public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
this.embeddedValueResolvers.add(valueResolver);
}
@Override
public 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;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeanException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
return bean;
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeanException {
return null;
}
@Override
public 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);
}
}
@Override
public 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
中进行实现。
@Override
public <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
中。
@Override
public <T> T getBean(Class<T> requiredType) throws BeanException {
return getBeanFactory().getBean(requiredType);
}
嗯,甩手掌柜了属于是。直接让BeanFactory帮他干活。
到这里,我们的注解处理就算完成了。不过这里还有一点要注意,因为我们扩充了 InstantiationAwareBeanPostProcessor
的内容,所以我们之前关于AOP相关的 DefaultAdvisorAutoProxyCreator
,也要增加相应的方法,做个默认处理就行。
织入!将注解的解析融合进Bean生命周期
对于这一套,我想大家经过了这么多次练习,已经逐渐的熟悉了。一旦牵扯到后置处理器,就会有Bean生命周期的织入。由此也可以看出,后置处理器真的是Spring中很重要的部分。我们很多的逻辑扩充,都可以通过它来实现,然后再插入到Bean生命周期的某个过程中。
那么这次的织入,我们要放在什么节点呢?答案是在bean实例化完成之后,设置属性之前。其实也很好理解,这个时候属性中还是一堆占位符,我们要把它们替换成真正的值。对于引用的Bean,现在也只是个字符串,甚至只是个注解,所以我们也要在这一步进行处理。好了,我们又要来折磨 AbstractAutowireCapableBeanFactory
了。这回它的这个名字总算是有些能够理解了,不再是有名无实,不明所以,白白的挂着Autowire名号的一个类了。
//实例化bean
bean = 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() {
//注册处理注解的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);
}
这样就完成了,用户可以直接使用注解和占位符,不需要额外的配置了。
解放双手,测试环节
终于,又一次来到了测试环节。这次我们要做的是测试注解的使用。大体上的测试类,和上一次没什么区别。不过这一次,我们要把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;
@Autowired
private 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));
}
}
@Override
public void destroy() throws Exception {
System.out.println("userService的destroy执行了");
}
@Override
public 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 {
@Test
public 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