在上一篇文章中,我们用了一个个简单的Demo去体验了一下Spring几个核心知识点的简单流程和原理。在这篇文章中,我们来按照上篇文章的流程大致写一个简单的启动过程来模拟一下Spring的启动流程,加深一下感觉。
我们继续来看一下上节文章中的这个入口代码:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
// ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("spring.xml");
final UserService userService = (UserService)context.getBean("userService");
userService.test();
}
- AnnotationConfigApplicationContext:
- 上文中讲到过,该类构造方法通过加载一个class类来获取配置信息(例如,通过@ComponentScan来获取扫描路径)作用与ClassPathXmlApplicationContext通过加载xml文件类似。
- 通过加载的配置类来获取扫描获取文件资源进行加载。
- 该类中还会提供一个getBean方法,接收一个参数,返回类型为Object。
本文简单介绍一下主要任务,大致是这么一个作用,当前Spring本身所做的远远不止这些,我们在在上篇文章中的基础之上在来体验一下Spring的加载过程。
先看下自定义的CustomerAnnotationConfigApplicationContext完整代码:
public class CustomerAnnotationConfigApplicationContext {
private Class configClass;
private Map beanDefinitionMap = new HashMap();
//单例池
private Map singletonObjects = new HashMap();
public CustomerAnnotationConfigApplicationContext(Class configClass) {
this.configClass = configClass;
//注解扫描
scanner(configClass);
//扫描beanDefinitionMap,对单例的进行创建bean
for (Map.Entry entry : beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.getScope().equals("singleton")) {
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
}
}
private void scanner(Class configClass) {
if (configClass.isAnnotationPresent(CustomerComponentScan.class)) {
//如果存在CustomerComponentScan注解,则获取注解信息得到value
CustomerComponentScan customerComponentScan = (CustomerComponentScan) configClass.getAnnotation(CustomerComponentScan.class);
//得到需要扫描的包路径
String path = customerComponentScan.value();
//将com.xxx.xx替换成 com/xxx/xx文件格式
path = path.replace(".", "/");
//获取CustomerAnnotationConfigApplicationContext类资源
ClassLoader classLoader = CustomerAnnotationConfigApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
//因为我本地目录存在中文,因此这里进行了解码。
File file = new File(URLDecoder.decode(resource.getFile()));
if (file.isDirectory()) {
//循环目录下文件
for (File f : file.listFiles()) {
//获取绝对路径
String absolutePath = f.getAbsolutePath();
//将文件路径转换为 com.xxx.xx
absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");
try {
//加载文件资源
final Class clazz = classLoader.loadClass(absolutePath);
//判断类上是否存在CustomerComponent注解
if (clazz.isAnnotationPresent(CustomerComponent.class)) {
//如果存在,则表示为一个bean,则封装BeanDefinition,这是重点。
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//该类上是 单例模型 还是 原型模式
if (clazz.isAnnotationPresent(CustomerScope.class)) {
String value = clazz.getAnnotation(CustomerScope.class).value();
beanDefinition.setScope(value);
} else {
beanDefinition.setScope("singleton");
}
//将封装的beanDefinition放入map
//获取map的key
String beanName = clazz.getAnnotation(CustomerComponent.class).value();
if ("".equals(beanName)) {
//首字母小写处理
beanName = beanName = Introspector.decapitalize(clazz.getSimpleName());
}
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName) {
if (!beanDefinitionMap.containsKey(beanName)) {
throw new NullPointerException("没有找到" + beanName + "的bean对象");
}
final BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
//单例bean直接吃单例池中返回。
Object singletonBean = singletonObjects.get(beanName);
return singletonBean;
} else {
//原型bean
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
private Object createBean(String beanName, BeanDefinition beanDefinition) {
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;
}
}
我们大致来梳理一下构造方法的流程:代码上也有较全的注释,可以阅读理解一下。
- scanner方法:
- 该方法首先判断是否存在自定义的CustomerComponentScan注解,如果存在则获取该注解上的包路径,通过处理得到包下的.class文件(当然这里包应该是递归查找,这里方法理解不做过多代码堆积)
- 当加载到.class文件后,判断是否存在CustomerComponent注解(可以是@Controoler、@Bean、@Servicer等等注解),然后进行封装成一个BeanDefinition对象(这里比较重要,实际在Spring中也确实是这么去做的,下文会解释到)。
- 遍历:
- 会进行BeanDefinitionMap遍历,将BeanDefinitionMap中的是单例(singleton)属性的进行创建bean对象。
解释一下BeanDefinition
public class BeanDefinition {
private Class type;
private String scope;
private boolean isLazy;
}
为什么需要beanDefintion,它主要用来定义一些bean的属性,这里简单描述了 scope(singleton、prototype)、是否懒加载,为什么需要这些呢。其实可以想象一下,如果一些类是懒加载的,也就是在getBean()的时候会去创建对象,那么在getBean的时候我们如何去知道该beanName对象的bean是否存在是懒加载、单例啊等等信息呢,他是不是又得通过扫描注解,加载文件资源等等信息重复判断,这些其实在CustomerAnnotationConfigApplicationContext中就已经去做了这些事情,然而CustomerAnnotationConfigApplicationContext中只会去创建单例的bean,因此方便后续的一些操作,Spring定义这一类对象用来存储一些bean的属性信息方便使用。
我们来看一下测试是否和上篇文章达到了同样的效果:
public static void main(String[] args) {
CustomerAnnotationConfigApplicationContext context = new CustomerAnnotationConfigApplicationContext(BeanConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
//console
spring
true
@CustomerComponent
@CustomerScope(value = "prototype") //singleton
public class UserService {
@CustomerAutowired
private AbService abService;
public void test(){
System.out.println("spring");
System.out.println(abService == null);
}
}
可以发现能正常打印,说明我们简单的完成了AnnotationConfigApplicationContext的功能。但是我们可以发现,System.out.println(abService == null); 是没有值的,也就是自定义的CustomerAutowired注解,没有进行依赖注入,我们在来解决一下
依赖注入修改一下CustomerAnnotationConfigApplicationContext的createBean方法:
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
Object instance = null;
//这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
try {
instance = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if(field.isAnnotationPresent(CustomerAutowired.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;
}
我们在得到Object对象的时候通过反射的机制去获取属性了,并判断是否存在CustomerAutowired是否进行属性赋值(依赖注入)。但是需要注意这一行field.set(instance,getBean(field.getName()));getBean我们实际上通过上面的代码我们是从单例池中去获取的,会出现这样一个问题,也能在进行此依赖注入的时候,Spring还没有加到到目标的value值,也就是在单例池中还不存在这样的一个bean对象。因此我们队getBean代码修改一下,如下:
public Object getBean(String beanName) {
if (!beanDefinitionMap.containsKey(beanName)) {
throw new NullPointerException("没有找到" + beanName + "的bean对象");
}
final BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
//单例bean直接吃单例池中返回。
Object singletonBean = singletonObjects.get(beanName);
if(singletonBean == null){
singletonBean = createBean(beanName,beanDefinition);
singletonObjects.put(beanName,singletonBean);
}
return singletonBean;
} else {
//原型bean
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
如果singletonBean为空,我们就会去主动创建这个bean这样就解决了这个问题。当然Spring本身需要考虑远远不止这些,这里就简单演示这么一个问题这样再次进行测试,就可以发现abservice的属性不为空了。在上篇文章中我们还提到一个InitializingBean这样一个处理。我们也来模拟一下。
InitializingBean在createBean方法中增加处理
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
Object instance = null;
//这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
try {
instance = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if(field.isAnnotationPresent(CustomerAutowired.class)){
field.setAccessible(true);
/**
* 这里我直接从单例池中获取值,这样是存在问题,有可能在做这一步操作的时候,单例池
* 中并没有还加载此bean
*/
field.set(instance,getBean(field.getName()));
}
}
if(instance instanceof CustomerInitializingBean){
((CustomerInitializingBean)instance).afterPropertiesSet();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
@CustomerComponent
@CustomerScope(value = "prototype") //singleton
public class UserService implements CustomerInitializingBean {
@CustomerAutowired
private AbService abService;
public void test(){
System.out.println("spring");
System.out.println(abService == null);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("CustomerInitializingBean afterPropertiesSet ");
}
}
再次测试:
console如下: CustomerInitializingBean afterPropertiesSet spring false
BeanPostProcessorSpring提供的BeanPostProcessor接口中提供两个方法:
- postProcessBeforeInitialization 初始化操作
- postProcessAfterInitialization 初始化后操作
在spring中,BeanPostProcessor的作用是非常重要的,比如我们常见的AOP,setbeanName()、setBeanClassLoader()、setBeanFactory()等Aware处理也就是在这里进行处理的,我们简单来用该功能来模拟一下aop代理对象创建。看例子:
@CustomerComponent
public interface CustomerBeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
@CustomerComponent
public class HyBeanPostProcessImpl implements CustomerBeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if("userService".equals(beanName)){
final Object newProxyInstance = Proxy.newProxyInstance(HyBeanPostProcessImpl.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//执行切面逻辑
return method.invoke(bean,objects);
}
});
return newProxyInstance;
}
return bean;
}
}
- createBean方法:增幅方法执行
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
Object instance = null;
//这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
try {
instance = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(CustomerAutowired.class)) {
field.setAccessible(true);
/**
* 这里我直接从单例池中获取值,这样是存在问题,有可能在做这一步操作的时候,单例池
* 中并没有还加载此bean
*/
field.set(instance, getBean(field.getName()));
}
}
if (instance instanceof CustomerInitializingBean) {
((CustomerInitializingBean) instance).afterPropertiesSet();
}
for (CustomerBeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(instance,beanName);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
- scanner方法:增加对process的判断
private void scanner(Class configClass) {
if (configClass.isAnnotationPresent(CustomerComponentScan.class)) {
//如果存在CustomerComponentScan注解,则获取注解信息得到value
CustomerComponentScan customerComponentScan = (CustomerComponentScan) configClass.getAnnotation(CustomerComponentScan.class);
//得到需要扫描的包路径
String path = customerComponentScan.value();
//将com.xxx.xx替换成 com/xxx/xx文件格式
path = path.replace(".", "/");
//获取CustomerAnnotationConfigApplicationContext类资源
ClassLoader classLoader = CustomerAnnotationConfigApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
//因为我本地目录存在中文,因此这里进行了解码。
File file = new File(URLDecoder.decode(resource.getFile()));
if (file.isDirectory()) {
//循环目录下文件
for (File f : file.listFiles()) {
//获取绝对路径
String absolutePath = f.getAbsolutePath();
//将文件路径转换为 com.xxx.xx
absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");
try {
//加载文件资源
final Class clazz = classLoader.loadClass(absolutePath);
//判断类上是否存在CustomerComponent注解
if (clazz.isAnnotationPresent(CustomerComponent.class)) {
if (CustomerBeanPostProcessor.class.isAssignableFrom(clazz)) {
beanPostProcessorList.add((CustomerBeanPostProcessor) clazz.getConstructor().newInstance());
}
//如果存在,则表示为一个bean,则封装BeanDefinition,这是重点。
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//该类上是 单例模型 还是 原型模式
if (clazz.isAnnotationPresent(CustomerScope.class)) {
String value = clazz.getAnnotation(CustomerScope.class).value();
beanDefinition.setScope(value);
} else {
beanDefinition.setScope("singleton");
}
//将封装的beanDefinition放入map
//获取map的key
String beanName = clazz.getAnnotation(CustomerComponent.class).value();
if ("".equals(beanName)) {
//首字母小写处理
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
本篇文章简单模拟一下spring加载bean的流程,来大致深入了解一下上一篇文章所提到的知识点,还希望各位有问题请指正。