前言
最近项目里有大量的 “长的很像的代码”。这类代码,用到了工厂模式、建造者模式,但是最终由于业务复杂,而且功能上是对接平台的,因此会有上百个这种类,每个都不一样。
出于对其优化的目的,我重学了下 Spring 框架,看看从框架层面,能不能有什么突破点。 后来,在对 Spring 的 Bean 的生命周期的学习中,看中了 BeanPostProcessor 这个前置和后置处理器。发现这框架还是得好好研究下,可能能让我们目前的项目代码能更加灵活、易懂。甚至达到开闭原则。
也正是这种想法,那对于简单的学习框架的使用,已经不能满足了,得从源码学起。而学习源码的第一课就是【写一个简易版的 Spring 框架】,用以熟悉框架中的思想。
我的简易版的代码位于这个仓库中: https://gitee.com/fengsoshuai/demo-spring
目前已实现
1 简单校验
主要是对 Configuration 注解和 ComponentScan 注解的校验,要求配置类上要有这俩注解。
2 扫描配置
根据启动时传入的配置类,获取配置类上的注解 ComponentScan 中的参数,即包名。 根据包名扫描该包下的所有的 .class 文件,包的深度现在设置的是20,应该是够用了。
将扫描到的 .class 文件名,使用字符串截取的方式,组装出要用的类的全限定名。
3 根据扫描到的配置组装 BeanDefinition
根据第2步获取到的类名,加载到类。得到 Class。 再根据Class解析Class上是否带有 Component注解。
如果带有 Component 注解,将该注解上的属性进行解析,作为 bean 的名称。如果没有指定名称默认使用类名小写第一个字母作为bean的名称。
BeanDefinition 中有 以下属性:
private Class<?> beanClass;
private String scope;
private boolean isLazy;
其中 beanClass 是bean对应的 Class 就是根据第2步获得的类的完整名称加载出来的。 scope 用来表示是单例bean还是多例bean。 isLazy表示当前生成bean是否是懒加载。
最终存储时,使用 beanName 作为 key,BeanDefinition 作为 value存储到一个Map中。
4 根据 BeanDefinition 创建非懒加载、并且是单例的bean,存储到单例池
这一步是根据 BeanDefinition 中的 beanClass,获取到该类的无参数构造器,在根据这个无参数构造器生成一个bean,同时将该bean存储到单例池中。
5 增加 BeanNameAware 接口
BeanNameAware 当类中需要使用 beanName时,实现此接口。 在初始化设置属性之后,对beanName 进行赋值。
6 增加 InitializingBean 接口
当对实例需要做属性设置值之后,再进行操作。 常用于对实例的属性值进行校验。
7 增加 BeanPostProcessor 接口
bean的后置处理器: 常用于扩展,在Bean初始化,设置属性值之后执行。或在刚刚通过构造器创建实例后执行(设置属性之前)。
延伸阅读
测试&包文件说明 在org.feng.demo包下,有一个Test类,目前暂时在里边使用 main方法进行测试。 至于 org.feng.framework,就是模拟spring框架的注解,接口,类的定义的位置。
其中各个注解的具体实现逻辑,基本都在DefaultAnnotationApplicationContext类内。
手写 Spring的代码
所有的代码就是这红框中的内容了。至于 org.feng.demo,是为了测试这个框架好不好用写的测试。
ApplicationContext
package org.feng.framework;
public interface ApplicationContext extends BeanFactory {
}
Autowird
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowird {
}
BeanDefinition
package org.feng.framework;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BeanDefinition {
private Class<?> beanClass;
private String scope;
private boolean isLazy;
}
BeanFactory
package org.feng.framework;
public interface BeanFactory {
Object getBean(String name);
<T>T getBean(String name, Class<T> clazz);
}
BeanNameAware
package org.feng.framework;
public interface BeanNameAware {
void setBeanName(String name);
}
BeanPostProcessor
package org.feng.framework;
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName){
return bean;
};
default Object postProcessAfterInitialization(Object bean, String beanName){
return bean;
};
}
Component
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
ComponentScan
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value();
}
Configuration
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Configuration {
}
DefaultAnnotationApplicationContext
package org.feng.framework;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class DefaultAnnotationApplicationContext implements ApplicationContext {
private final Class<?> configClass;
private static final String PROTOTYPE = "prototype";
private static final String SINGLETON = "singleton";
private static final Map<String, BeanDefinition> BEAN_DEFINITION_MAP = new HashMap<>(32);
private static final Map<String, Object> SINGLETON_MAP = new ConcurrentHashMap<>();
private static final List<BeanPostProcessor> BEAN_POST_PROCESSORS = new ArrayList<>();
public DefaultAnnotationApplicationContext(Class<?> configClass) {
this.configClass = configClass;
check(configClass);
scanConfig();
createNonLazySingletonBean();
}
@Override
public Object getBean(String name) {
BeanDefinition beanDefinition = BEAN_DEFINITION_MAP.get(name);
if (beanDefinition != null) {
if (SINGLETON.equals(beanDefinition.getScope())) {
if (beanDefinition.isLazy()) {
if (SINGLETON_MAP.containsKey(name)) {
return SINGLETON_MAP.get(name);
}
Object instance = creatInstance(name, beanDefinition);
SINGLETON_MAP.put(name, instance);
return instance;
}
return SINGLETON_MAP.get(name);
}
if (PROTOTYPE.equals(beanDefinition.getScope())) {
return creatInstance(name, beanDefinition);
}
}
return null;
}
@Override
public <T> T getBean(String name, Class<T> clazz) {
BeanDefinition beanDefinition = BEAN_DEFINITION_MAP.get(name);
if (beanDefinition != null) {
if (SINGLETON.equals(beanDefinition.getScope()) && clazz == beanDefinition.getBeanClass()) {
if (beanDefinition.isLazy()) {
if (SINGLETON_MAP.containsKey(name)) {
return (T) SINGLETON_MAP.get(name);
}
Object instance = creatInstance(name, beanDefinition);
SINGLETON_MAP.put(name, instance);
return (T) instance;
}
return (T) SINGLETON_MAP.get(name);
}
if (PROTOTYPE.equals(beanDefinition.getScope()) && clazz == beanDefinition.getBeanClass()) {
return (T) creatInstance(name, beanDefinition);
}
}
return null;
}
private void scanConfig() {
ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
String packageName = componentScan.value();
List<String> fullClassNameList = parsePackageToFullClassNameList(packageName);
for (String className : fullClassNameList) {
try {
Class<?> aClass = DefaultAnnotationApplicationContext.class.getClassLoader().loadClass(className);
if (aClass.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClass(aClass);
if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
BEAN_POST_PROCESSORS.add((BeanPostProcessor) aClass.getDeclaredConstructor().newInstance());
}
if (aClass.isAnnotationPresent(Lazy.class)) {
beanDefinition.setLazy(true);
}
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scope = aClass.getAnnotation(Scope.class);
String scopeValue = scope.value();
if (scopeValue == null || "".equals(scopeValue) || SINGLETON.equals(scopeValue)) {
beanDefinition.setScope(SINGLETON);
}
if (PROTOTYPE.equals(scopeValue)) {
beanDefinition.setScope(PROTOTYPE);
}
} else {
beanDefinition.setScope(SINGLETON);
}
Component component = aClass.getAnnotation(Component.class);
String beanName = fixBeanName(component.value(), aClass);
BEAN_DEFINITION_MAP.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
private void createNonLazySingletonBean() {
BEAN_DEFINITION_MAP.forEach((beanName, beanDefinition) -> {
if (!beanDefinition.isLazy() && SINGLETON.equals(beanDefinition.getScope())) {
Object instance = creatInstance(beanName, beanDefinition);
SINGLETON_MAP.put(beanName, instance);
}
});
}
private Object creatInstance(String beanName, BeanDefinition beanDefinition) {
Object instance = null;
Class<?> beanClass = beanDefinition.getBeanClass();
try {
instance = beanClass.getDeclaredConstructor().newInstance();
for (BeanPostProcessor beanPostProcessor : BEAN_POST_PROCESSORS) {
beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
}
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(Autowird.class)) {
Object fieldBean = getBean(declaredField.getName());
declaredField.setAccessible(true);
declaredField.set(instance, fieldBean);
}
}
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
for (BeanPostProcessor beanPostProcessor : BEAN_POST_PROCESSORS) {
beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return instance;
}
private List<String> parsePackageToFullClassNameList(String packageName) {
ClassLoader classLoader = DefaultAnnotationApplicationContext.class.getClassLoader();
String path = packageName.replaceAll("\\.", "/");
URL resource = classLoader.getResource(path);
assert resource != null;
try {
return Files.walk(Path.of(resource.toURI()), 20)
.map(Path::toFile)
.filter(file -> file.getName().endsWith(".class"))
.map(File::getAbsolutePath)
.map(filePath -> filePath.replaceAll("\\\\", "/"))
.map(filePath -> filePath.substring(filePath.indexOf(path), filePath.indexOf(".class")))
.map(filePath -> filePath.replaceAll("/", "."))
.collect(Collectors.toList());
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
return Collections.emptyList();
}
private String fixBeanName(String beanName, Class<?> aClass) {
if (beanName == null || "".equals(beanName)) {
String simpleName = aClass.getSimpleName();
String firstLetter = simpleName.substring(0, 1);
return firstLetter.toLowerCase(Locale.ROOT) + simpleName.substring(1);
}
return beanName;
}
private void check(Class<?> configClass) {
boolean isConfigClass = configClass.isAnnotationPresent(Configuration.class);
if (!isConfigClass) {
throw new RuntimeException("请给配置类上加 Configuration 注解");
}
boolean hasComponentScan = configClass.isAnnotationPresent(ComponentScan.class);
if (!hasComponentScan) {
throw new RuntimeException("请给配置类上加 ComponentScan 注解");
}
}
}
InitializingBean
package org.feng.framework;
public interface InitializingBean {
void afterPropertiesSet();
}
Lazy
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Lazy {
}
Scope
package org.feng.framework;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "";
}
|