目录
spring扫描底层实现
spring容器启动时需要根据配置类提供的信息去扫描,因为扫描路径定义在了配置类,所以需要判断有没有配置类上有没有ComponentScan注解,如果有就先获取到这个注解对象,接着就需要根据注解对象去获取注解的属性值,也就是获得扫描路径。获取的扫描路径只是包名,另外,扫描的时候需要扫描的是编译好的.class文件。
public PnjgApplicationContext(Class configClass) {
// 扫描类 得到 BeanDefinition
scan(configClass);
如何获得准确的扫描文件路径呢?通过类加载器的getResource方法,获取相对路径对应的资源,就能获得存放.class文件的路径并获得文件夹,判断文件里的.class文件并筛选出来。
ClassLoader classLoader = PnjgApplicationContext.class.getClassLoader();
packagePath = packagePath.replace(".", "/");
URL resource = classLoader.getResource(packagePath);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
// System.out.println(fileName);
if (fileName.endsWith(".class")) {
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
className = className.replace("\\", ".");
// System.out.println(className);
try {
Class<?> clazz = classLoader.loadClass(className);
beanClasses.add(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
获取到所有的.class文件之后需要判断是不是bean,也就是判断有没有@Component注解,这里用反射,拿到对应类的Class对象,然后去判断这个类上面有没有提示这是个bean的注解。
BeanDefinition的生成
如果知道某个类并且这个类上有@Component注解,这时候不是直接把bean创建出来而是生成beanDefinition,这个beanDefinition类里面会有一些属性,比如Bean的类型、单例多例等。
package com.spring;
public class BeanDefinition {
private Class beanClass;
private ScopeEnum scope;
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
public ScopeEnum getScope() {
return scope;
}
public void setScope(ScopeEnum scope) {
this.scope = scope;
}
}
生成完beanDefinition类的对象中之后,把扫描得到的信息(此时可以知道bean的类型,而单例多例也可以在扫描的时候判断是否有@Scope注解,如果有取出里面的值)写入给beanDefinition类的对象。
if (clazz.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClass(clazz);
// 解析scope
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scope = (Scope) clazz.getAnnotation(Scope.class);
String scopeValue = scope.value();
if (ScopeEnum.singleton.name().equals(scopeValue)) {
beanDefinition.setScope(ScopeEnum.singleton);
} else {
beanDefinition.setScope(ScopeEnum.prototype);
}
} else {
beanDefinition.setScope(ScopeEnum.singleton);
}
此时已经生成好了一个beanDefinition类的对象,需要把它存起来,放在一个MAP里面,key值bean的名字,value是beanDefinition类的对象。bean的名字在判断是否有@Component注解后获取该注解的对象并取出里面的value值,即bean的名字
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
Component component = (Component) clazz.getAnnotation(Component.class);
String beanName = component.value();
beanDefinitionMap.put(beanName, beanDefinition);
getBean方法的底层实现
getBean()方法传入的参数是beanName,可以根据这个参数在beanDefinitionMap中获取对应的beanDefinition对象。
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
如果获取到的beanDefinition对象不为空,根据beanDefinition对象内的属性值去创建bean对象,比如scope的值是单例还是多例,如果是单例,那么可以在spring容器启动的时候就把单例bean创建出来,并放入单例池。
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
private void instanceSingletonBean() {
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals(ScopeEnum.singleton)) {
Object bean = doCreateBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
}
}
所以后续getbean是单例bean的时候先尝试从单例池中获取,如果没有就创建一个单例bean并放入单例池
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName);
} else {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
return doCreateBean(beanName, beanDefinition);
}
Bean创建流程
在创建bean的方法里面拿到了beanName, beanDefinition这两个参数,beanDefinition有当前要创建的bean的类型,并获得这个类,利用反射可以直接获得构造方法获取该类的对象
Class beanClass = beanDefinition.getBeanClass();
Object instance = declaredConstructor.newInstance();
依赖注入
在创建bean的那个步骤去做依赖注入,在上一步拿到对应Bean的对象之后给加了@Autowired的属性赋值。
第一步先拿到所有属性,然后判断当前属性是否有@Autowired注解,如果有就赋值,通过getBean()注入
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
String fieldName = field.getName();
Object bean = getBean(fieldName);
field.setAccessible(true);
field.set(instance, bean);
}
}
Aware回调
比如如果需要查看bean的名字,这时候就需要使用到Aware回调,spring提供了beanNameAware接口来实现beanName回调
public interface BeanNameAware {
public void setBeanName(String name);
}
需要用到aware回到的时候这个bean类就需要去实现这个beanNameAware接口,并实现其中的setBeanName方法,这个方法是由spring调用。
在spring创建bean方法的依赖注入完之后,就回去判断这个bean是否实现了BeanNameAware接口,如果是实现了这个接口,就去调用setBeanName方法,并把bean的名字传入
// Aware回调
if (instance instanceof BeanNameAware) {
((BeanNameAware)instance).setBeanName(beanName);
}
spring初始化
spring会提供一个InitializingBean接口,里面提供一个afterPropertiesSet方法。bean类实现这个接口并实现afterPropertiesSet方法,在该方法内可以写需要实现功能的代码
public class OrderService implements BeanNameAware, InitializingBean {
@Autowired
private UserService userService;
private String beanName;
@Override
public void afterPropertiesSet() {
System.out.println("初始化");
}
@Override
public void setBeanName(String name) {
System.out.println(name);
this.beanName = name;
}
初始化方法类似于aware回调方法,会被spring在创建bean判断是否实现该接口并判断是否调用。
// 初始化
if (instance instanceof InitializingBean) {
((InitializingBean)instance).afterPropertiesSet();
}
与aware实现机制相同,不同点在于aware的调用是spring会告诉你一些东西,而初始化是实现你想要的一部分功能
BeanPostProcessor
利用BeanPostProcessor机制可以处理bean,spring会提供BeanPostProcessor接口,接口里会有两个方法postProcessBeforeInitialization(),postProcessAfterInitialization()。程序员可以实现这个接口,可以在这两个方法里做事情,比如获取具体的bean去做处理。
在spring容器中会创建一个List来记录实现了BeanPostProcessor接口的类
private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
在spring容器中扫面阶段,判断当前的类是否实现了BeanPostProcessor接口类型,如果是就生成一个该类的对象,然后把该对象加入上面创建好的list里面。
在创建bean的时候,取出beanPostProcessorList里面的内容并且遍历,就可以在初始化前、初始化后调用对应的两个方法
for (BeanPostProcessor beanPostProcessor: beanPostProcessorList) {
beanPostProcessor.postProcessAfterInitialization(beanName, instance);
}
springAOP机制
如果是AOP机制,那么从spring容器中拿到的是动态代理之后代理对象。在postProcessAfterInitialization方法中判断beanName是否为需要动态代理的bean,如果是,创建一个新的代理对象,先执行切面逻辑,再执行bean本身的方法,最后返回新的代理对象,这样在spring容器调用postProcessAfterInitialization方法时就能拿到代理对象并返回。