本文是纪录哔哩哔哩图灵-周瑜老师的手写Spring源码课程,仅供纪录
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─springCode
│ │ │ ├─service
│ │ │ └─spring
│ │ └─resources
│ └─test
│ └─java
└─target
├─classes
│ └─com
│ └─springCode
│ ├─service
│ └─spring
└─generated-sources
└─annotations
对比一下三种创建Spring容器的方式,
AnnotationConfigApplicationContext
是需要传入一个xml文件,告诉Spring需要按照指定的配置创建一个Spring容器classPathXmlApplicationContext
,创建完之后就可以通过getBean
从容器中拿到相应的bean对象SpringCodeApplicationContext
也是一个容器,只是传入的配置文件是以Java类的形式传入。可以在类上加上@ComponentScan
定义Spring等下需要扫描的路径,还可以通过@Bean
的方法(等于bean标签),通过这种注解的方式,给容器加入一个BeanSpringCodeApplicationContext
则是手写模拟SpringCodeApplicationContext
进行创建Ioc容器并注入Beanpublic class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
classPathXmlApplicationContext.getBean("user");
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
SpringCodeApplicationContext applicationContext = new SpringCodeApplicationContext(AppConfig.class);
}
}
参考Spring框架可知,这个容器类需要有构造方法、传入参数、
getBean()
方法
// com/springCode/spring/SpringCodeApplicationContext.java
public class SpringCodeApplicationContext {
private Class configClass;
public SpringCodeApplicationContext(Class configClass) {
this.configClass = configClass;
// 解析配置类
// ComponentScan注解-->扫描路径-->扫描
}
public Object getBean(String beanName){
return null;
}
// com/springCode/service/Test.java
public class Test {
public static void main(Stringp[] args) {
// Spring容器
SpringCodeApplicationContext applicationContext = new SpringCodeApplicationContext(AppConfig.class);
}
}
// com/springCode/spring/ComponentScan.java
// 注解生效的时间
@Retention(RetentionPolicy.RUNTIME)
// 表示只能在类上
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
// com/springCode/service/UserService.java
@ComponentScan("com.springCode.service")
public class AppConfig {
}
// com/springCode/spring/Component.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
SpringCodeApplicationContext
需要在其有参构造方法中实现扫描注解并注册Bean反射回顾:
// 拿到注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
// 拿到注解里面的value(扫描路径)
String path = componentScanAnnotation.value();
Spring在拿到扫描路径后,关心的是该路径下的类有没有@Component
这个注解
那扫描获取到路径后,下一步应该做什么?(借助类加载器)
因为Java需要编译成.class文件,得到class对象才能利用Java提供api去调用查看这个类上是否有对应的注解
扫描路径
Bootstrap-->jre/lib
Ext-->jre/ext/lib
App-->classpath-->
classpath
?
classpath
实际上是当前该应用对应的target路径D:\JAVA_Environment\jdk-8\bin\java.exe "-javaagent:D:\Idea\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=62539:D:\Idea\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\AJavaSpring\springCode\target\classes com.springCode.service.Test
因此,如果要获取对应文件,实际上是获取.class文件,其中需要两个重要信息。classpath
和类信息,将两个信息拼接就能得到编译好的.class文件
尝试获取,注意这里的获取的路径是相对路径,实际上是
classpath
的相对路径
ClassLoader classLoader = SpringCodeApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource("com/springCode/service")
我们都知道,这个URL实际上是一个目录。我们可以先判断它是否是一个目录,如果是一个目录的话就遍历这个目录并输出(名字)。
// File既可以表示为一个文件也可以表示为一个目录
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f);
}
}
所以我们已经有了类的具体路径,那么我们可以利用类加载器去加载类,获取.class对象,有了这个.class对象就能去判断当前类是否有指定的
@Component
注解
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f);
Class<?> aClass = classLoader.loadClass("..");
if(aClass.isAnnotationPresent(Component.class)){
...
}
}
}
这里的
Class> aClass = classLoader.loadClass("..");
写的是加载的类的路径,但是这个路径是否能直接拼接?实际上是不行的,因为需要的是com.springCode.service.UserService
(字符串),而不是绝对路径D:\XXX\target\classes\com\springCode\service\UserService.class
。
所以我们需要对这个字符串进行转换
即D:\XXX\target\classes\com\springCode\service\UserService.class
—>com.springCode.service.UserService
for (File f : files) {
String fileName = f.getAbsolutePath();
// fileName是D:\XXXX\XXX\XXX.class,因此要截取
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
// xxx\\xxx ---> xxx.xxx
className = className.replace("\\", ".");
}
当前存在一个问题是,将包下的所有文件都扫描出来并转换,实际上我们只需要.class的文件,因此要加一个判断。并抛出异常,调整一下代码的位置。
for (File f : files) {
// 获取文件的绝对路径
String fileName = f.getAbsolutePath();
if (fileName.endsWith(".class")) {
// fileName是D:\XXXX\XXX\XXX.class,因此要截取
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
// xxx\\xxx ---> xxx.xxx
className = className.replace("\\", ".");
try {
// 利用类加载器获取对应的类(反射)
Class<?> clazz = classLoader.loadClass(className);
// 再通过得到的类判断有没有对应的注解
if (clazz.isAnnotationPresent(Component.class)) {
// 表示当前这个类是一个bean
...
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
类上有一个
@Component
的注解,那么就代表这个类应该被Ioc容器所管理。那么是否有这个注解就需要生成bean对象呢?
实际上不一定,是否创建bean对象与bean的作用域相关。
@Scope
注解创建@Scope
注解用于标识是一个单例bean还是一个原型bean,针对不同作用域的bean执行不一样的初始化步骤。
其中
src/main/java/com/springCode/spring/Scope.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "";
}
模仿Spring创建一个单例池
public class SpringCodeApplicationContext {
private Class configClass;
// 用于存单例bean
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
...
}
那么什么时候才向单例池里注入对象呢?回到刚刚扫描到
@Component
的位置
我们需要去判断这个带@Component
的是单例bean还是prototype的bean
这里不进行考虑懒加载
如果扫描到是prototype的bean,要做什么呢?
BeanDefinition
目前遇到一个问题是,怎么根据一个名字去找到对应的类。同时如果是一个prototype的bean,由于单例池里面是没有这个bean的,那么是否需要每次获取时都要去查看一下这个bean的定义(即去查看是单例bean还是多例bean)
因此需要引入BeanDefinition
,标识bean的定义
解析类—>BeanDefinition
public class BeanDefinition {
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;
}
private Class type;
private String scope;
}
现在扫描到类上有一个
@Component
这个注解。我们先拿到这个注解并解析获取里面的属性。然后再查看这个类上是否有@Scope
这个注解,有的话查看这个注解是标识这个类是单例还是原型。并将相关信息报错在BeanDefinition
中,若没有则默认为单例。
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
// 解析获取里面的属性
String beanName = component.value();
BeanDefinition beanDefinition = new BeanDefinition();
if(clazz.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
beanDefinition.setScope("singleton");
}
}
BeanDefinitionMap
但实际上,更好的做法是设置一个
Map
用来存这个BeanDefinition
// 用于纪录BeanPostProcessor
private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
再将这个
beanDefinition
放在Map中
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
// 看看有没有Scope注解
if (clazz.isAnnotationPresent(Scope.class)) {
Scope annotation = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(annotation.value());
} else {
// 没有Scope的话默认就是单例的
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName, beanDefinition);
此时对于
getBean
就可以从这个Map中获取。此时遇到一个新的问题,如果获取不了,那么怎么去创建新的对象。
public Object getBean(String beanName){
if(beanDefinitionMap.containsKey(beanName)){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if(beanDefinition.getScope().equals("singleton")){
Object o = singletonObjects.get(beanNames);
return o;
} else {
...
}
else {
throw new NullPointerException();
}
}
}
createBean
写一个创建bean的方法,根据获取到的
BeanDefinition
信息,调用无参构造的反射方法进行新建。
private Object createBean(BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
try {
// getConstructor()是调用无参构造
Object instance = clazz.getConstructor().newInstance();
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
有了这个
createBean
方法,就可以在getBean
获取不到时进行创建
public Object getBean(String beanName){
if(beanDefinitionMap.containsKey(beanName)){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if(beanDefinition.getScope().equals("singleton")){
Object o = singletonObjects.get(beanNames);
return o;
} else {
Object bean = createBean(beanDefinition);
return bean; }
else {
throw new NullPointerException();
}
}
}
回到上面的
ApplicationContext
中,可以总结一下的步骤
解析配置类
ComponentScan
注解–>扫描路径–>扫描类的信息–>装载到BeanDefinition
–>BeanDefinitionMap
此时所有的类都被扫描完毕,同时被装配到BeanDefinitionMap
中,下一步就是进行创建bean
// 实例化单例bean
// 扫描完,就需要把单例bean创建出来
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
// 单例就创建bean,但是要怎样保证单例呢?
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
}
目前,无参构造都已经做好了,但是有一个问题是,如果这个类里面需要注入其他的类,那该怎么做呢?
@Autowired
@Retention(RetentionPolicy.RUNTIME)
// 写在字段上
@Target(ElementType.FIELD)
public @interface Autowired {
}
@Autowired
@Component("userService")
public class UserService {
@Autowired
private OrderService orderService;
}
createBean
很明显,属性注入是在创建bean的时候进行处理的,因此回到
createBean
需要先把类上的所有属性都拿到,然后一个个进行判断查看是否需要属性的自动注入
我们需要在单例池中找到相应的bean进行注入
private Object createBean(String beanName, BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
try {
// getConstructor()是调用无参构造
Object instance = clazz.getConstructor().newInstance();
// 对加了@Autowired的属性赋值
// 依赖注入
for (Field f : clazz.getDeclaredFields()) {
if (f.isAnnotationPresent(Autowired.class)) {
// 调用反射赋值,需要先暴力破解
f.setAccessible(true);
f.set(instance, getBean(f.getName()));
}
}
}
此时有一个新的需求,我们需要Spring为我们传递beanName到我们的类中
@Component("userService")
public class UserService{
@Autowired
private OrderService orderService;
private String beanName;
}
查看Spring源码,可以看到有一个
BeanNameAware
的接口去实现这个需求,因此我们可以模仿Spring
BeanNameAware
public interface BeanNameAware {
/**
* setBeanName bean初始化后回调,即由Spring通知类(监听者模式)
*
* @param beanName
*
*/
public void setBeanName(String beanName);
}
BeanNameAware
@Component("userService")
public class UserService implements BeanNameAware {
@Autowired
private OrderService orderService;
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
很明显,这个功能的实现也是在createBean中
因此我们在实现完自动注入后,去判断当前的实例是否实现了BeanNameAware
接口,若实现了,就把实例强制转化为BeanNameAware
类,并实现回调功能。
由于这里没有beanName
,因此修改createBean
方法,传入beanName
private Object createBean(String beanName, BeanDefinition beanDefinition){
...
for (Field f : clazz.getDeclaredFields()) {
if (f.isAnnotationPresent(Autowired.class)) {
// 调用反射赋值,需要先暴力破解
f.setAccessible(true);
f.set(instance, getBean(f.getName()));
}
}
// 判断是否实现了BeanNameAware接口
// Aware回调
if (instance instanceof BeanNameAware) {
// 相当于间接通知你beanName
((BeanNameAware) instance).setBeanName(beanName);
}
...
}
Spring中提供了一个初始化接口
initializingBean
,因此我们仿照Spring
initializingBean
接口public interface InitializingBean {
/**
* afterPropertiesSet 初始化机制,直接初始化调用
*/
public void afterPropertiesSet();
}
initializingBean
接口Spring在创建UserService的过程中,会调用
afterPropertiesSet()
方法。可以验证当前对象的某些属性是否符合要求等功能。
@Component("userService")
public class UserService implements BeanNameAware, InitializingBean {
@Autowired
private OrderService orderService;
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public void afterPropertiesSet() {
System.out.println("hahah");
}
}
initializingBean
功能很明显,这个功能的实现也是在createBean中
因此我们在实现完自动注入后,去判断当前的实例是否实现了initializingBean
接口,若实现了,就把实例强制转化为initializingBean
类,并实现回调功能。
private Object createBean(String beanName, BeanDefinition beanDefinition){
...
for (Field f : clazz.getDeclaredFields()) {
if (f.isAnnotationPresent(Autowired.class)) {
// 调用反射赋值,需要先暴力破解
f.setAccessible(true);
f.set(instance, getBean(f.getName()));
}
}
// 判断是否实现了BeanNameAware接口
// Aware回调
if (instance instanceof BeanNameAware) {
// 相当于间接通知你beanName
((BeanNameAware) instance).setBeanName(beanName);
}
// 初始化即直接调用方法
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
...
}
BeanPostProcessor
Bean后置处理器需求:在初始化之前之后可以操作某些事情,注意
BeanNameAware
和initializingBean
都是初始化之后Object instance = clazz.getConstructor().newInstance();
做的事情。
我们需要在Object instance = clazz.getConstructor().newInstance();
前后做一些操作。
Spring提供了一个BeanPostProcessor
接口满足这个需求。
Spring底层用到大量的BeanPostProcessor
,我们模仿Spring创建一个BeanPostProcessor
接口
BeanPostProcessor
接口public interface BeanPostProcessor {
public void postProcessBeforeInitialization(String beanName, Object bean);
public void postProcessAfterInitialization(String beanName, Object bean);
}
BeanPostProcessor
接口可以针对所有的bean,这里针对某一个bean(UserService)进行处理
【注意】:这里一定要加入@Component
注解,让Spring找到这里的操作
@Component
public class SpringCodePostProcessor implements BeanPostProcessor {
@Override
public void postProcessBeforeInitialization(String beanName, Object bean) {
if (beanName.equals("userService")) {
System.out.println("before");
}
}
@Override
public void postProcessAfterInitialization(String beanName, Object bean) {
if (beanName.equals("userService")) {
System.out.println("after");
}
}
}
BeanPostProcessor
功能回到
SpringCodeApplicationContext
。
【注意】:这里不能用instanceof
,因为是针对对象去判断是否是某个类型的。因为此时还没创建bean实例
BeanPostProcessor.class.isAssignableFrom(clazz)
用于判断当前的类是否实现了BeanPostProcessor
接口
try {
// 利用类加载器获取对应的类(反射)
Class<?> clazz = classLoader.loadClass(className);
// 再通过得到的类判断有没有对应的注解
if (clazz.isAnnotationPresent(Component.class)) {
// 额外判断当前这个类是否实现BeanPostProcessor,有则提出来单独处理
// 这里不能用instanceof,因为是针对对象去判断是否是某个类型的
// 得到对象并加入List中
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
beanPostProcessorList.add(instance);
}
同时,针对不同的Bean实例可能会出现不同的
BeanPostProcessor
,因此会有很多的BeanPostPeocessor
,可以设置一个列表进行保存
private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
对
BeanPostProcessor
进行保存
try {
// 利用类加载器获取对应的类(反射)
Class<?> clazz = classLoader.loadClass(className);
// 再通过得到的类判断有没有对应的注解
if (clazz.isAnnotationPresent(Component.class)) {
// 额外判断当前这个类是否实现BeanPostProcessor,有则提出来单独处理
// 这里不能用instanceof,因为是针对对象去判断是否是某个类型的
// 得到对象并加入List中
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
beanPostProcessorList.add(instance);
}
...
}
}
扫描完之后会真正创建bean对象,因此在回调之后和创建之前进行调用
BeanPostProcessor
的postProcessBeforeInitialization
方法。初始化之后调用BeanPostProcessor
的postProcessAfterInitialization
方法。
// 判断是否实现了BeanNameAware接口
// Aware回调
if (instance instanceof BeanNameAware) {
// 相当于间接通知你beanName
((BeanNameAware) instance).setBeanName(beanName);
}
// 调用所有方法,创造bean前
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
}
// 初始化即直接调用方法
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
// 调用所有方法,创造bean后
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
beanPostProcessor.postProcessAfterInitialization(beanName, instance);
}
BeanPostProcessor
Aop产生的是代理对象,对Spring中的对象进行切面增强操作后。我们拿到的对象就是代理对象,即加强后的对象。我们在Ioc中创建的对象实际上是原始的Bean对象。
这里的instance
是原始的Bean对象
// 调用所有方法,创造bean后
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
beanPostProcessor.postProcessAfterInitialization(beanName, instance);
}
// BeanPostProcessor 初始化后 AOP
return instance;
我们可以在
postProcessBeforeInitialization
方法和postProcessBeforeInitialization
方法加点操作。,此时就要修改为返回Object对象
public interface BeanPostProcessor {
public Object postProcessBeforeInitialization(String beanName, Object bean);
public Object postProcessAfterInitialization(String beanName, Object bean);
}
@Component
public class SpringCodePostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(String beanName, Object bean) {
if (beanName.equals("userService")) {
System.out.println("before");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(String beanName, Object bean) {
if (beanName.equals("userService")) {
System.out.println("after");
}
return bean;
}
}
UserInterface
甚至可以自己创建一个新的对象,这里使用JDK的动态代理。新建一个接口满足JDK动态代理需要实现接口的要求
public interface UserInterface {
void test();
}
@Component("userService")
public class UserService implements BeanNameAware, InitializingBean, UserInterface {
@Autowired
private OrderService orderService;
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public void afterPropertiesSet() {
System.out.println("hahah");
}
@Override
public void test() {
System.out.println(orderService);
}
}
Proxy.newProxyInstance
有三个参数,第一个是获取类加载器,第二个是获取所实现的接口,第三个是生成代理对象
@Override
public Object postProcessAfterInitialization(String beanName, Object bean) {
if (beanName.equals("userService")) {
Object instance = Proxy.newProxyInstance(SpringCodePostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("切面逻辑");
return method.invoke(bean, args);
}
});
return instance;
}
return bean;
}
相应的这里
createBean
也要改。因此此时获得的是代理对象
// 调用所有方法,创造bean后
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
}
// BeanPostProcessor 初始化后 AOP
return instance;
测试,发现报错。这是是JDK动态代理的问题。
由于在容器中得到的对象是代理对象UserService bean = (UserService) applicationContext.getBean("userService");
,代理对象的类型是UserInterface
,而不是UserService
before
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to com.springCode.service.UserService
at com.springCode.service.Test.main(Test.java:14)
因此修改为
UserInterface
UserInterface bean = (UserInterface) applicationContext.getBean("userService");
结果正确
知识回顾:动态代理,就是要求被代理的类需要实现接口。原理就是生成这个接口的子类,这个子类代理实际的对象。
before
切面逻辑
com.springCode.service.OrderService@63947c6b