Sprig源码学习
本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。
目标
- 了解Spring底层源码启动过程
- 了解BeanDefinition、BeanPostProcessor的概念
- 了解Spring解析配置类等底层源码工作流程
- 了解依赖注入,Aware回调等底层源码工作流程
- 了解Spring AOP的底层源码工作流程
1. 基础工作
1.1 bean是在什么时候创建的
在手写之前我们先搞清楚bean是在什么时候创建的呢?
bean分为单例bean和原型bean(多例bean),单例bean又分为懒加载和非懒加载。懒加载的意思是你要用的时候才去创建这个bean,非懒加载的单例bean是在Spring启动的过程中间把非懒加载的单例bean给创建出来。
1.2 准备大致框架
首先是先定义一个bean,UserService
package com.ding.service;
public class UserService {
}
再模拟一个XXXApplicationContext类,然后写一个测试类,在写测试类之前需要一个AppConfig这个类,用来定义一个配置类的构造方法,还有一个是获取bean的名字一个方法。
XXXApplicationContext
package com.spring;
public class DingJunXiaApplicationContext {
private Class configClass;
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
}
public Object getBean(String beanName){
return null;
}
}
做一个配置类
package com.ding;
public class AppConfig {
}
测试类
package com.ding;
import com.ding.service.UserService;
import com.spring.DingJunXiaApplicationContext;
public class Test {
public static void main(String[] args) {
DingJunXiaApplicationContext applicationContext = new DingJunXiaApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();//写这个之前在UserService中添加test方法即可
}
}
到此一个简单的运行案例就写出来了
2. 实现@ComponentScan注解
package com.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
这样我们就可以使用我们自己的注解了。我们就扫描service这个包
package com.ding;
import com.spring.ComponentScan;
@ComponentScan("com.ding.service")
public class AppConfig {
}
3. 实现@Component注解
其实我们在写了扫描范围之后,我们需要在UserService上加一个@Service或者是@Component,所以我们要实现@Component注解,作用就是用于以后取bean的名字
package com.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
package com.ding.service;
import com.spring.Component;
@Component("userService")
public class UserService {
public void test() {
System.out.println("测试一下");
}
}
4. spring底层原理具体简单模拟实现
4.1 @ComponentScan+@Component+@Scope的功能实现
在创建bean对象的时候,我们肯定要先扫描,(因为懒加载单例bean、非懒加载单例bean、多例bean创建bean的时间不同,所以我们是需要知道创建懒加载单例bean,还是非懒加载单例bean,还是多例bean呢?那么我们怎么才能知道呢,我们就需要进行扫描。我们要看service包下面都是有什么类,这个类是属于哪一种加载类还是只是普通类)。
因此扫描这份工作就需要在第9行执行之前进行扫描,也就是new 的这个DingJunXiaApplicationContext这个方法中,这个方法在DingJunXiaApplicationContext这个类中,所以从这个地方入手。
扫描第一步先拿到路径(在这之前可以先判断是不是有这个ComponentScan注解,如果有我们获取这个注解里面的内容,拿到内容之后我们在那到扫描路径)
package com.spring;
import java.lang.annotation.Annotation;
public class DingJunXiaApplicationContext {
private Class configClass;
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
System.out.println(path);
}
}
public Object getBean(String beanName){
return null;
}
}
其实这个时候我们可以把test文件运行一下,我们知道肯定会报错呢,因为我们在getBean那个方法里面传的是null,应该会报一个空指针。但是路径会输出
这个时候我们拿到的扫描路径是com.ding.service这样的,但是我们只是找到了文件的路径,我们还要看这个路径下面有几个文件啊,文件里面有没有Component,如果有就是一个bean,如果没有就不是一个bean。那么我们该怎么做呢?
我们要取编译后的com.ding.service目录下面的文件,因为我们自己写的是源码文件。我们如何去理解这个target下面的classes下面的文件呢?
可以看一下java文件运行时候idea的启动参数信息(就是下面图片用线画出来的地方):
为了能够能清晰的看到,我把这部分粘贴出来了,如下:
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:
D:\soft\idea2020\IntelliJ IDEA 2020.1\lib\idea_rt.jar=8349:
D:\soft\idea2020\IntelliJ IDEA 2020.1\bin"
-Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;
D:\postgraduate\Research\employment\code\spring\spring2.2_2\target\classes;
D:\soft\apache-maven-3.6.1\repository\net\bytebuddy\byte-buddy\1.12.22\byte-buddy-1.12.22.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-context\6.0.2\spring-context-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-aop\6.0.2\spring-aop-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-beans\6.0.2\spring-beans-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-core\6.0.2\spring-core-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-jcl\6.0.2\spring-jcl-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-expression\6.0.2\spring-expression-6.0.2.jar" com.ding.Test
其中,在这个里面可以看到划线部分加载的是target/classes(其实这个是与类加载器是由关系的,总共有三个类加载器,
Boot管理的是jre/lib
Ext管理的是jre/ext/lib
App管理的是那个classpath所指定的,从做指定的jar包和target/classes下面的):
因此下面我们要做的就是把上面的那个路径通过类加载器去target/classes那个目录下去找com.ding.service这个目录。需要注意的是刚才我们打印出来的路径是com.ding.service,要想真正走到这个路径下,需要使用com/ding/service,那么我们就使用替换的方法把原来地址的“.”换成"/"即可。
然后使用ClassLoader先找到路径,在该路径下遍历文件
package com.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
public class DingJunXiaApplicationContext {
private Class configClass;
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
URL url = classLoader.getResource(path);
//System.out.println(url);
File file = new File(url.getFile());
//如果是一个目录,遍历文件
if (file.isDirectory()){
for (File f : file.listFiles()){
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
}
}
}
}
public Object getBean(String beanName){
return null;
}
}
结果:
接下来就是要判断我们所找到的这些class文件到底有没有Component,但是现在我们只是找到了文件而已,其实这样也可以看是不是有Component注解,就是利用一项技术(这个我没有去了解)。最简单的方法就是把这个类加载出来,都加载成class对象,然后就可以直接判断这个类上有没有Component注解。
但是由于类加载 Class<?> clazz = classLoader.loadClass(“com.ding.service.UserService”);括号里面加载的是“com.ding.service.UserService”,而我们之前上述结果是一个很长的路径,因此加载之前我们要进去截取。
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
截取之后,然后因为是windows要把“\”替换成".",最后传进去
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
Class<?> clazz = classLoader.loadClass(absolutePath);
然后再进行判断是不是有Component注解。
总结一下代码
package com.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
public class DingJunXiaApplicationContext {
private Class configClass;
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
URL url = classLoader.getResource(path);
//System.out.println(url);
File file = new File(url.getFile());
//如果是一个目录,遍历文件
if (file.isDirectory()){
for (File f : file.listFiles()){
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
try {
//把这个类加载出来,都加载成class对象
Class<?> clazz = classLoader.loadClass(absolutePath);
//但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
// Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
//判断是否存在Component注解
if (clazz.isAnnotationPresent(Component.class)){
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName){
return null;
}
}
如果有Component注解,那么就代表这是一个bean,那么知道了之后我们就还要知道这是一个单例bean还是一个原型bean,如果是一个原型的我们不需要创建,所以我们现在判断是不是单例bean。
因此我们需要先看上面有没有Scope注解,要是没有,那就是单例的,如果有,我们要看里面的内容,根据里面的内容判断是单例还是原型,singleton就是单例,prototype就是多例。
package com.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
public class DingJunXiaApplicationContext {
private Class configClass;
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
URL url = classLoader.getResource(path);
//System.out.println(url);
File file = new File(url.getFile());
//如果是一个目录,遍历文件
if (file.isDirectory()){
for (File f : file.listFiles()){
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
System.out.println(absolutePath);
try {
//把这个类加载出来,都加载成class对象
Class<?> clazz = classLoader.loadClass(absolutePath);
//但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
// Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
//判断是否存在Component注解
if (clazz.isAnnotationPresent(Component.class)){
if (clazz.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
//这个value也可能是单例也可能是原型
}else {
//单例
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName){
return null;
}
}
因为上面判断了,我们后期会用到去测试哈,我们先去写一个Scope注解
package com.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "";
}
然后我们在UserService里面也添加这个注解
package com.ding.service;
import com.spring.Component;
import com.spring.Scope;
@Component("userService")
@Scope("prototype")
public class UserService {
public void test() {
System.out.println("测试一下");
}
}
假设我们现在已经判断出来了是单例了,那么接下来我们就去创建bean吗(我们先不关心懒加载哈),其实思路是没有问题的,但是有一个非常非常关键的一个点,回到getbean
getBean的用法就是你传进去一个bean的名字,最后我们要去返回一个对象,----->就是DingJunXiaApplicationContext这个类里面的这块。
public Object getBean(String beanName){
return null;
}
传一个字符串,我怎么返回一个对象呢?我们的思路是这样的。
我们根据传的字符串,去找到这个类,不然就找不到这个类怎么返回一个对象呢?
//beanName—>UserService.class---->又得解析这个类,看这个类有没有scope,是不是单例,其实我们在扫描的时候这些我们已经做了啊,结果getBean的时候又做了一遍,这个我们就引入一个非常重要的概念,bean的定义---->BeanDefinition,在这里面有bean的类型是什么(UserService吗),bean的作用域是什么(单例还是原型),bean是不是懒加载的,这些都是bean的定义,在这里我们可以定义bean的所有的属性。
package com.spring;
public class BeanDefinition {
private Class type;
private String scope;
private boolean isLazy;
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;
}
public boolean isLazy() {
return isLazy;
}
public void setLazy(boolean lazy) {
isLazy = lazy;
}
}
所以我们把刚才判断的那个代码改一下去。如果扫描过程中发现有Component,说明这是一个bean,如果要是一个bean,我们就要去创建bean的定义,设置bean的scope以及bean的Type。
除此之外还有就是在内部还有一个map,在spring源码中也很重要,存的是BeanDefinition,我们每扫描完一个bean就有一个BeanDefinition,然后存在map里面,map中的key是bean的名字,value是BeanDefinition。为什么要有这个Map后面又讲。
为了方便,我们单独把这个方法抽象出来,作为一个扫描方法,全部代码如下:
package com.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class DingJunXiaApplicationContext {
private Class configClass;
private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
scan(configClass);
}
public Object getBean(String beanName){
return null;
}
private void scan(Class configClass) {
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
URL url = classLoader.getResource(path);
//System.out.println(url);
File file = new File(url.getFile());
//如果是一个目录,遍历文件
if (file.isDirectory()){
for (File f : file.listFiles()){
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
System.out.println(absolutePath);
try {
//把这个类加载出来,都加载成class对象
Class<?> clazz = classLoader.loadClass(absolutePath);
//但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
// Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
//判断是否存在Component注解
if (clazz.isAnnotationPresent(Component.class)){
Component componentAnnotation = clazz.getAnnotation(Component.class);
String beanName = componentAnnotation.value();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
if (clazz.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
//这个value也可能是单例也可能是原型,我们拿到什么我们就设置什么
beanDefinition.setScope(value);
}else {
//单例
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
}
写到现在了,那我们总结一下这个扫描做了什么事情。解析得到扫描路径,遍历路径下的每一class文件,加载每一个class文件得到一个class对象,然后判断有没有Component注解,然后去解析bean的名字
,scope注解,得到BeanDefinition,然后存在Map中。
那么这个beanDefinitionMap到底有什么用呢?如果Map里面没有这个名字,代表我们压根就没有定义过这个bean。这个Map代表当前整个项目里面到底定义了哪些bean。
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")){
}else {
//原型
}
return null;
}
做到这该做的都做完了,也扫描完了,那就该创建bean了,那我们开始
就是扫描----->遍历寻找单例bean----->创建bean
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
scan(configClass);
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);
}
}
}
//创建bean
private Object createBean(String beanName,BeanDefinition beanDefinition){
}
在我们把单例bean创建出来,还要进行保存,引入单例池。把创建好的单例bean存进去,存进去有什么好处?回头去getBean的时候不用创建了,直接去单例池去拿就行了。
现在来完成创建bean,要想创建bean,还要根据类去创建,那么就要先找到他的类在哪里?找到类后,这里为了简单,我们直接使用空参的构造方法
//创建bean
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;
}
写到这,我们就可以测试一下啦。
单例:
多例:
其实我们在写的过程中一直遗漏了一个问题,就是在我写@Component注解的时候,我们在service那块没写名字,会如何呢?肯定会报错啊,报错图:
因为之前我们在使用别人写好的spring的时候,如果@Component后面没有传值,他会有一个默认值,就是类的名字首字母小写,因此我们也可以加进去并进行判断。
所以我们也去设置默认值:
然后我们再来测试一下:
4.2 依赖注入
在UserService里面加入OrderService,然后再这个属性上加上注解,加注解前先自己写一个@Autowired注解:
package com.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
package com.ding.service;
import com.spring.Autowired;
import com.spring.Component;
import com.spring.Scope;
@Component("userService")
@Scope("singleton")
public class UserService {
@Autowired
private OrderService orderService;
public void test() {
System.out.println(orderService);
}
}
这个时候有值吗?属性赋值成功了吗?测试一下:
显然是空值,没有值
依赖注入是在创建bean的过程会去依赖注入。因此我们在createBean这个方法中进行依赖注入。
因此我们需要遍历属性,看哪个属性上面有@Autowired,如果有就去给这个字段去赋值。赋值之前记得反射。
//创建bean
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(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;
}
那赋是赋什么值呢,需要根据当前字段的名字找。循环依赖,这个不在这次的分享范围内。
流程上是先扫描了,假设创建userService这个bean,进行到了createBean,而内部注入的orderService还没有生成,我们会拿到一个空;所以我们可以加一个判断,如果为空,就创建bean。
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")){
Object singletonBean = singletonObjects.get(beanName);
if (singletonBean == null){
singletonBean = createBean(beanName,beanDefinition);
singletonObjects.put(beanName,singletonBean);
}
return singletonBean;
}else {
//原型
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
结果:
DingJunXiaApplicationContext.java总代码
package com.spring;
import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class DingJunXiaApplicationContext {
private Class configClass;
private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
private Map<String,Object> singletonObjects = new HashMap<>();
public DingJunXiaApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描
scan(configClass);
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);
singletonObjects.put(beanName,bean);
}
}
}
//创建bean
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(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;
}
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")){
Object singletonBean = singletonObjects.get(beanName);
if (singletonBean == null){
singletonBean = createBean(beanName,beanDefinition);
singletonObjects.put(beanName,singletonBean);
}
return singletonBean;
}else {
//原型
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
private void scan(Class configClass) {
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
URL url = classLoader.getResource(path);
//System.out.println(url);
File file = new File(url.getFile());
//如果是一个目录,遍历文件
if (file.isDirectory()){
for (File f : file.listFiles()){
String absolutePath = f.getAbsolutePath();
//System.out.println(absolutePath);
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
//System.out.println(absolutePath);
try {
//把这个类加载出来,都加载成class对象
Class<?> clazz = classLoader.loadClass(absolutePath);
//但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
// Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
//判断是否存在Component注解
if (clazz.isAnnotationPresent(Component.class)){
Component componentAnnotation = clazz.getAnnotation(Component.class);
String beanName = componentAnnotation.value();
if ("".equals(beanName)){
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
if (clazz.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
//这个value也可能是单例也可能是原型,我们拿到什么我们就设置什么
beanDefinition.setScope(value);
}else {
//单例
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
}
4.3 模拟初始化InitializingBean +BeanPostProcessor
spring bean的创建流程,依赖注入完,还涉及到了初始化的东西,也来实现一下。
4.3.1 接口实现InitializingBean
首先创建一个接口
package com.spring;
public interface InitializingBean {
void afterPropertiesSet();
}
再去实例化一个接口
package com.ding.service;
import com.spring.Autowired;
import com.spring.Component;
import com.spring.InitializingBean;
import com.spring.Scope;
@Component("userService")
@Scope("prototype")
public class UserService implements InitializingBean {
@Autowired
private OrderService orderService;
public void test() {
System.out.println(orderService);
}
@Override
public void afterPropertiesSet() {
System.out.println("初始化");
}
}
但是目前这个方法能自动被调到妈?我们还调用不到,因为底层还没有实现,实现还是在创建bean的过程中进行实现,依赖注入完之后spring就支持这个功能,就可以进行实现。
判断当前bean的实例instanceof有没有实现InitializingBean 这个接口,如果实现了把当前这个实例对象强制转化成为这个接口的类型
运行:
4.3.2 BeanPostProcessor
下面就是spring中特别重要的一个东西BeanPostProcessor ,这个接口有两个方法 postProcessBeforeInitialization 和 postProcessAfterInitialization,也就是初始化前与初始化后,这个接口的方法,是针对于所有的bean的,也就是说,如果实现了这个接口,所有的bean都会调用重写的这两个方法
定义接口
package com.spring;
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean,String beanName){
return bean;
}
default Object postProcessAfterInitialization(Object bean,String beanName){
return bean;
}
}
实现接口
package com.ding.service;
import com.spring.BeanPostProcessor;
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
}
}
这个方法里面我们就可以写一些我们想实现的功能,按照逻辑我们这个方法应该在初始化后调用该方法,那怎么调呢?按照我们现在这个代码
这样逻辑是没有错的,但是这样写合适吗?Spring会这样直接new出来吗?Spring会怎么做呢?是的,用接口。Spring要想调用这个方法,首先就是要拿到这个类的一个实例,Spring得知道你写了这个类,那就是需要加一个Component,扫描发现有一个Component。
package com.ding.service;
import com.spring.BeanPostProcessor;
import com.spring.Component;
@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println(beanName);
//return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
return bean;
}
}
那现在就要去看扫描代码,扫描的时候发现有一个@Component注解,这个时候就会去判断这个类是不是实现的BeanPostprocessor这个接口,如果是就构造实例化出来,因为是在创建得时候用所以要进行缓存,
在创建bean里面遍历去调函数
结果展示:
如果我们想针对某一个方法,可以直接使用if语句
因为这个方法是有返回值的,我们还需要改一下,到时候直接返回instance
Aop就是基于BeanPostProcessor
那么我们现在就针对userService去实现一下,
使用jdk的代理对象,我们还要先去写一个接口
package com.ding.service;
public interface UserInterface {
public void test();
}
接口实现
package com.ding.service;
import com.spring.Autowired;
import com.spring.Component;
import com.spring.InitializingBean;
import com.spring.Scope;
@Component("userService")
@Scope("prototype")
public class UserService implements UserInterface {
@Autowired
private OrderService orderService;
public void test() {
System.out.println(orderService);
}
}
代理
package com.ding.service;
import com.spring.BeanPostProcessor;
import com.spring.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (beanName.equals("userService")){
Object proxyInstance = Proxy.newProxyInstance(DingJunXiaBeanPostProcessor.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 proxyInstance;
//System.out.println(beanName);
}
//return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
return bean;
}
}
测试
package com.ding;
import com.ding.service.UserInterface;
import com.ding.service.UserService;
import com.spring.DingJunXiaApplicationContext;
public class Test {
public static void main(String[] args) {
DingJunXiaApplicationContext applicationContext = new DingJunXiaApplicationContext(AppConfig.class);
UserInterface userService = (UserInterface) applicationContext.getBean("userService");
userService.test();
}
}
结果
5. 拓展:
我希望在我自己的项目中定义一个注解,叫DingJunXiaValue
package com.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DingJunXiaValue {
String value() default "";
}
在UserService上进行使用,实现的功能是:@DIngJunXiaValue里面传的值是多少就赋值给这个属性
package com.ding.service;
import com.spring.*;
@Component("userService")
@Scope("prototype")
public class UserService implements UserInterface {
@Autowired
private OrderService orderService;
@DingJunXiaValue("XXX")
private String test;
public void test() {
System.out.println(test);
}
/*@Override
public void afterPropertiesSet() {
System.out.println("初始化");
}*/
}
添加在DingJunXiaApplicationContext
在DingJunXiaValueBeanPostProcessor.java实现before
package com.ding.service;
import com.spring.BeanPostProcessor;
import com.spring.Component;
import com.spring.DingJunXiaValue;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (beanName.equals("userService")){
Object proxyInstance = Proxy.newProxyInstance(DingJunXiaBeanPostProcessor.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 proxyInstance;
//System.out.println(beanName);
}
//return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws IllegalAccessException {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(DingJunXiaValue.class)){
field.setAccessible(true);
try {
field.set(bean,field.getAnnotation(DingJunXiaValue.class).value());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
结果
再来模拟一下BeanNameAware接口(如果一个bean想知道自己的名字的话就用这个)
package com.ding.service;
import com.spring.*;
@Component
public class UserService implements UserInterface,BeanNameAware {
@Autowired
private OrderService orderService;
@DingJunXiaValue("test01010101")
private String test;
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
public void test() {
System.out.println(beanName);
System.out.println(test);
//System.out.println("pppp");
}
}
结果