简单模拟Spring
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 一个有梦有戏的人 @怒放吧德德
🌝分享学习心得,欢迎指正,大家一起学习成长!
文章目录
前言
上次已经学习了Java的设计模式,接下来就先来学习一下如何手写模拟简易的Spring,通过动手实践,才会更好的了解spring底层原理,今天就简单的模拟Spring容器是如何创建,bean又是如何注入的。
来看一下本次案例的spring类图
Spring容器
模拟spring,首先就是需要一个容器,是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管理。Spring容器也就是一个bean工厂(BeanFactory)。应用中bean的实例化,获取,销毁等都是由这个bean工厂管理的。就像我们刚开始学习的时候接触的ApplicationContext,就是spring的容器,他就是为了完成容器的配置,初始化,管理bean的。因此笔者自己创建了一个LydApplicationContext来模拟简单的spring容器。
开始使用
首先通过
new LydApplicationContext(AppConfig.class)
实例化对象,在通过
applicationContext.getBean("userService")
去获得bean对象。然而在容器的初始化可是做了许多的事情,包括扫描、实例化bean等等操作。
初始容器创建:
publicclassLydApplicationContext{privateClass configClass;publicLydApplicationContext(Class configClass){// 构造方法this.configClass = configClass;}}
Spring扫描底层实现
Spring容器建好之后我们就需要通过配置文件的注解获取扫描路径,我们需要获取所有的bean,并且需要实例对象。在此我们需要一个配置文件,就是使用
new LydApplicationContext(AppConfig.class)
实例携带的配置类,当然这里有好多的形式,也可以是通过xml文件来处理。
配置文件AppConfig.java
这个就是为了提供扫描的包路径的,不做任何操作,所以不需要其他代码。
@ComponentScan("com.lyd.service")// 扫描路径,扫描这个包下的publicclassAppConfig{}
通过注解存放这个包路径,在后面可以通过这个注解来获取包路径,所以就需要我们创建一个
ComponentScan
注解。
编写ComponentScan注解
这个注解是用来spring容器扫描包为之提供包路径。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceComponentScan{// 指定扫描路径Stringvalue()default"";}
编写Component注解
在Spring中,通过
Component
注解将bean注入Spring容器中,这里我们也采用高这个注解。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceComponent{Stringvalue()default"";}
获取包路径
既然已经通过注解将包路径存在配置类中,接下来就可以通过这个注解来得到。但是,在这里需要注意的是,我们扫描的并非是java源文件,而是编译后的class文件。我们需要在
LydApplicationContext
的构造方法中去实现。
首先,我们需要通过isAnnotationPresent方法先判断是否存在
ComponentScan
注解,在通过类的
getAnnotation
方法来得到注解。这样就可以直接得到注解上的值。这个值就是我们写入的包路径,注意,这里的路径是com.lyd.service,而我们需要用替换方法将’.‘替换成’/',因为在后面获取资源路径的时候,用的是
com/lyd/service
这种形式,也就是相对路径。
接下来需要获取资源路径,这个时候就需要用到类加载器
LydApplicationContext.class.getClassLoader()
,类加载器中有一个
getResource(path)
方法,这个可以根据传入的路径获取相应的资源,最后是能够拼出我们需要的绝对路径。
if(configClass.isAnnotationPresent(ComponentScan.class)){ComponentScan componentScanAnnotation =(ComponentScan) configClass.getAnnotation(ComponentScan.class);// 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.serviceString path = componentScanAnnotation.value();// 1.2 将路径文件替换成/的形式
path = path.replace(".","/");// 1.3 通过类加载器获取资源路径ClassLoader classLoader =LydApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);// 1.4 转成文件形式,主要是为了获取他的绝对地址File file =newFile(resource.getFile());System.out.println(file);}
通过类加载器得到的资源有个获取File的方法,然后我们通过
File file = new File(resource.getFile())
;将资源转成File类型,因为他可以表示一个地址或者是具体文件。扫描就是扫描文件路径,也就是文件夹。我们可以打印看一下这个地址:
判断是否为文件夹,如果是,那就获取里面的所有文件,在通过遍历这些文件,获取绝对路径。
if(file.isDirectory()){// 如果是文件夹File[] files = file.listFiles();for(File f : files){String absolutePath = f.getAbsolutePath();// 获取绝对路径System.out.println("绝对路径:"+ absolutePath);}}
我们可以打印出来看一下:
因为我们要的是编译的.class文件,因此需要在遍历文件的时候进行判断文件是否为.class文件。为了拿到这个类,需要通过全限定名使用类加载器获取类 ,也就是利用反射机制。我们都知道,spring是通过Component注解来将bean注入spring的,因此最后就是通过判断是否有这个注解来得到一个bean。
for(File f : files){String absolutePath = f.getAbsolutePath();// 获取绝对路径System.out.println("绝对路径:"+ absolutePath);// 1.5 对编译文件进行处理if(absolutePath.endsWith(".class")){// 判断是否为编译文件/**
* 需要拿到的是编译文件,通过类加载器去获取
* 需要将com\lyd\service\UserService转成com.lyd.service.UserService
*/String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
className = className.replace("\\",".");System.out.println("类名:"+ className);try{// 1.6 通过全限定名使用类加载器获取类 (利用反射机制)Class<?> clazz = classLoader.loadClass(className);// 1.7 在通过这个clazz(类)来判断是否有component注解,有则是beanif(clazz.isAnnotationPresent(Component.class)){// 到这里就是一个bean}}catch(Exception e){
e.printStackTrace();}}}
最后我们需要的地址就是如下:
Spring生成BeanDefinition
在我们Spring容器启动或者是扫描的时候,并不建议直接实例化bean对象,因为bean是区分单例和多例的,多例bean我们是需要用到的时候再去创建。这个时候就需要生成BeanDefinition,即bean的定义,这个类存储了类和作用域(单例还是多例)。
定义Scope注解
通过Scope这个注解来标明是单例bean还是多例bean。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceScope{Stringvalue()default"";}
定义BeanDefinition
在spring中,不会在获取bean的时候再去解析是否为单例,而是通过BeanDefinition类来操作。对bean的定义,记录了bean类和作用域。
publicclassBeanDefinition{privateClass type;privateString scope;// 单例多例publicClassgetType(){return type;}publicvoidsetType(Class type){this.type = type;}publicStringgetScope(){return scope;}publicvoidsetScope(String scope){this.scope = scope;}}
因为我们注入spring的bean对象是有Component注解,因此在扫描的时候,我们会通过这个获得到bean,在这时候去创建BeanDefinition对象。通过判断注解上的值来赋值其作用域,如果没有设置,就默认是单例模式。
创建好的bean对象,我们还需要将他进行保存起来,这个就需要定义
ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>()
;存储beanDefinition。key是component的值,如果component没有传入beanName的值,那就使用spring的命名规则,采用类名首字母小写。
// 1.7 在通过这个clazz(类)来判断是否有component注解,有则是beanif(clazz.isAnnotationPresent(Component.class)){Component annotation = clazz.getAnnotation(Component.class);String beanName = annotation.value();// * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成if(beanName.equals("")){// 默认开头小字母
beanName =Introspector.decapitalize(clazz.getSimpleName());}/**
* 這就是一个bean了
* 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean
*/BeanDefinition beanDefinition =newBeanDefinition();
beanDefinition.setType(clazz);if(clazz.isAnnotationPresent(Scope.class)){// 判断是单例还是多例Scope scope = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());}else{
beanDefinition.setScope("singleton");}
beanDefinitionMap.put(beanName, beanDefinition);}
getBean底层
通过传来一个beanName,通过beanDefinitionMap中获取key为beanName的BeanDefinition对象,进行判空处理。这样我们可以通过这个BeanDefinition对象去获取作用域,判断是否为单例。
获取bean
在此,我们创建单例的时候,是需要将单例保存起来的,需要定义
ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>()
;单例池来保存单例bean。然后在getBean的时候,如果是单例bean,就可以先去单例池中寻找,如果没找到,再去创建对象。而多例模式就需要每次都去创建。
// 获取bean对象publicObjectgetBean(String beanName){BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if(beanDefinition ==null){thrownewNullPointerException("找不到bean名字为["+ beanName +"]的bean对象");}else{// 找到就做相应的操作String scope = beanDefinition.getScope();if(scope.equals("singleton")){// 通过单例池获取Object bean = singletonObjects.get(beanName);if(bean ==null){// 单例池中如果没有bean,就需要去创建
bean =createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);}return bean;}else{// 多例的就不需要记录,每次都是通过创建returncreateBean(beanName, beanDefinition);}}}
创建bean
利用反射机制,通过无参构造方法去获取实例,这里就是bean对象实例了。就可以直接返回。
// 创建beanprivateObjectcreateBean(String beanName,BeanDefinition definition){// 利用反射获取实例,采用无参构造方法Class clazz = definition.getType();// 通过无参构造方法获取实例try{Object instance = clazz.getConstructor().newInstance();// 到这里直接返回,bean的对象也就创建完成return instance;}catch(InstantiationException e){
e.printStackTrace();}returnnull;}
在构造方法扫描后要根据生成的beanDefinitionMap去创建单例bean对象。
// 2 创建单例bean对象for(String beanName : beanDefinitionMap.keySet()){BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if(beanDefinition.getScope().equals("singleton")){// 那么,如何保证单例呢?就需要有个单例池,singletonObjectsObject bean =createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);// 将单例bean存到单例池中}}
运行
publicclassTest{publicstaticvoidmain(String[] args){LydApplicationContext applicationContext =newLydApplicationContext(AppConfig.class);System.out.println(applicationContext.getBean("userService"));System.out.println(applicationContext.getBean("userService"));System.out.println(applicationContext.getBean("userService"));System.out.println(applicationContext.getBean("userService"));System.out.println(applicationContext.getBean("userService"));}}
我们可以先使用单例模式进行测试,我们不加scope注解,并且获取多个bean,可以看到得到的bean对象是同一个。
多例的时候在scope标上值,就可以看到每次获取的bean对象都是不一样的。
Autowired自动依赖注入
当我们在UserService中使用RoleService,我们就需要通过Autowired注解进行依赖注入。就是需要在createBean方法中,在创建实例之后,获取类中的字段,进行依赖注入,要通过判断Autowried注解。通过字段的set方法,将bean对象注入,而这个对象如何获得呢?那就是用过getBean()方法。
// 3 依赖注入for(Field field : clazz.getDeclaredFields()){// 判断字段上是否存在Autowried注解if(field.isAnnotationPresent(Autowired.class)){/**
* 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
* 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
* 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;
*/
field.setAccessible(true);// 反射需要设置这个,不然无法赋值// 用其属性名,这就意味着private RoleService roleService;roleService不能乱取
field.set(instance,getBean(field.getName()));}}
Aware回调机制与初始化
Aware回调
那如果我们需要获取当前bean的名字呢?那就得通过Aware回调机制。我们需要创建一个BeanNameAware接口,里面提供一个setBeanName方法。
publicinterfaceBeanNameAware{publicvoidsetBeanName(String beanName);}
在createBean方法中去编写回调机制,通过判断这个实例是否有BeanNameAware这个类,通过setName方法间接传递了beanName。
// 4 Aware回调机制if(instance instanceofBeanNameAware){((BeanNameAware)instance).setBeanName(beanName);}
在UserService方法中去实现这个BeanNameAware方法,这就能够在UserService里的beanName字段中得到这个bean对象的真实的beanName了。
@Component("userService")// 注入spring@Scope("property")publicclassUserServiceimplementsBeanNameAware,InitializingBean{@AutowiredprivateRoleService roleService;// 依赖注入,加上@Autowired注解privateString beanName;@OverridepublicvoidsetBeanName(String beanName){this.beanName = beanName;}publicvoidtest(){System.out.println("RoleService: "+ roleService);}@OverridepublicvoidafterPropertiesSet(){// ......System.out.println("初始化");}}
初始化
在spring容器中,不只是完成bean对象的创建,还需要能够对bean进行初始化。需要创建InitializingBean接口类。
publicinterfaceInitializingBean{publicvoidafterPropertiesSet();}
在需要进行初始化的bean对象去实现这个接口的方法,这里main可以进行一些操作。在createBean中,会根据判断是否有InitializingBean类,会在实例化之后调用这个方法进行初始化。
// 5 初始化if(instance instanceofInitializingBean){((InitializingBean)instance).afterPropertiesSet();}
结果
BeanPostProcessor
BeanPostProcessor可以对spring中bean的创建去做一些操作。
BeanPostProcessor接口
在spring中定义一个BeanPostProcessor接口,里面会有初始化前后操作的方法,并且将beanName和bean对象带入进行自定义的操作。
publicinterfaceBeanPostProcessor{publicObjectpostBeforeProcessor(String beanName,Object bean);// 初始化前publicObjectpostAfterProcessor(String beanName,Object bean);// 初始化后}
自定义BeanPostProcessor
这里可以定义自己的类去实现spring中的BeanPostProcessor,对初始化进行一些相应操作。并且能够根据某个bean对象来做不同的操作。原理就是将MyBeanPostProcessor注入到容器中,在扫描的时候将这个对象保存起来,在创建bean的时候去遍历BeanPostProcessor集合,在去调用这个实例的方法。
@Component// 需要注入springpublicclassMyBeanPostProcessorimplementsBeanPostProcessor{@OverridepublicObjectpostBeforeProcessor(String beanName,Object bean){System.out.println("初始化前的bean:"+ beanName +" -- "+ bean);return bean;}@OverridepublicObjectpostAfterProcessor(String beanName,Object bean){System.out.println("初始化后的bean:"+ beanName +" -- "+ bean);return bean;}}
然而,在spring扫描的时候会进行操作,因为自己实现的BeanPostProcessor是通过Component注解注入spring容器的。因此可以通过判断有Component注解时候,进行判断是否含有BeanPostProcessor类,如果有生成BeanPostProcessor对象,并且将其实例添加到beanPostProcessorList容器中。在此就需要定义
ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>()
;来记录BeanPostProcessor。
// 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceofif(BeanPostProcessor.class.isAssignableFrom(clazz)){// 直接生成对象BeanPostProcessor instance =(BeanPostProcessor) clazz.newInstance();// 然后保存进去
beanPostProcessorList.add(instance);}
接着就可以在创建bean对象的时候,在初始化前后去遍历这个BeanPostProcessor链表,调用相应的方法,就能够调用自定义的MyBeanPostProcessor的方法。
// 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorListfor(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
instance = beanPostProcessor.postBeforeProcessor(beanName, instance);}// 5 初始化if(instance instanceofInitializingBean){((InitializingBean)instance).afterPropertiesSet();}// 6 BeanPostProcessor 初始化后 AOPfor(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
instance = beanPostProcessor.postAfterProcessor(beanName, instance);}
运行结果:.
AOP机制
需要通过代理对象。这样如果不进行操作,返回的对象还是原来的,如果是通过操作了,那么返回的就是代理的对象。这里就简单的打印一句话。在扫描的时候,扫到userService这个bean的时候,返回的实例就是代理对象。
@OverridepublicObjectpostAfterProcessor(String beanName,Object bean){System.out.println("初始化后的bean:"+ beanName +" -- "+ bean);if(beanName.equals("userService")){// 创建一个代理对象, 代理的是UserInterface这个Object proxyInstance =Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(),newInvocationHandler(){@Override/**
* proxy:代理对象
* method:代理对象当前正在执行的方法
*/publicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{System.out.println("切面逻辑");return method.invoke(bean, args);}});return proxyInstance;}return bean;}
源码备注
spring
LydApplicationContext
publicclassLydApplicationContext{privateClass configClass;// 注入的配置类privateConcurrentHashMap<String,BeanDefinition> beanDefinitionMap =newConcurrentHashMap<>();// key是component名字privateConcurrentHashMap<String,Object> singletonObjects =newConcurrentHashMap<>();// 单例池privateArrayList<BeanPostProcessor> beanPostProcessorList =newArrayList<>();// 记录BeanPostProcessor,在扫描的时候判断,使用publicLydApplicationContext(Class configClass){// 构造方法this.configClass = configClass;// spring容器创建之后// 1 扫描// 通过配置文件的注解获取扫描路径if(configClass.isAnnotationPresent(ComponentScan.class)){ComponentScan componentScanAnnotation =(ComponentScan) configClass.getAnnotation(ComponentScan.class);// 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.serviceString path = componentScanAnnotation.value();// 1.2 将路径文件替换成/的形式
path = path.replace(".","/");// 1.3 通过类加载器获取资源路径ClassLoader classLoader =LydApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);// 1.4 转成文件形式,主要是为了获取他的绝对地址File file =newFile(resource.getFile());// System.out.println(file);if(file.isDirectory()){// 如果是文件夹File[] files = file.listFiles();for(File f : files){String absolutePath = f.getAbsolutePath();// 获取绝对路径System.out.println("绝对路径:"+ absolutePath);// 1.5 对编译文件进行处理if(absolutePath.endsWith(".class")){// 判断是否为编译文件/**
* 需要拿到的是编译文件,通过类加载器去获取
* 需要将com\lyd\service\UserService转成com.lyd.service.UserService
*/String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
className = className.replace("\\",".");System.out.println("类名:"+ className);try{// 1.6 通过全限定名使用类加载器获取类 (利用反射机制)Class<?> clazz = classLoader.loadClass(className);// 1.7 在通过这个clazz(类)来判断是否有component注解,有则是beanif(clazz.isAnnotationPresent(Component.class)){// 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceofif(BeanPostProcessor.class.isAssignableFrom(clazz)){// 直接生成对象BeanPostProcessor instance =(BeanPostProcessor) clazz.newInstance();// 然后保存进去
beanPostProcessorList.add(instance);}Component annotation = clazz.getAnnotation(Component.class);String beanName = annotation.value();// * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成if(beanName.equals("")){// 默认开头小字母
beanName =Introspector.decapitalize(clazz.getSimpleName());}/**
* 這就是一个bean了
* 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean
*/BeanDefinition beanDefinition =newBeanDefinition();
beanDefinition.setType(clazz);if(clazz.isAnnotationPresent(Scope.class)){// 判断是单例还是多例Scope scope = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());}else{
beanDefinition.setScope("singleton");}
beanDefinitionMap.put(beanName, beanDefinition);}}catch(Exception e){
e.printStackTrace();}}}}}// 2 创建单例bean对象for(String beanName : beanDefinitionMap.keySet()){BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if(beanDefinition.getScope().equals("singleton")){// 那么,如何保证单例呢?就需要有个单例池,singletonObjectsObject bean =createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);// 将单例bean存到单例池中}}}// 创建beanprivateObjectcreateBean(String beanName,BeanDefinition definition){// 利用反射获取实例,采用无参构造方法Class clazz = definition.getType();// 通过无参构造方法获取实例try{Object instance = clazz.getConstructor().newInstance();// 到这里直接返回,bean的对象也就创建完成// 3 依赖注入for(Field field : clazz.getDeclaredFields()){// 判断字段上是否存在Autowried注解if(field.isAnnotationPresent(Autowired.class)){/**
* 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
* 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
* 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;
*/
field.setAccessible(true);// 反射需要设置这个,不然无法赋值// 用其属性名,这就意味着private RoleService roleService;roleService不能乱取
field.set(instance,getBean(field.getName()));}}// * instanceof:是针对某个对象去判断是否实现某个类// 4 Aware回调机制if(instance instanceofBeanNameAware){((BeanNameAware)instance).setBeanName(beanName);}// 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorListfor(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
instance = beanPostProcessor.postBeforeProcessor(beanName, instance);}// 5 初始化if(instance instanceofInitializingBean){((InitializingBean)instance).afterPropertiesSet();}// 6 BeanPostProcessor 初始化后 AOPfor(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
instance = beanPostProcessor.postAfterProcessor(beanName, instance);}return instance;}catch(InstantiationException e){
e.printStackTrace();}catch(IllegalAccessException e){
e.printStackTrace();}catch(InvocationTargetException e){
e.printStackTrace();}catch(NoSuchMethodException e){
e.printStackTrace();}returnnull;}// 获取bean对象publicObjectgetBean(String beanName){BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if(beanDefinition ==null){thrownewNullPointerException("找不到bean名字为["+ beanName +"]的bean对象");}else{// 找到就做相应的操作String scope = beanDefinition.getScope();if(scope.equals("singleton")){// 通过单例池获取Object bean = singletonObjects.get(beanName);if(bean ==null){// 单例池中如果没有bean,就需要去创建
bean =createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);}return bean;}else{// 多例的就不需要记录,每次都是通过创建returncreateBean(beanName, beanDefinition);}}}}
service
test
publicclassTest{publicstaticvoidmain(String[] args){LydApplicationContext applicationContext =newLydApplicationContext(AppConfig.class);// UserService userService = (UserService) applicationContext.getBean("userService");UserInterface userService =(UserInterface) applicationContext.getBean("userService");// System.out.println(applicationContext.getBean("userService"));// System.out.println(applicationContext.getBean("roleService"));
userService.test();}}
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
版权归原作者 一个有梦有戏的人 所有, 如有侵权,请联系我们删除。