这一期是我自己手写一个简单spring的一个记录, 里面的内容不一定和源码逻辑相同, 只是为了自己能手动实现spring的部分功能, 其中包括: @Component, @ComponentScan, @Scope, @Autowired注解, BeanDefinition类, ApplicationContext类以及BeanPostProcessor类.
springframework包为自定义模拟spring功能的包
user包为用户自己使用的包
首先我们为spring创建一个包名为spirngframework, 里面创建一个MyApplicationContext类并定义构造方法和getBean()方法
package com.yve.springframe;
/**
* @author 伟大的Yve菌
* 我们自己定义的一个spring的ApplicationContext类, 我们通过ApplicationContext可以获取到任意bean对象
*/
public class MyApplicationContext {
private Class appConfig;
public MyApplicationContext(Class appConfig) {
this.appConfig = appConfig;
}
public Object getBean(String beanName) {
return null;
}
}
之后再为我们user类创建一个AppConfig配置类, 一个Test运行类和service包以及一个UserService类, 在Test类中创建我们定义的MyApplicationContext
/**
* @author 伟大的Yve菌
* 用户的service类, 通过spring获取到Bean对象后调用test()方法测试
*/
package com.yve.user.service;
public class UserService {
public void test() {
System.out.println("userService");
}
}
package com.yve.user;
import com.yve.springframe.MyApplicationContext;
import com.yve.user.service.UserService;
/**
* @author 伟大的Yve菌
* 通过Test类启动主方法, 内部声明一个spring的ApplicationContext对象调用bean的方法
*/
public class Test {
public static void main(String[] args) {
MyApplicationContext context = new MyApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
}
我们现在通过getBean方法肯定无法获取userService, 一是我们需要通过传入MyApplicationContext中的AppConfig上的ComponentScan注解扫描路径, 其次是需要通过getBean()方法返回Bean对象.
我们接下来需要先定义一个@ComponentScan注解:
package com.yve.springframe;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value() default "";
}
之后我们就可以往AppConfig类上添加@ComponentScan注解来定义扫描路径.
package com.yve.user;
import com.yve.springframe.ComponentScan;
/**
* @author 伟大的Yve菌
* AppConfig配置类
*/
@ComponentScan("com.yve.user.service.UserService")
public class AppConfig {
}
我们定义完了扫描注解, 现在需要定义一个@Component注解, 有@Component的类才能被spring扫描到
package com.yve.springframe;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
之后我们就可以在UserService类上添加@Component注解, 之后再spring进行扫描时UserService类就可以被加载为一个Bean对象了
@Component
public class UserService {
为了方便与UserService对比我们再创建一个OrderService类
package com.yve.user.service;
import com.yve.springframe.Component;
/**
* @author 伟大的Yve菌
* orderservice类, 方便与UserService对比
*/
public class OrderService {
public void test() {
System.out.println("OrderService");
}
}
到现在基本的框架写好了, 我们通过创建ApplicationContext传入配置类AppConfig包, 之后spring就可以根据AppConfig包的@ComponentScan注解去扫描对应包, 之后把UserService类和OrderService类加载为bean. 但是目前我们并没有写扫描的逻辑, 我们接下来就去完成扫描相关的逻辑
我们需要在MyApplicationContext被创建的时候就去扫描对应路径然后把带有@Component的类加载为bean, 所以我们定义一个scan()方法
/**
* @param appConfig 传入配置类
* 此方法用于扫描classpath路径下需要被加载为bean的类, 并对他们进行存放.
*/
private void scan(Class appConfig) {
//首先判断当前类上是否有扫描路径注解
if (appConfig.isAnnotationPresent(ComponentScan.class)) {
//取出注解中的路径
ComponentScan componentScan = (ComponentScan) appConfig.getAnnotation(ComponentScan.class);
String path = componentScan.value(); //com.yve.user.service
//目前的到的是文件位置, 但是我们需要修改为路径
path = path.replace(".", "/"); //com/yve/user/service
//我们需要解析的的实际上是编译之后的class类上的注解, 也就是classpath下的对应目录, 而path中是我们java文件的路径, 所以我们需要ClassLoader来读取到classpath的位置
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
//获取对应路径下的所有文件
File file = new File(resource.getFile());
//判断文件是否为目录
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
//获取到每个文件的绝对路径
String absolutePath = listFile.getAbsolutePath(); //两个文件, 这里为了方便就写一个,\\为单斜杠 D:\\tuling\\code\\spring\\mySpring\\target\\classes\\com\\yve\\user\\service\\UserService.class
absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");
//System.out.println(absolutePath); //com.yve.user.service.OrderService
try {
//判断每个文件是否有@Component注解
Class<?> clazz = classLoader.loadClass(absolutePath);
if (clazz.isAnnotationPresent(Component.class)) {
System.out.println(clazz); //class com.yve.user.service.UserService
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
现在我们的scan方法可以获取到所有带有@Component注解的类了, 我们在UserService上添加了@Component注解, OrderService上并没有添加, 因此在打印clazz时只有UserService. 我们既然已经找到了哪些是要加载为Bean的类, 接下来就需要判断这些Bean的作用域, 因此我们先创建一个@Scope注解
package com.yve.springframe;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
String value() default "";
}
让我们接着回到scan()方法, 我们在确定了哪些类时bean之后还需要再次判断他们的作用域, 在真实的spring中有5中作用域, 在我们的spring中我们只设置两种, 一种为"singleton", 另一种为"prototype".
但是一个Bean中除了scope之外还有很多属性, 例如Bean的类型, 是否为懒加载等等, 我们不可能每次加载一个类都重新去判断, 因此我们可以创建一个BeanDefinition类, 将bean对象的内部属性全部存进去.
package com.yve.springframe;
/**
* @author 伟大的Yve菌
* Bean的内部属性类
*/
public class BeanDefinition {
private Class type;
private String scope;
public Class getType() {
return type;
}
public void setType(Class type) {
this.type = type;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
我们继续回到判断是否存在@Component的逻辑, 我们现在就可以把带有@Component的类的信息放入到BeanDefinition了
try {
//判断每个文件是否有@Component注解
Class<?> clazz = classLoader.loadClass(absolutePath);
if (clazz.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//如果该类有@Scope注解就保存为注解中的值, 否则默认为singleton
if (clazz.isAnnotationPresent(Scope.class)) {
beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
} else {
beanDefinition.setScope("singleton");
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
我们现在已经可以判断bean的作用域和数据类型了, 我们现在需要定义一个map来存放这些bean, beanName作为key, beanDefinition作为value.
现在我们扫描完之后就该把扫描出来的信息放入到beanDefinitionMap中了.
/**
* @param appConfig 传入配置类
* 此方法用于扫描classpath路径下需要被加载为bean的类, 并对他们进行存放.
*/
private void scan(Class appConfig) {
//首先判断当前类上是否有扫描路径注解
if (appConfig.isAnnotationPresent(ComponentScan.class)) {
//取出注解中的路径
ComponentScan componentScan = (ComponentScan) appConfig.getAnnotation(ComponentScan.class);
String path = componentScan.value(); //com.yve.user.service
//目前的到的是文件位置, 但是我们需要修改为路径
path = path.replace(".", "/"); //com/yve/user/service
//我们需要解析的的实际上是编译之后的class类上的注解, 也就是classpath下的对应目录, 而path中是我们java文件的路径, 所以我们需要ClassLoader来读取到classpath的位置
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
//获取对应路径下的所有文件
File file = new File(resource.getFile());
//判断文件是否为目录
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
//获取到每个文件的绝对路径
String absolutePath = listFile.getAbsolutePath(); //两个文件, 这里为了方便就写一个,\\为单斜杠 D:\\tuling\\code\\spring\\mySpring\\target\\classes\\com\\yve\\user\\service\\UserService.class
absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");
//System.out.println(absolutePath); //com.yve.user.service.OrderService
try {
//判断每个文件是否有@Component注解
Class<?> clazz = classLoader.loadClass(absolutePath);
if (clazz.isAnnotationPresent(Component.class)) {
String beanName = clazz.getAnnotation(Component.class).value();
//创建BeanDefinition保存Bean对象信息
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//如果该类有@Scope注解就保存为注解中的值, 否则默认为singleton
if (clazz.isAnnotationPresent(Scope.class)) {
beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
} else {
beanDefinition.setScope("singleton");
}
//把定义好的bean放入到beanDefinitionMap中
//如果@Component注解中没有值, 则取该类型首字母小写作为beanName
if(beanName.isEmpty()) {
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
接下来我们该完善getBean()方法了
/**
*
* @param beanName 通过beanName获取到bean对象
* @return bean对象
*
*/
public Object getBean(String beanName) {
//通过beanDefinitionMap获取到BeanDefinition并根据作用域来返回bean对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//如果没有beanName则证明该类型bean没有被声明
if (beanDefinition == null) {
return new NullPointerException("No bean definition");
}
if (beanDefinition.getScope().equals("singleton")) {
//这里是singleton类型的bean
} else {
//这里是prototype类型的bean
}
}
我们通过以上步骤可以根据不同bean的作用域获得bean对象, 因为singleton为单例, 所以我们需要创建一个单例池来把所有的单例bean存放进来, 后续使用直接从单例池中取出来, 我们需要在扫描完成之后时就需要进行遍历bean, 并把所有的单例bean存入单例池.
/**
*
* @param appConfig 传入配置类
* 配置类赋值, 扫描路径获取到bean对象之后把单例bean放入单例池
*/
public MyApplicationContext(Class appConfig) {
this.appConfig = appConfig;
scan(appConfig);
//遍历循环每个bean, 将所有的单例bean放入单例池中
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
//获取名字和信息
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
//如果是单例就创建并放入到单例池
if (beanDefinition.getScope().equals("singleton")) {
Object bean = createBean(beanName, beanDefinition);
singleObjects.put(beanName, bean);
}
}
}
createBean方法
/**
*
* @param beanName
* @param beanDefinition
* @return bean对象
* 该方法通过beanDefinition中的type的构造方法创建一个bean对象
*/
private Object createBean(String beanName, BeanDefinition beanDefinition) {
//通过type获取到类
Class clazz = beanDefinition.getType();
Object instance = null;
try {
//通过构造方法创建对象
instance = clazz.getConstructor().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return instance;
}
最后我们可以通过getBean()方法获取到bean对象了
/**
*
* @param beanName 通过beanName获取到bean对象
* @return bean对象
*
*/
public Object getBean(String beanName) {
//通过beanDefinitionMap获取到BeanDefinition并根据作用域来返回bean对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//如果没有beanName则证明该类型bean没有被声明
if (beanDefinition == null) {
return new NullPointerException("No bean definition");
}
if (beanDefinition.getScope().equals("singleton")) {
//这里是singleton类型的bean
return singleObjects.get(beanName);
} else {
//这里是prototype类型的bean
return createBean(beanName, beanDefinition);
}
}
现在我们基本完成了spring的功能, 我们可以通过@ComponentScan注解获取到扫描路径, 通过路径扫描找出所有@Component的类加载为bean对象, 并可以根据bean的作用域来获取. 我们验证一下
不设置作用域或者作用域设置为"Singleton"
作用域设置为"prototype"
我们现在就可以通过MyApplicationContext成功获取到我们的bean对象了, 这里我们的OrderService没有添加@Component注解, 因此扫描不到.
接下来我们在添加一些其他功能.首先添加@Autowired注解
package com.yve.springframe;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
我们在UserService中添加一个OrderService属性并注解@Autowired, 当然现在我们没有定义内部逻辑, spring无法为OrderService赋值
我们在创建bean时同时为成员变量注入
private Object createBean(String beanName, BeanDefinition beanDefinition) {
//通过type获取到类
Class clazz = beanDefinition.getType();
Object instance = null;
try {
//通过构造方法创建对象
instance = clazz.getConstructor().newInstance();
//为添加了@Autowired注解的属性赋值
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
//为属性赋值
field.set(instance, getBean(field.getName()));
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return instance;
}
这样的逻辑可能会出现bug, 因为我们没有办法判断如果有@Autowired的属性的作用域是"singleton"时单例池中是否已经加载过这个单例bean(我们必须保证单例bean只能存在一个), 如果没有的话我们需要先将他加入单例池之后再赋值, 我们为getBean()方法添加一点判断逻辑.
public Object getBean(String beanName) {
//通过beanDefinitionMap获取到BeanDefinition并根据作用域来返回bean对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//如果没有beanName则证明该类型bean没有被声明
if (beanDefinition == null) {
return new NullPointerException("No bean definition");
}
if (beanDefinition.getScope().equals("singleton")) {
//这里是singleton类型的bean
Object singletonBean = singleObjects.get(beanName);
//如果这个时候单例bean还没被加载就直接创建加载
if (singletonBean == null) {
singletonBean = createBean(beanName, beanDefinition);
singleObjects.put(beanName, singletonBean);
}
return singletonBean;
} else {
//这里是prototype类型的bean
return createBean(beanName, beanDefinition);
}
}
我们现在通过getBean方法获取UserService的bean对象调用test()方法就可以顺利的获取到内部带有@Autowired的OrderService属性了(运行时记得在OrderService类上加上@Component注解, OrderService没有被加载成bean, 就无法注入)
我们接下来完成bean的初始化操作. 我们创建一个InitializingBean接口并让UserService去实现
package com.yve.springframe;
/**
* @author 伟大的Yve菌
* 初始化Bean接口
*/
public interface InitializingBean {
void afterPropertiesSet();
}
@Component("userService")
@Scope("prototype")
public class UserService implements InitializingBean {
@Autowired
private OrderService orderService;
@Override
public void afterPropertiesSet() {
//我们可以在进行任何想对bean的初始化操作, 在创建bean之前就会完成
System.out.println("初始化userService");
}
public void test() {
System.out.println(orderService);
}
}
但是目前来说我们没办法在创建Bean时自动调用这个方法, 所以我们需要在CreateBean()方法中添加相应逻辑
初始化完成后我们将要进行定义一个BeanPostProcesser接口, 里面定义初始化前和初始化后的方法, 之后用户就可以自定义一个类去实现这个接口
package com.yve.springframe;
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) { return bean;}
default Object postProcessAfterInitialization(Object bean, String beanName) { return bean;}
}
package com.yve.user.service;
import com.yve.springframe.BeanPostProcessor;
/**
* @author 伟大的Yve菌
* 定义初始化前后的操作
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println(beanName + "初始化后");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println(beanName + "初始化前");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
}
现在我们要去在初始化操作的前后去添加这两个方法, 但是在这之前我们还需要在最开始扫描@Component注解时判断哪些带有@Component的类实现了BeanPostProcessor, 同时创建一个list来存放他
在scan判断@Component是添加判断逻辑
这样我们就可以在初始化的前后添加对应的操作了
现在我们就可以取到完整的经过BeanPostProcessor以及初始化的bean对象了, 我们可以通过这些方法进行很多操作, 例如aop, value注解等等
这样我们一个基础的spring框架就搭建好了.
那我们这次的手写简单spring框架就完成了, 如果需要源码的话可以私信我. 谢谢大家