简单模拟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等等操作。

初始容器创建:

  1. public class LydApplicationContext {
  2. private Class configClass;
  3. public LydApplicationContext(Class configClass) { // 构造方法
  4. this.configClass = configClass;
  5. }
  6. }

Spring扫描底层实现

Spring容器建好之后我们就需要通过配置文件的注解获取扫描路径,我们需要获取所有的bean,并且需要实例对象。在此我们需要一个配置文件,就是使用new LydApplicationContext(AppConfig.class) 实例携带的配置类,当然这里有好多的形式,也可以是通过xml文件来处理。

配置文件AppConfig.java

这个就是为了提供扫描的包路径的,不做任何操作,所以不需要其他代码。

  1. @ComponentScan("com.lyd.service") // 扫描路径,扫描这个包下的
  2. public class AppConfig {
  3. }

通过注解存放这个包路径,在后面可以通过这个注解来获取包路径,所以就需要我们创建一个ComponentScan注解。

编写ComponentScan注解

这个注解是用来spring容器扫描包为之提供包路径。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ComponentScan {
  4. // 指定扫描路径
  5. String value() default "";
  6. }

编写Component注解

在Spring中,通过Component注解将bean注入Spring容器中,这里我们也采用高这个注解。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Component {
  4. String value() default "";
  5. }

获取包路径

既然已经通过注解将包路径存在配置类中,接下来就可以通过这个注解来得到。但是,在这里需要注意的是,我们扫描的并非是java源文件,而是编译后的class文件。我们需要在LydApplicationContext的构造方法中去实现。

首先,我们需要通过isAnnotationPresent方法先判断是否存在ComponentScan注解,在通过类的getAnnotation方法来得到注解。这样就可以直接得到注解上的值。这个值就是我们写入的包路径,注意,这里的路径是com.lyd.service,而我们需要用替换方法将'.'替换成'/',因为在后面获取资源路径的时候,用的是com/lyd/service这种形式,也就是相对路径。

接下来需要获取资源路径,这个时候就需要用到类加载器LydApplicationContext.class.getClassLoader(),类加载器中有一个getResource(path)方法,这个可以根据传入的路径获取相应的资源,最后是能够拼出我们需要的绝对路径。

  1. if (configClass.isAnnotationPresent(ComponentScan.class)) {
  2. ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
  3. // 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.service
  4. String path = componentScanAnnotation.value();
  5. // 1.2 将路径文件替换成/的形式
  6. path = path.replace(".","/");
  7. // 1.3 通过类加载器获取资源路径
  8. ClassLoader classLoader = LydApplicationContext.class.getClassLoader();
  9. URL resource = classLoader.getResource(path);
  10. // 1.4 转成文件形式,主要是为了获取他的绝对地址
  11. File file = new File(resource.getFile());
  12. System.out.println(file);
  13. }

通过类加载器得到的资源有个获取File的方法,然后我们通过File file = new File(resource.getFile());将资源转成File类型,因为他可以表示一个地址或者是具体文件。扫描就是扫描文件路径,也就是文件夹。我们可以打印看一下这个地址:

判断是否为文件夹,如果是,那就获取里面的所有文件,在通过遍历这些文件,获取绝对路径。

  1. if (file.isDirectory()) { // 如果是文件夹
  2. File[] files = file.listFiles();
  3. for (File f : files) {
  4. String absolutePath = f.getAbsolutePath(); // 获取绝对路径
  5. System.out.println("绝对路径:" + absolutePath);
  6. }
  7. }

我们可以打印出来看一下:

