这里写自定义目录标题
- 1、Spring使用方法
- 2、IOC控制反转实现步骤
- 1. 创建一个Spring容器ApplicationContext
- 2、解析底层实现逻辑
- 1、扫描逻辑
- 1. 首先判断传进来的类有没有添加`@ComponenetScan`注解,如果有则拿到扫描路径
- 2. 因此我们要获得classes目录下的class文件。使用AppClassLoader就可以很简单的实现:
- 3. 循环获取的所有class,生成class对象
- 4. 此时我们就可以根据包名创建bean
- 5. 当创建出了扫描路径下面的类时候,我们需要判断该类上有没有实现@Component接口,只有添加了@Component注解的类才是我们要找的类。
- 6. 找到了我们需要的需要创建的类后,还需要判断这个类是不是单例类,怎么判断该类是否是非懒加载单例类,使用@Scope注解来实现,在org.kenny.spring包下创建Scope注解。
- 7. 修改第五步的方法,判断是否是单例类
- 8. 修改方法ApplicationContext里面的方法
- 2、创建单例Bean
- 3、完善getBean()
- 3、 总结
- 3、DI依赖注入实现步骤
- 4、初始化
- 5、BeanPostProcessor (重要组件),初始化前,初始化后操作
- 6、BeanNameAware ---》Aware回调
- 7、 总结
1、Spring使用方法
public static void main(String[] args) {
// 创建一个Spring容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
}
-
解析
- 从上面代码可以看出,要手动实现Spring需要有以下几个步骤:
- 要有一个Spring容器
- 要有最基本getBean的方法
2、IOC控制反转实现步骤
新建两个package,org.kenny.spring (用于存放自定义spring的类);org.kenny.test(用于存放测试类)
1. 创建一个Spring容器ApplicationContext
- 通过
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
可以看出创建的容器需要就收一个类。因此自定义的容器需要有一个Class的属性和一个有参数的构造函数
public class KennyApplicationContext {
private Class configClass;
public KennyApplicationContext(Class configClass){
this.configClass = configClasss;
}
}
- 通过
UserService userService = (UserService) applicationContext.getBean("userService");
可以看出,该自定义容器还包含getBean(beanName)方法.
public Object getBean(String beanName){
return null;
}
那么我们如何才能通过一个beanName获取一个实际的Bean呢?
第一步中我们创建了容器 KennyApplicationContext ,我们要从容器中获取通过BeanName获取Bean的话,我们需要把bean加载到我们容器中来。那么我们可以通过扫描的方式将bean加载到容器中,即使用ComponenetScan注解来告诉容器,我要扫描那些bean到容器中,因此我们还需要创建一个@ComponenetScan注解
- 创建ComponenetScan注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义扫描注解
* @description
* @author KennySong
* @date 2023-3-19
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
创建了包的扫描路径,Spring并不会把包下所有的bean都加载到容器中,使用@CompontScan注解来扫描声明了@Controller、@Service、@Repository、@Component注解的类。此处以@Component为例,因此我们还需要创建一个@Component的注解
- 创建Component注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义Spring组件注解
* @description
* @author KennySong
* @date 2023-3-19
* @copyright 千景空间科技有限公司
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
- 在测试包下创建使用自定义Spring的代码
//创建AppConfig
import org.kenny.spring.ComponentScan;
@ComponentScan("org.kenny.test.service")
public class AppConfig {
}
//创建service
import org.kenny.spring.Component;
@Component("userService")
public class UserService {
public void test(){
System.out.println("test方法");
}
}
//创建程序入口
import org.kenny.spring.KennyApplicationContext;
import org.kenny.test.service.UserService;
public class Main {
public static void main(String[] args) {
KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
UserService userService = (UserService) kennyApplicationContext.getBean("userService");
System.out.println(kennyApplicationContext.getBean("userService"));
System.out.println(kennyApplicationContext.getBean("userService"));
System.out.println(kennyApplicationContext.getBean("userService"));
userService.test();
}
}
2、解析底层实现逻辑
首先我们在创建Spring容器的时候,容器需要做的一件事情就是创建非懒加载的单例bean,我们要创建单例bean首先得找到要创建那些类,我们可以通过@ComponenetScan
注解来扫描我们需要创建的单例bean,因此我们容器的构造函数需要进行扫描的实现
1、扫描逻辑
在第一步创建的KennyApplicationContext 构造方法基础上
public class KennyApplicationContext {
private Class configClass;
public KennyApplicationContext(Class configClass){
this.configClass = configClasss;
}
}
1. 首先判断传进来的类有没有添加@ComponenetScan
注解,如果有则拿到扫描路径
if (configClass.isAnnotationPresent(ComponentScan.class)){
//得到注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
//得到注解的值,此时得到的是org.kenny.test.service 包名
String path = componentScanAnnotation.value();
System.out.println(path);
}
然后我们继续分析,有了扫描路径后,我们拿到了很多类,但是不是所有的类都需要扫描到容器,只有实现了@Component注解的类我们才需要扫描。我们来分析下,我们扫描的路径实际是target/classes文件夹下面的路径下面的class文件。
我们都知道生成类的三种方法:
- BootstrapClassLoader:是顶级加载器,默认加载的%JAVA_HOME%中lib下的jar包和class类文件,即对应jre/lib
- ExtClassLoader:ExtClassLoader扩展类加载器,负责加载%JAVA_HOME%中lib/ext文件下的jar包和class类文件,ExtClassLoader加载器是AppClassLoader的父类,当然也不是继承(extends)关系,也是类中有parent变量,即对应jre/ext/lib
- AppClassLoader是自定义加载器的父类,负责加载classPath下的类文件,平时引用的jar包以及我们自己写的类都是这个加载器进行加载的,同时AppClassLoader还是线程上下文加载器.,如果想实现一个自定义加载器的话就继承(extends)ClassLoader来实现,即对应target/classes。
2. 因此我们要获得classes目录下的class文件。使用AppClassLoader就可以很简单的实现:
//由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
//因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
path = path.replace(".","/");
//然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
//通过getResource()方法获取具体的目录
URL resource = classLoader.getResource(path);
String allPath = resource.getFile();
//由于我的目录中带有空格,需要替换转义
allPath = allPath.replaceAll("%20", " ");
//通过路径得到文件或者目录
File file = new File(allPath);
3. 循环获取的所有class,生成class对象
接下来循环获取的所有class,生成class对象,但是上面只能得到绝对路径,比如 D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class,但是我们通过AppClassLoader创建对象需要的是完整的类路径,比如org.kenny.test.service.UserService,因此我们需要转换绝对路径
if (file.isDirectory()){
for (File f : file.listFiles()) {
//得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
//判断创建非懒加载的单例bean
//解析绝对路径成包名,org.kenny.test.service.UserService
String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
org = org.replace("\\", ".");
System.out.println(org);
}
}
打印结果为:org.kenny.test.service.UserService
4. 此时我们就可以根据包名创建bean
//通过类名创建类
Class<?> clazz = classLoader.loadClass(org);
5. 当创建出了扫描路径下面的类时候,我们需要判断该类上有没有实现@Component接口,只有添加了@Component注解的类才是我们要找的类。
if (clazz.isAnnotationPresent(Component.class)){
}
6. 找到了我们需要的需要创建的类后,还需要判断这个类是不是单例类,怎么判断该类是否是非懒加载单例类,使用@Scope注解来实现,在org.kenny.spring包下创建Scope注解。
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 "";
}
7. 修改第五步的方法,判断是否是单例类
//判断是否是Bean
if (clazz.isAnnotationPresent(Component.class)){
//判断是否是单例bean,即有没有是按Scope注解
if (clazz.isAnnotationPresent(Scope.class)){
//原型
}else {
//单例
}
}
至此,我们找到了单例类,接下来就是创建单例类了,我们接下来分析源码kennyApplicationContext.getBean("userService");
,因为我们调用getBean方法是需要返回对象的,那么我们在getBean方法中还需要判断对象是单例对象还是原型对象,因为传进来的是beanName,因此在构造方法中判断单例bean和原型bean的逻辑还得在执行一遍,实际上是没有必要的,那么我们怎么解决这个问题呢。
8. 修改方法ApplicationContext里面的方法
a、创建一个Map将得到的类放入map中
public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
b、创建Bean的定义类BeanDefinition
/**
* Bean的定义
* @description
* @author KennySong
* @date 2023-3-19
*/
public class BeanDefinition {
/**
* bean的类型
*/
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;
}
}
c、将找出来的bean生成BeanDefinition 在添加到map中
try {
//通过类名创建类
Class<?> clazz = classLoader.loadClass(org);
//判断是否是Bean
if (clazz.isAnnotationPresent(Component.class)){
Component componentAnnotation = clazz.getAnnotation(Component.class);
String beanName = componentAnnotation.value();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//判断是否是单例bean,即有没有是按Scope注解
if (clazz.isAnnotationPresent(Scope.class)){
//原型
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}else {
//单例
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
d、将上述代码汇总提取
/**
* 扫描
* @descrition
* 1、通过传进来的类得到扫描路径
* 2、通过扫描路径得到class文件
* 3、遍历class文件
* 4、通过class文件路径创建class对象
* 5、判断对象有没有Component注解
* 6、如果有注解,则获取component注解的值(bean的名字),并且创建BeanDefinition对象,设置对象类型type
* 7、判断是否是单例类,即判断有没有@Scope注解,如果有,则设置BeanDefinition对象的scope为注解值,如果没有则设置为singleton单例对象
* 8、将BeanDefinition对象 加载到beanDefinitionMap中
* @param configClass
*/
private void scan(Class configClass) {
if (configClass.isAnnotationPresent(ComponentScan.class)){
//得到注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
//得到注解的值,此时得到的是org.kenny.test.service 包名
String path = componentScanAnnotation.value();
//由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
//因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
path = path.replace(".","/");
//然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
//通过getResource()方法获取具体的目录
URL resource = classLoader.getResource(path);
String allPath = resource.getFile();
//由于我的目录中带有空格,需要替换转义
allPath = allPath.replaceAll("%20", " ");
//通过路径得到文件或者目录
File file = new File(allPath);
//判断得到的是否是目录
if (file.isDirectory()){
for (File f : file.listFiles()) {
//得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
//判断创建非懒加载的单例bean
//解析绝对路径成包名,org.kenny.test.service.UserService
String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
org = org.replace("\\", ".");
System.out.println(org);
try {
//通过类名创建类
Class<?> clazz = classLoader.loadClass(org);
//判断是否是Bean
if (clazz.isAnnotationPresent(Component.class)){
Component componentAnnotation = clazz.getAnnotation(Component.class);
String beanName = componentAnnotation.value();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
//判断是否是单例bean,即有没有是按Scope注解
if (clazz.isAnnotationPresent(Scope.class)){
//原型
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}else {
//单例
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
}
修改构造方法:
public KennyApplicationContext(Class configClass) {
this.configClass = configClass;
//判断传进来的类是否添加了@ComponentScan注解
scan(configClass);
}
2、创建单例Bean
上述代码我们基本实现了Spring扫描的逻辑,接下来要实现单例Bean的逻辑。
首先我们 public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
beanDefinitionMap 中已经存放了所有的Bean的定义,我们循环这个Map去创建bean,为了getBean()中使用,创建 public Map<String,Object> singletonObjects = new HashMap<>();
存放所有的单例类对象
//1、创建单例对象Map
public Map<String,Object> singletonObjects = new HashMap<>();
//2、创建createBean方法
private Object createBean(String beanName,BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
Object object = null;
try {
object = clazz.getConstructor().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return object;
}
//3、ApplicationContext构造方法中添加以下方法
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String key = entry.getKey();
BeanDefinition value = entry.getValue();
if ("singleton".equals(value.getScope())){
//创建单例对象
Object bean = createBean(key, value);
singletonObjects.put(key,bean);
}
}
3、完善getBean()
- 首先我们判断传进来的beanName有没有被扫描到容器中
- 如果没有则返回空指针异常,存在则从beanDefinitionMap中取出bean定义
- 通过beanDefinition判断是单例还是原型
- 如果是单例,则从单例Map中取出单例对象并返回
- 如果是原型类,则通过调用createBean创建一个新的对象并返回
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断单例还是原型
if ("singleton".equals(beanDefinition.getScope())){
Object singletonBean = singletonObjects.get(beanName);
return singletonBean;
}else {
//原型
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
3、 总结
至此,我们整个Spring扫描以及生成非懒加载的单例Bean就实现了,目录结构如下:
我们贴出完整的ApplicationContext代码
KennyApplicationContext.java
package org.kenny.spring;
import com.sun.deploy.util.StringUtils;
import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class KennyApplicationContext {
private Class configClass;
public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
public Map<String,Object> singletonObjects = new HashMap<>();
public KennyApplicationContext(Class configClass) {
this.configClass = configClass;
//判断传进来的类是否添加了@ComponentScan注解
scan(configClass);
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String key = entry.getKey();
BeanDefinition value = entry.getValue();
if ("singleton".equals(value.getScope())){
//创建单例对象
Object bean = createBean(key, value);
singletonObjects.put(key,bean);
}
}
}
private Object createBean(String beanName,BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
Object object = null;
try {
object = clazz.getConstructor().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return object;
}
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断单例还是原型
if ("singleton".equals(beanDefinition.getScope())){
Object singletonBean = singletonObjects.get(beanName);
return singletonBean;
}else {
//原型
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
/**
* 扫描
* @descrition
* 1、通过传进来的类得到扫描路径
* 2、通过扫描路径得到class文件
* 3、遍历class文件
* 4、通过class文件路径创建class对象
* 5、判断对象有没有Component注解
* 6、如果有注解,则获取component注解的值(bean的名字),并且创建BeanDefinition对象,设置对象类型type
* 7、判断是否是单例类,即判断有没有@Scope注解,如果有,则设置BeanDefinition对象的scope为注解值,如果没有则设置为singleton单例对象
* 8、将BeanDefinition对象 加载到beanDefinitionMap中
* @param configClass
*/
private void scan(Class configClass) {
if (configClass.isAnnotationPresent(ComponentScan.class)){
//得到注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
//得到注解的值,此时得到的是org.kenny.test.service 包名
String path = componentScanAnnotation.value();
//由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
//因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
path = path.replace(".","/");
//然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
//通过getResource()方法获取具体的目录
URL resource = classLoader.getResource(path);
String allPath = resource.getFile();
//由于我的目录中带有空格,需要替换转义
allPath = allPath.replaceAll("%20", " ");
//通过路径得到文件或者目录
File file = new File(allPath);
//判断得到的是否是目录
if (file.isDirectory()){
for (File f : file.listFiles()) {
//得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
String absolutePath = f.getAbsolutePath();
System.out.println(absolutePath);
//判断创建非懒加载的单例bean
//解析绝对路径成包名,org.kenny.test.service.UserService
String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
org = org.replace("\\", ".");
System.out.println(org);
try {
//通过类名创建类
Class<?> clazz = classLoader.loadClass(org);
//判断是否是Bean
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);
//判断是否是单例bean,即有没有是按Scope注解
if (clazz.isAnnotationPresent(Scope.class)){
//原型
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}else {
//单例
beanDefinition.setScope("singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
测试方法如下:
public static void main(String[] args) {
//扫描--->创建非懒加载的单例bean
KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
UserService userService = (UserService) kennyApplicationContext.getBean("userService");
System.out.println(kennyApplicationContext.getBean("userService"));
System.out.println(kennyApplicationContext.getBean("userService"));
System.out.println(kennyApplicationContext.getBean("userService"));
userService.test();
}
结果如下:
应为我们UserService类上@Scope为prototype,不是单例类,因此生成了三个不同的对象。如果我们将@Scope设置为singleton或者不添加@Scope注解,则就只会有一个实例。
3、DI依赖注入实现步骤
1、通过上面createBean
方法的基础上,我们通过class获取类上的所有属性clazz.getDeclaredFields()
;
2、循环属性判断该属性是否添加了@Autowired注解,如果实现了注解,就可以使用getBea
方法进行赋值;
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) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return instance;
}
通过我们上面所实现的代码可以发现,我们是循环创建单例bean的。那么我们在geBean方法调用的时候,可能bean还没有创建,因此需要修改下我们getBean的方法,在原有的从单例池获取beanObject singletonBean = singletonObjects.get(beanName);
的下面添加以下代码。
//判断单例bean是否存在
if(singletonBean==null){
singletonBean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName,singletonBean);
}
完整代码如下:
public Object getBean(String beanName){
if (!beanDefinitionMap.containsKey(beanName)){
throw new NullPointerException();
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断单例还是原型
if ("singleton".equals(beanDefinition.getScope())){
Object singletonBean = singletonObjects.get(beanName);
//判断单例bean是否存在
if(singletonBean==null){
singletonBean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName,singletonBean);
}
return singletonBean;
}else {
//原型
Object prototypeBean = createBean(beanName, beanDefinition);
return prototypeBean;
}
}
4、初始化
1、首先创建InitializingBean 接口,定义afterPropertiesSet
方法。
/**
* Spring 初始化
* @description
* @author KennySong
* @date 2023-3-22
*/
public interface InitializingBean {
void afterPropertiesSet();
}
2、其次在依赖注入完成以后,进行初始化操作,判断当前创建的类是否实现了初始化的Bean的接口,如果实现了就调用afterPropertiesSet();
方法,进行初始化。
//初始化,判断当前创建的类是否实现了初始化的Bean的接口
if (instance instanceof InitializingBean){
((InitializingBean) instance).afterPropertiesSet();
}
5、BeanPostProcessor (重要组件),初始化前,初始化后操作
1、定义BeanPostProcessor 接口
/**
* Bean初始化前,初始化后
* @description
* @author KennySong
* @date 2023-3-22
*/
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
2、创建MyBeanPostProcessor
类,实现BeanPostProcessor 接口里面的方法。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
如果我们想spring去调用我们的方法,一般写法是new MyBeanPostProcessor ().postProcessAfterInitialization(a,b)
去使用,很明显spring不可能去new一个我们自己的类。那么我们怎么来实现呢?
- 首先肯定是我们定义的类要被spring发现,那么怎么发现,实际上添加
@Component
即可 - 发现后,spring则需要去判断该类有没有实现BeanPostProcessor ,
clazz.isAssignableFrom(BeanPostProcessor.class)
如果有,则使用list存起来,此处不能用instanceof ,应为还在扫描阶段,instanceof是判断实例用的。 - 最后在初始化后调用初始化后的代码。
1、基于BeanPostProcessor 实现AOP
1、我们把执行了初始化后的对象赋值给instance
2、需改MyBeanPostProcessor 中postProcessAfterInitialization
方法,我们用动态代理,返回一个代理对象,应为JDK的动态代理是基于接口是实现的,因此我们创建一个IUserService接口,修改UserService实现IUserService。修改postProcessAfterInitialization
方法如下:
//AOP
if (beanName.equals("userService")){
Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//进行切面逻辑编写
System.out.println("UserService切面");
//继续执行原有的方法
return method.invoke(bean,args);
}
});
return proxyInstance;
}
初始话后我们返回了动态代理的对象实现了切面逻辑:
KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
IUserService userService = (IUserService) kennyApplicationContext.getBean("userService");
// System.out.println(kennyApplicationContext.getBean("userService"));
// System.out.println(kennyApplicationContext.getBean("userService"));
// System.out.println(kennyApplicationContext.getBean("orderService"));
userService.test();
执行结果如下:
初始化前
初始化后
UserService切面
test
6、BeanNameAware —》Aware回调
1、创建BeanNameAware接口
public interface BeanNameAware {
void setBeanName(String name);
}
2、在初始化前进行aware回调
//Aware回调
if (instance instanceof BeanNameAware){
((BeanNameAware) instance).setBeanName(beanName);
}
7、 总结
1.扫描
(1)、创建SpringContext解析ComponentScan注解
通过传入的AppConfig解析ComponentScan注解,拿到扫描路径,解析扫描路径,通过AppClassLoader拿到需要扫描的包路径
(2)、扫描bean,解析Component注解
将扫描路径扫描到后,通过AppClassLoader中loadClass
方法加载对象,判断对象是不是添加了Component注解,如果是则是我们要找的bean,不是则不管。通过Component注解可以拿到bean的名字,如果没有传值,则使用Introspector.decapitalize(clazz.getSimpleName());
获取默认名字。
(3)、判断是不是实现了BeanPostProcessor接口
使用BeanPostProcessor.class.isAssignableFrom(clazz)
判断是不是实现了BeanPostProcessor 接口,如果实现了则直接实例化对象,并将该对象放入beanPostProcessorList中存储。如果没有实现则执行第四步。因为BeanPostProcessor 是针对所有的Bean,所以不需要使用map存储。
(4)、创建BeanDefinition
将扫描到的bean创建BeanDefinition对象,BeanDefinition对象用于存储对象,描述对象类型、作用域(单例还是原型)、是不是懒加载等信息;判断作用域是使用Scope
注解,创建完成后将BeanDefinition对象存入beanDefinitionMap中。
2、创建单例Bean
(1)、循环beanDefinitionMap判断是否是单例bean
通过循环扫描得到的beanDefinitionMap,来判断当前bean是否是单例bean,如果是单例bean则直接实例化bean,并将当前对象方式单例Map中singletonObjects。
(2)、创建bean
通过BeanDefinition中类型,调用默认构造方法,实例化对象instance
(3)、依赖注入
通过bean类型,使用 clazz.getDeclaredFields()
获取所有的字段,循环字段列表,判断该字段是否添加了Autowired
注解,如果添加了则设置字段可编辑field.setAccessible(true);
,并通过instance赋值,赋值的时候需要传入对象以及值,对象则是第二步实例化的 instance,而需要赋的值,则可以通过getBean
方法拿到,写法如下field.set(instance,getBean(field.getName()));
(4)、Aware回调
当有bean实现了BeanNameAware接口时候,则在依赖注入完成后,进行Aware回调。实现方式是判断实例是否实现了BeanNameAware接口instance instanceof BeanNameAware
,如果实现了则调用其方法如: ((BeanNameAware) instance).setBeanName(beanName);
(5)、BeanPostProcessor初始化前方法调用
通过扫描拿到了所有实现了BeanPostProcessor 接口的bean,instance初始化之前循环调用postProcessBeforeInitialization方法beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
,如果将创建的beanPostProcessor返回的对象赋值给instance实例的话,我们可以实现AOP instance = beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
。
(6)、初始化
判断当前对象是否继承了InitializingBean
接口,instance instanceof InitializingBean
,如果继承了,则执行初始化逻辑,调用afterPropertiesSet();
方法,代码如下 ((InitializingBean) instance).afterPropertiesSet();
(7)、BeanPostProcessor初始化后方法调用
与第5步相同。
3、getBean(String beanName)以及BeanPostProcessor 方法的修改。
1、getBean(String beanName)
- 从创建bean的时候我们调用了一次getBean方法,需要通过beanName获取bean。通过传入的beanName在beanDefinitionMap中找出扫描到的beanDefinition
- 如果为空,则说明没有扫描到该bean,返回空指针异常,不为空,则继续执行
- 不为空取出beanDefinition,判断是否是单例,如果是单例则从singletonObjects中取出bean,如果取出的bean为空,则可能当前的bean还未创建完成,则调用createBean方法,创建一个实例并返回,如果不为空则直接返回
- 如果不是单例,则调用createBean方法,创建一个实例并返回。
2、BeanPostProcessor 方法的修改。
比如我们自己写了一个MyBeanPostProcessor 实现了BeanPostProcessor 接口。
那么我们可以重写 postProcessAfterInitialization(Object bean, String beanName)
或者postProcessBeforeInitialization(Object bean, String beanName)
方法,应为我们在创建bean的时候,我们进行了赋值。那么我们可以在自定义BeanPostProcessor 类中通过动态代理返回一个代理对象,那么我们通过getBean方法调用的时候,实际获取的是我们返回回去的代理对象。那么我们就可以在调用实际方法前做一些事情,如下:
//AOP
if (beanName.equals("userService")){
Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//进行切面逻辑编写
System.out.println("UserService切面");
return method.invoke(bean,args);
}
});
return proxyInstance;
}
方法解析这里就不在赘述,上面已经解析过了。那么我门在调用userService里面的方法的时候,都会先执行我们这个切面逻辑。