作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!? ❞
忒复杂,没等搞明白大促都过去了!
你经历过618
和双11
吗?你加入过大促时候那么多复杂的营销活动赚几毛钱吗?你开发过连读明白玩法都需要一周但只使用3天的大促需求吗?有时候对于有些产品的需求真的是太复杂了,复杂到开发、测试都需要在整个过程中不断的学习最后才可能读懂产品为啥这样的玩,要是一个长期的活动可能也就算了,培养用户心智吗!但这一整套拉新、助力、激活、下单、投保、领券、消费、开红包等等一连串的骚操作下来,如果在线上只用3天呢,或者是只用1天,那TM连参与的用户都没弄明白呢,活动就结束了,最后能打来什么样好的数据呢?对于这样流程复杂,估计连羊毛党都看不上!!!
以上只是举个例子,大部分时候并不会搞的这么恶心,评审也是过不去的!而同样的道理用在程序设计开发和使用中也是一样的,如果你把你的代码逻辑实现的过于分散,让外部调用方在使用的时候,需要调用你的接口多个和多次,还没有消息触达,只能定时自己轮训你的接口查看订单状态,每次还只能查10条,查多了你说不行,等等反人类的设计,都会给调用方带来要干你的体会。
所以,如果我们能在完成目的的情况下,都是希望尽可能流程简单、模式清晰、自动服务。那这在Spring的框架中也是有所体现的,这个框架的普及使用程度和它所能带来的方便性是分不开的,而我们如果能做到如此的方便,那肯定是一种好的设计和实现。
其实到本章节我们已经把关于 IOC 和 AOP 全部核心内容都已经实现完成了,只不过在使用上还有点像早期的 Spring 版本,需要一个一个在 spring.xml 中进行配置。这与实际的目前使用的 Spring 框架还是有蛮大的差别,而这种差别其实都是在核心功能逻辑之上建设的在更少的配置下,做到更简化的使用。
这其中就包括:包的扫描注册、注解配置的使用、占位符属性的填充等等,而我们的目标就是在目前的核心逻辑上填充一些自动化的功能,让大家可以学习到这部分的设计和实现,从中体会到一些关于代码逻辑的实现过程,总结一些编码经验。
首先我们要考虑?,为了可以简化 Bean 对象的配置,让整个 Bean 对象的注册都是自动扫描的,那么基本需要的元素包括:扫描路径入口、XML解析扫描信息、给需要扫描的Bean对象做注解标记、扫描Class对象摘取Bean注册的基本信息,组装注册信息、注册成Bean对象。那么在这些条件元素的支撑下,就可以实现出通过自定义注解和配置扫描路径的情况下,完成 Bean 对象的注册。除此之外再顺带解决一个配置中占位符属性的知识点,比如可以通过 ${token}
给 Bean 对象注入进去属性信息,那么这个操作需要用到 BeanFactoryPostProcessor,因为它可以处理 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制 而实现这部分内容是为了后续把此类内容结合到自动化配置处理中。整体设计结构如下图:
结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。
在XmlBeanDefinitionReader中解析<context:component-scan />
标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。
<context:component-scan />
配置包路径并在 XmlBeanDefinitionReader 解析并做相应的处理。这里的处理会包括对类的扫描、获取注解信息等BeanFactoryPostProcessor
的使用,因为我们需要完成对占位符配置信息的加载,所以需要使用到 BeanFactoryPostProcessor 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,修改 BeanDefinition 的属性信息。这一部分的实现也为后续处理关于占位符配置到注解上做准备small-spring-step-12
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ └── AspectJExpressionPointcut.java
│ │ │ └── AspectJExpressionPointcutAdvisor.java
│ │ ├── framework
│ │ │ ├── adapter
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── autoproxy
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├── JdkDynamicAopProxy.java
│ │ │ ├── ProxyFactory.java
│ │ │ └── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java
│ │ ├── Advisor.java
│ │ ├── BeforeAdvice.java
│ │ ├── ClassFilter.java
│ │ ├── MethodBeforeAdvice.java
│ │ ├── MethodMatcher.java
│ │ ├── Pointcut.java
│ │ ├── PointcutAdvisor.java
│ │ └── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ ├── InstantiationAwareBeanPostProcessor.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ ├── ListableBeanFactory.java
│ │ │ └── PropertyPlaceholderConfigurer.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── annotation
│ │ │ ├── ClassPathBeanDefinitionScanner.java
│ │ │ ├── ClassPathScanningCandidateComponentProvider.java
│ │ │ └── Scope.java
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ ├── stereotype
│ │ └── Component.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── IUserService.java
│ └── UserService.java
└── ApiTest.java
工程源码:公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码
在Bean的生命周期中自动加载包扫描注册Bean对象和设置占位符属性的类关系,如图 14-2
图 14-2
cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {
/**
* Default placeholder prefix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/**
* Default placeholder suffix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
private String location;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 加载属性文件
try {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(location);
Properties properties = new Properties();
properties.load(resource.getInputStream());
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
Object value = propertyValue.getValue();
if (!(value instanceof String)) continue;
String strVal = (String) value;
StringBuilder buffer = new StringBuilder(strVal);
int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {
String propKey = strVal.substring(startIdx + 2, stopIdx);
String propVal = properties.getProperty(propKey);
buffer.replace(startIdx, stopIdx + 1, propVal);
propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buffer.toString()));
}
}
}
} catch (IOException e) {
throw new BeansException("Could not load properties", e);
}
}
public void setLocation(String location) {
this.location = location;
}
}
buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue
cn.bugstack.springframework.context.annotation.Scope
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
String value() default "singleton";
}
cn.bugstack.springframework.stereotype.Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
cn.bugstack.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
public class ClassPathScanningCandidateComponentProvider {
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
Set<Class<?>> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class);
for (Class<?> clazz : classes) {
candidates.add(new BeanDefinition(clazz));
}
return candidates;
}
}
basePackage=cn.bugstack.springframework.test.bean
,解析出 classes 信息的工具方法 findCandidateComponents,通过这个方法就可以扫描到所有 @Component 注解的 Bean 对象了。cn.bugstack.springframework.context.annotation.ClassPathBeanDefinitionScanner
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
private BeanDefinitionRegistry registry;
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public void doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition beanDefinition : candidates) {
// 解析 Bean 的作用域 singleton、prototype
String beanScope = resolveBeanScope(beanDefinition);
if (StrUtil.isNotEmpty(beanScope)) {
beanDefinition.setScope(beanScope);
}
registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);
}
}
}
private String resolveBeanScope(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Scope scope = beanClass.getAnnotation(Scope.class);
if (null != scope) return scope.value();
return StrUtil.EMPTY;
}
private String determineBeanName(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Component component = beanClass.getAnnotation(Component.class);
String value = component.value();
if (StrUtil.isEmpty(value)) {
value = StrUtil.lowerFirst(beanClass.getSimpleName());
}
return value;
}
}
cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException, DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
// 解析 context:component-scan 标签,扫描包中的类并提取相关信息,用于组装 BeanDefinition
Element componentScan = root.element("component-scan");
if (null != componentScan) {
String scanPath = componentScan.attributeValue("base-package");
if (StrUtil.isEmpty(scanPath)) {
throw new BeansException("The value of base-package attribute can not be empty or null");
}
scanPackage(scanPath);
}
// ... 省略其他
// 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
private void scanPackage(String scanPath) {
String[] basePackages = StrUtil.splitToArray(scanPath, ',');
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
scanner.doScan(basePackages);
}
}
component-scan
,解析后调用 scanPackage 方法,其实也就是我们在 ClassPathBeanDefinitionScanner#doScan 功能。@Component("userService")
public class UserService implements IUserService {
private String token;
public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小傅哥,100001,深圳";
}
public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注册用户:" + userName + " success!";
}
@Override
public String toString() {
return "UserService#token = { " + token + " }";
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
@Component("userService")
和一个属性信息 String token
。这是为了分别测试包扫描和占位符属性。token=RejDlI78hu223Opo983Ds
spring-property.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context">
<bean class="cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:token.properties"/>
</bean>
<bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
<property name="token" value="${token}"/>
</bean>
</beans>
classpath:token.properties
设置占位符属性值 ${token}
spring-scan.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context">
<context:component-scan base-package="cn.bugstack.springframework.test.bean"/>
</beans>
component-scan
属性,设置包扫描根路径@Test
public void test_property() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-property.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("测试结果:" + userService);
}
测试结果
测试结果:UserService#token = { RejDlI78hu223Opo983Ds }
Process finished with exit code 0
token.properties
的属性值了。@Test
public void test_scan() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-scan.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("测试结果:" + userService.queryUserInfo());
}
测试结果
测试结果:小傅哥,100001,深圳
Process finished with exit code 0
- END -