因为我们要的是编译的.class文件,因此需要在遍历文件的时候进行判断文件是否为.class文件。为了拿到这个类,需要通过全限定名使用类加载器获取类 ,也就是利用反射机制。我们都知道,spring是通过Component注解来将bean注入spring的,因此最后就是通过判断是否有这个注解来得到一个bean。

  1. for (File f : files) {
  2. String absolutePath = f.getAbsolutePath(); // 获取绝对路径
  3. System.out.println("绝对路径:" + absolutePath);
  4. // 1.5 对编译文件进行处理
  5. if (absolutePath.endsWith(".class")) { // 判断是否为编译文件
  6. /**
  7. * 需要拿到的是编译文件,通过类加载器去获取
  8. * 需要将com\lyd\service\UserService转成com.lyd.service.UserService
  9. */
  10. String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
  11. className = className.replace("\\", ".");
  12. System.out.println("类名:" + className);
  13. try {
  14. // 1.6 通过全限定名使用类加载器获取类 (利用反射机制)
  15. Class<?> clazz = classLoader.loadClass(className);
  16. // 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean
  17. if (clazz.isAnnotationPresent(Component.class)) {
  18. // 到这里就是一个bean
  19. }
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

最后我们需要的地址就是如下:

Spring生成BeanDefinition

在我们Spring容器启动或者是扫描的时候,并不建议直接实例化bean对象,因为bean是区分单例和多例的,多例bean我们是需要用到的时候再去创建。这个时候就需要生成BeanDefinition,即bean的定义,这个类存储了类和作用域(单例还是多例)。

定义Scope注解

通过Scope这个注解来标明是单例bean还是多例bean。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Scope {
  4. String value() default "";
  5. }

定义BeanDefinition

在spring中,不会在获取bean的时候再去解析是否为单例,而是通过BeanDefinition类来操作。对bean的定义,记录了bean类和作用域。

  1. public class BeanDefinition {
  2. private Class type;
  3. private String scope; // 单例多例
  4. public Class getType() {
  5. return type;
  6. }
  7. public void setType(Class type) {
  8. this.type = type;
  9. }
  10. public String getScope() {
  11. return scope;
  12. }
  13. public void setScope(String scope) {
  14. this.scope = scope;
  15. }
  16. }

因为我们注入springbean对象是有Component注解,因此在扫描的时候,我们会通过这个获得到bean,在这时候去创建BeanDefinition对象。通过判断注解上的值来赋值其作用域,如果没有设置,就默认是单例模式。

创建好的bean对象,我们还需要将他进行保存起来,这个就需要定义ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();存储beanDefinition。key是component的值,如果component没有传入beanName的值,那就使用spring的命名规则,采用类名首字母小写。

  1. // 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean
  2. if (clazz.isAnnotationPresent(Component.class)) {
  3. Component annotation = clazz.getAnnotation(Component.class);
  4. String beanName = annotation.value();
  5. // * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成
  6. if (beanName.equals("")) {
  7. // 默认开头小字母
  8. beanName = Introspector.decapitalize(clazz.getSimpleName());
  9. }
  10. /**
  11. * 這就是一个bean了
  12. * 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean
  13. */
  14. BeanDefinition beanDefinition = new BeanDefinition();
  15. beanDefinition.setType(clazz);
  16. if (clazz.isAnnotationPresent(Scope.class)) { // 判断是单例还是多例
  17. Scope scope = clazz.getAnnotation(Scope.class);
  18. beanDefinition.setScope(scope.value());
  19. } else {
  20. beanDefinition.setScope("singleton");
  21. }
  22. beanDefinitionMap.put(beanName, beanDefinition);
  23. }

getBean底层

通过传来一个beanName,通过beanDefinitionMap中获取key为beanName的BeanDefinition对象,进行判空处理。这样我们可以通过这个BeanDefinition对象去获取作用域,判断是否为单例。

获取bean

在此,我们创建单例的时候,是需要将单例保存起来的,需要定义ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();单例池来保存单例bean。然后在getBean的时候,如果是单例bean,就可以先去单例池中寻找,如果没找到,再去创建对象。而多例模式就需要每次都去创建。

  1. // 获取bean对象
  2. public Object getBean(String beanName) {
  3. BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
  4. if (beanDefinition == null) {
  5. throw new NullPointerException("找不到bean名字为[" + beanName + "]的bean对象");
  6. } else { // 找到就做相应的操作
  7. String scope = beanDefinition.getScope();
  8. if (scope.equals("singleton")) {
  9. // 通过单例池获取
  10. Object bean = singletonObjects.get(beanName);
  11. if (bean == null) {
  12. // 单例池中如果没有bean,就需要去创建
  13. bean = createBean(beanName, beanDefinition);
  14. singletonObjects.put(beanName, bean);
  15. }
  16. return bean;
  17. } else {
  18. // 多例的就不需要记录,每次都是通过创建
  19. return createBean(beanName, beanDefinition);
  20. }
  21. }
  22. }

创建bean

利用反射机制,通过无参构造方法去获取实例,这里就是bean对象实例了。就可以直接返回。

  1. // 创建bean
  2. private Object createBean(String beanName, BeanDefinition definition) {
  3. // 利用反射获取实例,采用无参构造方法
  4. Class clazz = definition.getType();
  5. // 通过无参构造方法获取实例
  6. try {
  7. Object instance = clazz.getConstructor().newInstance(); // 到这里直接返回,bean的对象也就创建完成
  8. return instance;
  9. } catch (InstantiationException e) {
  10. e.printStackTrace();
  11. }
  12. return null;
  13. }

在构造方法扫描后要根据生成的beanDefinitionMap去创建单例bean对象。

  1. // 2 创建单例bean对象
  2. for (String beanName : beanDefinitionMap.keySet()) {
  3. BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
  4. if (beanDefinition.getScope().equals("singleton")) { // 那么,如何保证单例呢?就需要有个单例池,singletonObjects
  5. Object bean = createBean(beanName, beanDefinition);
  6. singletonObjects.put(beanName, bean); // 将单例bean存到单例池中
  7. }
  8. }

运行

  1. public class Test {
  2. public static void main(String[] args) {
  3. LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class);
  4. System.out.println(applicationContext.getBean("userService"));
  5. System.out.println(applicationContext.getBean("userService"));
  6. System.out.println(applicationContext.getBean("userService"));
  7. System.out.println(applicationContext.getBean("userService"));
  8. System.out.println(applicationContext.getBean("userService"));
  9. }
  10. }

我们可以先使用单例模式进行测试,我们不加scope注解,并且获取多个bean,可以看到得到的bean对象是同一个。



多例的时候在scope标上值,就可以看到每次获取的bean对象都是不一样的。

Autowired自动依赖注入

当我们在UserService中使用RoleService,我们就需要通过Autowired注解进行依赖注入。就是需要在createBean方法中,在创建实例之后,获取类中的字段,进行依赖注入,要通过判断Autowried注解。通过字段的set方法,将bean对象注入,而这个对象如何获得呢?那就是用过getBean()方法。

  1. // 3 依赖注入
  2. for (Field field : clazz.getDeclaredFields()) {
  3. // 判断字段上是否存在Autowried注解
  4. if (field.isAnnotationPresent(Autowired.class)) {
  5. /**
  6. * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
  7. * 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
  8. * 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;
  9. */
  10. field.setAccessible(true); // 反射需要设置这个,不然无法赋值
  11. // 用其属性名,这就意味着private RoleService roleService;roleService不能乱取
  12. field.set(instance, getBean(field.getName()));
  13. }
  14. }

Aware回调机制与初始化

Aware回调

那如果我们需要获取当前bean的名字呢?那就得通过Aware回调机制。我们需要创建一个BeanNameAware接口,里面提供一个setBeanName方法。

  1. public interface BeanNameAware {
  2. public void setBeanName(String beanName);
  3. }

createBean方法中去编写回调机制,通过判断这个实例是否有BeanNameAware这个类,通过setName方法间接传递了beanName。

  1. // 4 Aware回调机制
  2. if (instance instanceof BeanNameAware) {
  3. ((BeanNameAware)instance).setBeanName(beanName);
  4. }

UserService方法中去实现这个BeanNameAware方法,这就能够在UserService里的beanName字段中得到这个bean对象的真实的beanName了。

  1. @Component("userService") // 注入spring
  2. @Scope("property")
  3. public class UserService implements BeanNameAware, InitializingBean {
  4. @Autowired
  5. private RoleService roleService; // 依赖注入,加上@Autowired注解
  6. private String beanName;
  7. @Override
  8. public void setBeanName(String beanName) {
  9. this.beanName = beanName;
  10. }
  11. public void test() {
  12. System.out.println("RoleService: " + roleService);
  13. }
  14. @Override
  15. public void afterPropertiesSet() {
  16. // ......
  17. System.out.println("初始化");
  18. }
  19. }

初始化

在spring容器中,不只是完成bean对象的创建,还需要能够对bean进行初始化。需要创建InitializingBean接口类。

  1. public interface InitializingBean {
  2. public void afterPropertiesSet();
  3. }

在需要进行初始化的bean对象去实现这个接口的方法,这里main可以进行一些操作。在createBean中,会根据判断是否有InitializingBean类,会在实例化之后调用这个方法进行初始化。

  1. // 5 初始化
  2. if (instance instanceof InitializingBean) {
  3. ((InitializingBean)instance).afterPropertiesSet();
  4. }

结果

BeanPostProcessor

BeanPostProcessor可以对spring中bean的创建去做一些操作。

BeanPostProcessor接口

在spring中定义一个BeanPostProcessor接口,里面会有初始化前后操作的方法,并且将beanNamebean对象带入进行自定义的操作。

  1. public interface BeanPostProcessor {
  2. public Object postBeforeProcessor(String beanName, Object bean); // 初始化前
  3. public Object postAfterProcessor(String beanName, Object bean); // 初始化后
  4. }

自定义BeanPostProcessor

这里可以定义自己的类去实现spring中的BeanPostProcessor,对初始化进行一些相应操作。并且能够根据某个bean对象来做不同的操作。原理就是将MyBeanPostProcessor注入到容器中,在扫描的时候将这个对象保存起来,在创建bean的时候去遍历BeanPostProcessor集合,在去调用这个实例的方法。

  1. @Component // 需要注入spring
  2. public class MyBeanPostProcessor implements BeanPostProcessor {
  3. @Override
  4. public Object postBeforeProcessor(String beanName, Object bean) {
  5. System.out.println("初始化前的bean:" + beanName + " -- " + bean);
  6. return bean;
  7. }
  8. @Override
  9. public Object postAfterProcessor(String beanName, Object bean) {
  10. System.out.println("初始化后的bean:" + beanName + " -- " + bean);
  11. return bean;
  12. }
  13. }

然而,在spring扫描的时候会进行操作,因为自己实现的BeanPostProcessor是通过Component注解注入spring容器的。因此可以通过判断有Component注解时候,进行判断是否含有BeanPostProcessor类,如果有生成BeanPostProcessor对象,并且将其实例添加到beanPostProcessorList容器中。在此就需要定义ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();来记录BeanPostProcessor。

  1. // 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceof
  2. if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
  3. // 直接生成对象
  4. BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
  5. // 然后保存进去
  6. beanPostProcessorList.add(instance);
  7. }

接着就可以在创建bean对象的时候,在初始化前后去遍历这个BeanPostProcessor链表,调用相应的方法,就能够调用自定义的MyBeanPostProcessor的方法。

  1. // 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorList
  2. for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
  3. instance = beanPostProcessor.postBeforeProcessor(beanName, instance);
  4. }
  5. // 5 初始化
  6. if (instance instanceof InitializingBean) {
  7. ((InitializingBean)instance).afterPropertiesSet();
  8. }
  9. // 6 BeanPostProcessor 初始化后 AOP
  10. for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
  11. instance = beanPostProcessor.postAfterProcessor(beanName, instance);
  12. }

运行结果:.

AOP机制

需要通过代理对象。这样如果不进行操作,返回的对象还是原来的,如果是通过操作了,那么返回的就是代理的对象。这里就简单的打印一句话。在扫描的时候,扫到userService这个bean的时候,返回的实例就是代理对象。

  1. @Override
  2. public Object postAfterProcessor(String beanName, Object bean) {
  3. System.out.println("初始化后的bean:" + beanName + " -- " + bean);
  4. if (beanName.equals("userService")) {
  5. // 创建一个代理对象, 代理的是UserInterface这个
  6. Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
  7. @Override
  8. /**
  9. * proxy:代理对象
  10. * method:代理对象当前正在执行的方法
  11. */
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  13. System.out.println("切面逻辑");
  14. return method.invoke(bean, args);
  15. }
  16. });
  17. return proxyInstance;
  18. }
  19. return bean;
  20. }

源码备注

spring

LydApplicationContext

  1. public class LydApplicationContext {
  2. private Class configClass; // 注入的配置类
  3. private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // key是component名字
  4. private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 单例池
  5. private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(); // 记录BeanPostProcessor,在扫描的时候判断,使用
  6. public LydApplicationContext(Class configClass) { // 构造方法
  7. this.configClass = configClass;
  8. // spring容器创建之后
  9. // 1 扫描
  10. // 通过配置文件的注解获取扫描路径
  11. if (configClass.isAnnotationPresent(ComponentScan.class)) {
  12. ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
  13. // 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.service
  14. String path = componentScanAnnotation.value();
  15. // 1.2 将路径文件替换成/的形式
  16. path = path.replace(".","/");
  17. // 1.3 通过类加载器获取资源路径
  18. ClassLoader classLoader = LydApplicationContext.class.getClassLoader();
  19. URL resource = classLoader.getResource(path);
  20. // 1.4 转成文件形式,主要是为了获取他的绝对地址
  21. File file = new File(resource.getFile());
  22. // System.out.println(file);
  23. if (file.isDirectory()) { // 如果是文件夹
  24. File[] files = file.listFiles();
  25. for (File f : files) {
  26. String absolutePath = f.getAbsolutePath(); // 获取绝对路径
  27. System.out.println("绝对路径:" + absolutePath);
  28. // 1.5 对编译文件进行处理
  29. if (absolutePath.endsWith(".class")) { // 判断是否为编译文件
  30. /**
  31. * 需要拿到的是编译文件,通过类加载器去获取
  32. * 需要将com\lyd\service\UserService转成com.lyd.service.UserService
  33. */
  34. String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
  35. className = className.replace("\\", ".");
  36. System.out.println("类名:" + className);
  37. try {
  38. // 1.6 通过全限定名使用类加载器获取类 (利用反射机制)
  39. Class<?> clazz = classLoader.loadClass(className);
  40. // 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean
  41. if (clazz.isAnnotationPresent(Component.class)) {
  42. // 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceof
  43. if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
  44. // 直接生成对象
  45. BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
  46. // 然后保存进去
  47. beanPostProcessorList.add(instance);
  48. }
  49. Component annotation = clazz.getAnnotation(Component.class);
  50. String beanName = annotation.value();
  51. // * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成
  52. if (beanName.equals("")) {
  53. // 默认开头小字母
  54. beanName = Introspector.decapitalize(clazz.getSimpleName());
  55. }
  56. /**
  57. * 這就是一个bean了
  58. * 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean
  59. */
  60. BeanDefinition beanDefinition = new BeanDefinition();
  61. beanDefinition.setType(clazz);
  62. if (clazz.isAnnotationPresent(Scope.class)) { // 判断是单例还是多例
  63. Scope scope = clazz.getAnnotation(Scope.class);
  64. beanDefinition.setScope(scope.value());
  65. } else {
  66. beanDefinition.setScope("singleton");
  67. }
  68. beanDefinitionMap.put(beanName, beanDefinition);
  69. }
  70. } catch (Exception e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. }
  75. }
  76. }
  77. // 2 创建单例bean对象
  78. for (String beanName : beanDefinitionMap.keySet()) {
  79. BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
  80. if (beanDefinition.getScope().equals("singleton")) { // 那么,如何保证单例呢?就需要有个单例池,singletonObjects
  81. Object bean = createBean(beanName, beanDefinition);
  82. singletonObjects.put(beanName, bean); // 将单例bean存到单例池中
  83. }
  84. }
  85. }
  86. // 创建bean
  87. private Object createBean(String beanName, BeanDefinition definition) {
  88. // 利用反射获取实例,采用无参构造方法
  89. Class clazz = definition.getType();
  90. // 通过无参构造方法获取实例
  91. try {
  92. Object instance = clazz.getConstructor().newInstance(); // 到这里直接返回,bean的对象也就创建完成
  93. // 3 依赖注入
  94. for (Field field : clazz.getDeclaredFields()) {
  95. // 判断字段上是否存在Autowried注解
  96. if (field.isAnnotationPresent(Autowired.class)) {
  97. /**
  98. * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
  99. * 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
  100. * 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;
  101. */
  102. field.setAccessible(true); // 反射需要设置这个,不然无法赋值
  103. // 用其属性名,这就意味着private RoleService roleService;roleService不能乱取
  104. field.set(instance, getBean(field.getName()));
  105. }
  106. }
  107. // * instanceof:是针对某个对象去判断是否实现某个类
  108. // 4 Aware回调机制
  109. if (instance instanceof BeanNameAware) {
  110. ((BeanNameAware)instance).setBeanName(beanName);
  111. }
  112. // 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorList
  113. for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
  114. instance = beanPostProcessor.postBeforeProcessor(beanName, instance);
  115. }
  116. // 5 初始化
  117. if (instance instanceof InitializingBean) {
  118. ((InitializingBean)instance).afterPropertiesSet();
  119. }
  120. // 6 BeanPostProcessor 初始化后 AOP
  121. for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
  122. instance = beanPostProcessor.postAfterProcessor(beanName, instance);
  123. }
  124. return instance;
  125. } catch (InstantiationException e) {
  126. e.printStackTrace();
  127. } catch (IllegalAccessException e) {
  128. e.printStackTrace();
  129. } catch (InvocationTargetException e) {
  130. e.printStackTrace();
  131. } catch (NoSuchMethodException e) {
  132. e.printStackTrace();
  133. }
  134. return null;
  135. }
  136. // 获取bean对象
  137. public Object getBean(String beanName) {
  138. BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
  139. if (beanDefinition == null) {
  140. throw new NullPointerException("找不到bean名字为[" + beanName + "]的bean对象");
  141. } else { // 找到就做相应的操作
  142. String scope = beanDefinition.getScope();
  143. if (scope.equals("singleton")) {
  144. // 通过单例池获取
  145. Object bean = singletonObjects.get(beanName);
  146. if (bean == null) {
  147. // 单例池中如果没有bean,就需要去创建
  148. bean = createBean(beanName, beanDefinition);
  149. singletonObjects.put(beanName, bean);
  150. }
  151. return bean;
  152. } else {
  153. // 多例的就不需要记录,每次都是通过创建
  154. return createBean(beanName, beanDefinition);
  155. }
  156. }
  157. }
  158. }

service

test

  1. public class Test {
  2. public static void main(String[] args) {
  3. LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class);
  4. // UserService userService = (UserService) applicationContext.getBean("userService");
  5. UserInterface userService = (UserInterface) applicationContext.getBean("userService");
  6. // System.out.println(applicationContext.getBean("userService"));
  7. // System.out.println(applicationContext.getBean("roleService"));
  8. userService.test();
  9. }
  10. }

创作不易,如有错误请指正,感谢观看!记得点赞哦!

【Spring系列】- 手写模拟Spring框架的更多相关文章

  1. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  2. 朱晔和你聊Spring系列S1E1:聊聊Spring家族的几大件

    朱晔和你聊Spring系列S1E1:聊聊Spring家族的几大件 [下载本文PDF进行阅读] Spring家族很庞大,从最早先出现的服务于企业级程序开发的Core.安全方面的Security.到后来的 ...

  3. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  4. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  5. 闭关修炼180天--手写持久层框架(mybatis简易版)

    闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...

  6. 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动

    上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...

  7. Spring源码 20 手写模拟源码

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  8. 【spring】-- 手写一个最简单的IOC框架

    1.什么是springIOC IOC就是把每一个bean(实体类)与bean(实体了)之间的关系交给第三方容器进行管理. 如果我们手写一个最最简单的IOC,最终效果是怎样呢? xml配置: <b ...

  9. 带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心

    注册中心代码使用 zookeeper 实现,我们通过图片来看看我们注册中心的架构. 首先说明, zookeeper 的实现思路和代码是参考架构探险这本书上的,另外在 github 和我前面配置文件中的 ...

随机推荐

  1. 1.7_CSS基础

    层叠样式表 (Cascading Style Sheets) CSS产生缘由 HTML 标签原本被设计为用于定义文档内容.通过使用 <h1>.<p>.<table> ...

  2. rh358 001 Linux网络与systemd设置

    358 rhel7 ce ansible 部署服务 dhcp nginx vanish haproxy 打印机服务 服务管理自动化 systemd与systemctl systemctl 来管理sys ...

  3. VS2017Enterprise版本安装ImageWatch 2017问题解决

    按照Andrei给的方法并不一定能成功. 方法如下: 1. Download the extension (ImageWatch.vsix) and open it using WinRAR 2. F ...

  4. 使用 Mypy 检查 30 万行 Python 代码,总结出 3 大痛点与 6 个技巧!

    作者:Charlie Marsh 译者:豌豆花下猫@Python猫 英文:Using Mypy in production at Spring (https://notes.crmarsh.com/u ...

  5. LIKE与等式查询比较

    我们知道 char 是定长类型的数据,如果数据长度小于定义的长度,会在字符串尾部加上空格.而对于空格的处理,对于等式匹配,或length等,会忽略空格.而对于like 或模式匹配,空格不能忽略. 一. ...

  6. Erda 开源的迷失和反思

    前言 Erda 是我从2018年初加入上家公司直到今年初离开的四年时间里一直在做的一个云原生 PaaS 平台.在开源之前,Erda 在公司内部的名字代号是 D ,在21年初改名为现在的 Erda 进行 ...

  7. MySQL5.7.15数据库配置主从服务器实现双机热备实例教程

    环境说明 程序在:Web服务器192.168.0.57上面 数据库在:MySQL服务器192.168.0.67上面 实现目的:增加一台MySQL备份服务器(192.168.0.68),做为MySQL服 ...

  8. Deployment必须包含资源对象

    Deployment 是一个控制器,能够用来控制 pod 数量跟期望数量一致,配置 pod 的发布方式 Deployment 会按照给定策略进行发布指定 pod,保证在更新过程中不可用数量在限定范围内 ...

  9. CentOS系统一键部署jdk,maven,tomcat,mysql

    #!/bin/bash ####使用方法############### # chmod a+x JdTomK-Auto.sh # source JdTomK-Auto.sh ############# ...

  10. Zookeeper QuickStart

    环境版本 操作系统:CentOS release 6.6 (Final) java版本: jdk1.8 zookeeper版本: zookeeper-3.4.11 一. 安装jdk 此处省略 二. 安 ...