SpringBoot原理学习

一、IoC/DI

1. 什么是IoC?

IoC控制反转是一种思想,即将对象创建和管理的权力交给IoC容器,用户在需要使用对象时直接向容器索取即可,容器会完成对象的注入,

这样做的好处就是用户不需要关注如何使用各种实现类的各种构造器来构造自己需要的对象,直接使用创建好的对象根据接口调用方法即可;

 

 

 

 

2. Bean

①什么是 Spring Bean?

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。

 

 

②将一个类声明为 Bean 的注解有哪些?

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面

 

 

③@Component 和 @Bean 的区别是什么?

  • @Component 注解作用于类,而@Bean注解作用于方法。
  • @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
  • @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现;

 

 

④Bean的属性有哪些?

懒加载:指是否在创建容器时就加载Bean到容器中,还是等使用到的时候再加载;

作用域:常用的就是singleton单例、prototype每次都重新创建,默认为单例;

 

 

⑤什么是循环依赖?

参考文章:Spring之循环依赖

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。

发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错;

此处不是循环调用,循环调用是方法之间的环调用,如下图:

 

 

⑥Bean 的作用域有哪些?——Spring 中的 bean 默认都是单例的

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean;

 

如何配置 bean 的作用域呢?

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

 

 

⑦Bean 是线程安全的吗?

Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。

我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。

  prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。

  singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式);

 

 

⑧Bean的生命周期

大致就是从创建到销毁的一系列操作;

 

 

 

 

3. 如何进行DI依赖注入?

①依赖注入的三种方式

Ⅰ. 属性注入

    /**
     * Field 注入/属性注入
     * */
//    @Resource(name = "mySQLDbServiceImpl")
//
    @Autowired
    @Qualifier(value = "mySQLDbServiceImpl")
    private DbService dbService;

@Autowired、@Resource均支持,使用简单代码简洁,是最常用的一种依赖注入方式;

容易出现空指针异常,属性注入允许构建对象实例时依赖的对象为空,导致空指针异常不能在启动时就爆出来,只能在用到它时才发现;

有发生循环依赖的隐患;

 

 Ⅱ. setter注入

    /**
     * setter注入
     * */
    private DbService dbService;

//    @Resource(name = "mySQLDbServiceImpl")

    @Autowired
    @Qualifier(value = "mySQLDbServiceImpl")
    public void setDbService(DbService dbService) {
        this.dbService = dbService;
    }

 @Autowired、@Resource均支持,缺点是setter方法可能会被多次调用,注入对象有被修改的风险,很少使用;

 

 Ⅲ. 构造器注入

    /**
     * 构造器注入
     * */
    private final DbService mySQLDbServiceImpl;

    public DbController(DbService mySQLDbServiceImpl) {
        this.mySQLDbServiceImpl = mySQLDbServiceImpl;
    }

@Autowired支持,@Resource不支持,不过实际上不需要@Autowired也可以完成,如上面的代码;

构造器注入方式可以注入final修饰的对象(前两种注入方式都不可以),可以确保注入对象不会被修改,

而且因为构造器方法在类加载阶段就会执行,所以依赖对象在使用前就会被初始化,可以确保需要的依赖不为空,

除此之外,使用构造器注入的方式,如果出现循环依赖,在启动项目时就会报错;

 

构造器注入是Spring官方推荐的依赖注入方式,配合lombok的@RequiredArgsConstructor使用可以省去手写构造器的步骤,代码也非常简洁;

 (注意:对于有多个实现类的接口,构造器注入是根据属性名来区分的,相当于byName,所以不要乱起属性名)

 

 

 ②@Autowired和@Resourece的区别

Ⅰ. @Autowired来自Spring框架,@Resource来自JDK;

Ⅱ. @Autowired支持属性、setter方法和构造器注入,@Resource只支持属性、setter方法;

Ⅲ. @Autowired默认采用byName的方式查找依赖,@Resource默认采用byType的方式查找依赖;

Ⅳ. 对于有多个实现类的接口的依赖注入,@Autowired可以通过@Qualifier注解的value属性来指定名称,@Resource可以通过其name属性来指定名称;

(@Autowired在构造器注入中,不允许通过@Qualifier注解的value属性来指定名称,必须显式地通过要注入的属性名来指定名称,虽然说构造器注入其实不是很有使用@Autowired的必要)

 

 

 

 

4. IOC原理完整解析

参考文章:Spring IOC 容器源码分析

SpringBoot源码阅读

以xml文件配置方式为例进行分析:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">

    <bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/>
</beans>
public class App {
    public static void main(String[] args) {
        // 用我们的配置文件来启动一个 ApplicationContext
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");

        System.out.println("context 启动成功");

        // 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式
        MessageService messageService = context.getBean(MessageService.class);
        // 这句将输出: hello world
        System.out.println(messageService.getMessage());
    }
}

 

 

在使用xml配置文件的路径作为参数创建ApplicationContext时,ClassPathXmlApplicationContext的构造器中调用了refresh()方法,

ClassPathXmlApplicationContext
 public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
  private Resource[] configResources;

  // 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
  public ClassPathXmlApplicationContext(ApplicationContext parent) {
    super(parent);
  }
  ...
  public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
      throws BeansException {

    super(parent);
    // 根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
    setConfigLocations(configLocations);
    if (refresh) {
      refresh(); // 核心方法
    }
  }
    ...
}

正是refresh()方法完成了容器的创建、Bean的加载和初始化:

refresh()
 @Override
public void refresh() throws BeansException, IllegalStateException {
   // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
      prepareRefresh();

      // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
      // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
      // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      // 这块待会会展开说
      prepareBeanFactory(beanFactory);

      try {
         // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
         // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】

         // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
         // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
         postProcessBeanFactory(beanFactory);
         // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
         // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
         registerBeanPostProcessors(beanFactory);

         // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
         initMessageSource();

         // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
         initApplicationEventMulticaster();

         // 从方法名就可以知道,典型的模板方法(钩子方法),
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();

         // 重点,重点,重点
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后,广播事件,ApplicationContext 初始化完成
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // 把异常往外抛
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

下面对refresh()方法中的几个主要步骤/方法进行分析:

 

①obtainFreshBeanFactory()——初始化 BeanFactory、加载和注册 Bean到这个BeanFactory中

obtainFreshBeanFactory()
 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   // 关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等等
   refreshBeanFactory();

   // 返回刚刚创建的 BeanFactory
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

 其核心方法是refreshBeanFactory(),在这个方法中又有两个主要方法:

  createBeanFactory():创建了一个DefaultListableBeanFactory的实例作为容器,这个容器中包含一个存放Bean id的ArrayList和一个以Bean id为key、BeanDefinition为value的ConcurrentHashMap;

  loadBeanDefinitions(beanFactory):从xml配置文件中解析加载各个Bean的信息,并以BeanDefiniton的形式保存,之后将Bean id和BeanDefinition存入到刚刚创建的容器的哈希表和集合中;

refreshBeanFactory()
@Override
protected final void refreshBeanFactory() throws BeansException {
   // 如果 ApplicationContext 中已经加载过 BeanFactory 了,销毁所有 Bean,关闭 BeanFactory
   // 注意,应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前
   // ApplicationContext 是否有 BeanFactory
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      // 初始化一个 DefaultListableBeanFactory,为什么用这个,我们马上说。
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      // 用于 BeanFactory 的序列化,我想不部分人应该都用不到
      beanFactory.setSerializationId(getId());

      // 下面这两个方法很重要,别跟丢了,具体细节之后说
      // 设置 BeanFactory 的两个配置属性:是否允许 Bean 覆盖、是否允许循环引用
      customizeBeanFactory(beanFactory);

      // 加载 Bean 到 BeanFactory 中
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}

 

补充——

BeanFactory:顾名思义,其实就是一个创建和管理Bean的工厂接口,其中包括通过各种参数获取Bean的getBean()方法、判断Bean类型的各种isXxx()方法等:

BeanFactory
public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";
    
	Object getBean(String name) throws BeansException;

	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

	boolean containsBean(String name);

	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

	String[] getAliases(String name);

}

,其实现类包括下列:

ApplicationContext也是Beanfactory的一个实现类,所以我们能用ApplicationContext的父类引用来调用getBean()方法获取Bean,

不过真正执行了创建、初始化Bean操作的是其内部持有的一个DefaultListableBeanFactory实例,可以看到正是上图右下角的一个类,

 

DefaultListableBeanFactory:包含了一个存放BeanDefinition对象的map集合beanDefinitionMap,key值为bean的名称,

还有一个List集合BeanDefinitionNames中,这个List集合存储了当前beanFactory中全部bean的名称,

当然它也是BeanFactory接口的实现类,接口的各种方法也实现了;

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
        
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

}

 

BeanDefinition:BeanDefinition是一个定义了存储bean标签中各属性值的接口,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等;

 

 

 

②finishBeanFactoryInitialization(beanFactory)——初始化容器中所有单例、非懒加载的Bean

finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)
 // 初始化剩余的 singleton beans
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {

   // 首先,初始化名字为 conversionService 的 Bean。本着送佛送到西的精神,我在附录中简单介绍了一下 ConversionService,因为这实在太实用了
   // 什么,看代码这里没有初始化 Bean 啊!
   // 注意了,初始化的动作包装在 beanFactory.getBean(...) 中,这里先不说细节,先往下看吧
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

   // Register a default embedded value resolver if no bean post-processor
   // (such as a PropertyPlaceholderConfigurer bean) registered any before:
   // at this point, primarily for resolution in annotation attribute values.
   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
         @Override
         public String resolveStringValue(String strVal) {
            return getEnvironment().resolvePlaceholders(strVal);
         }
      });
   }

   // 先初始化 LoadTimeWeaverAware 类型的 Bean
   // 之前也说过,这是 AspectJ 相关的内容,放心跳过吧
   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   // Stop using the temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(null);

   // 没什么别的目的,因为到这一步的时候,Spring 已经开始预初始化 singleton beans 了,
   // 肯定不希望这个时候还出现 bean 定义解析、加载、注册。
   beanFactory.freezeConfiguration();

   // 开始初始化
   beanFactory.preInstantiateSingletons();
}

其核心方法是preInstantiateSingletons(),这是一个DefaultListableBeanFactory中的方法:

preInstantiateSingletons()
 @Override
public void preInstantiateSingletons() throws BeansException {
   if (this.logger.isDebugEnabled()) {
      this.logger.debug("Pre-instantiating singletons in " + this);
   }
   // this.beanDefinitionNames 保存了所有的 beanNames
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

   // 下面这个循环,触发所有的非懒加载的 singleton beans 的初始化操作
   for (String beanName : beanNames) {

      // 合并父 Bean 中的配置,注意 <bean id="" class="" parent="" /> 中的 parent,用的不多吧,
      // 考虑到这可能会影响大家的理解,我在附录中解释了一下 "Bean 继承",不了解的请到附录中看一下
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

      // 非抽象、非懒加载的 singletons。如果配置了 'abstract = true',那是不需要初始化的
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         // 处理 FactoryBean(读者如果不熟悉 FactoryBean,请移步附录区了解)
         if (isFactoryBean(beanName)) {
            // FactoryBean 的话,在 beanName 前面加上 ‘&’ 符号。再调用 getBean,getBean 方法别急
            final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
            // 判断当前 FactoryBean 是否是 SmartFactoryBean 的实现,此处忽略,直接跳过
            boolean isEagerInit;
            if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
               isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                  @Override
                  public Boolean run() {
                     return ((SmartFactoryBean<?>) factory).isEagerInit();
                  }
               }, getAccessControlContext());
            }
            else {
               isEagerInit = (factory instanceof SmartFactoryBean &&
                     ((SmartFactoryBean<?>) factory).isEagerInit());
            }
            if (isEagerInit) {

               getBean(beanName);
            }
         }
         else {
            // 对于普通的 Bean,只要调用 getBean(beanName) 这个方法就可以进行初始化了
            getBean(beanName);
         }
      }
   }

   // 到这里说明所有的非懒加载的 singleton beans 已经完成了初始化
   // 如果我们定义的 bean 是实现了 SmartInitializingSingleton 接口的,那么在这里得到回调,忽略
   for (String beanName : beanNames) {
      Object singletonInstance = getSingleton(beanName);
      if (singletonInstance instanceof SmartInitializingSingleton) {
         final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
         if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
               @Override
               public Object run() {
                  smartSingleton.afterSingletonsInstantiated();
                  return null;
               }
            }, getAccessControlContext());
         }
         else {
            smartSingleton.afterSingletonsInstantiated();
         }
      }
   }
}

其核心方法是getBean(beanName),方法内部通过调用doGetBean()方法实现,这是抽象类AbstractBeanFactory(DefaultListableBeanFactory的父类)中的方法,

doGetBean()方法功能强大,对于单例Bean如果存在可以直接获取,不存在就创建然后返回,并且这个方法不光可以创建单例Bean,还可以创建ProtoType的Bean并返回;

具体如下:

getBean(String name)->doGetBean()
@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

// 我们在剖析初始化 Bean 的过程,但是 getBean 方法我们经常是用来从容器中获取 Bean 用的,注意切换思路,
// 已经初始化过了就从容器中直接返回,否则就先初始化再返回
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
   // 获取一个 “正统的” beanName,处理两种情况,一个是前面说的 FactoryBean(前面带 ‘&’),
   // 一个是别名问题,因为这个方法是 getBean,获取 Bean 用的,你要是传一个别名进来,是完全可以的
   final String beanName = transformedBeanName(name);

   // 注意跟着这个,这个是返回值
   Object bean; 

   // 检查下是不是已经创建过了
   Object sharedInstance = getSingleton(beanName);

   // 这里说下 args 呗,虽然看上去一点不重要。前面我们一路进来的时候都是 getBean(beanName),
   // 所以 args 传参其实是 null 的,但是如果 args 不为空的时候,那么意味着调用方不是希望获取 Bean,而是创建 Bean
   if (sharedInstance != null && args == null) {
      if (logger.isDebugEnabled()) {
         if (isSingletonCurrentlyInCreation(beanName)) {
            logger.debug("...");
         }
         else {
            logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
         }
      }
      // 下面这个方法:如果是普通 Bean 的话,直接返回 sharedInstance,
      // 如果是 FactoryBean 的话,返回它创建的那个实例对象
      // (FactoryBean 知识,读者若不清楚请移步附录)
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }

   else {
      if (isPrototypeCurrentlyInCreation(beanName)) {
         // 创建过了此 beanName 的 prototype 类型的 bean,那么抛异常,
         // 往往是因为陷入了循环引用
         throw new BeanCurrentlyInCreationException(beanName);
      }

      // 检查一下这个 BeanDefinition 在容器中是否存在
      BeanFactory parentBeanFactory = getParentBeanFactory();
      if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         // 如果当前容器不存在这个 BeanDefinition,试试父容器中有没有
         String nameToLookup = originalBeanName(name);
         if (args != null) {
            // 返回父容器的查询结果
            return (T) parentBeanFactory.getBean(nameToLookup, args);
         }
         else {
            // No args -> delegate to standard getBean method.
            return parentBeanFactory.getBean(nameToLookup, requiredType);
         }
      }

      if (!typeCheckOnly) {
         // typeCheckOnly 为 false,将当前 beanName 放入一个 alreadyCreated 的 Set 集合中。
         markBeanAsCreated(beanName);
      }

      /*
       * 稍稍总结一下:
       * 到这里的话,要准备创建 Bean 了,对于 singleton 的 Bean 来说,容器中还没创建过此 Bean;
       * 对于 prototype 的 Bean 来说,本来就是要创建一个新的 Bean。
       */
      try {
         final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
         checkMergedBeanDefinition(mbd, beanName, args);

         // 先初始化依赖的所有 Bean,这个很好理解。
         // 注意,这里的依赖指的是 depends-on 中定义的依赖
         String[] dependsOn = mbd.getDependsOn();
         if (dependsOn != null) {
            for (String dep : dependsOn) {
               // 检查是不是有循环依赖,这里的循环依赖和我们前面说的循环依赖又不一样,这里肯定是不允许出现的,不然要乱套了,读者想一下就知道了
               if (isDependent(beanName, dep)) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
               }
               // 注册一下依赖关系
               registerDependentBean(dep, beanName);
               // 先初始化被依赖项
               getBean(dep);
            }
         }

         // 如果是 singleton scope 的,创建 singleton 的实例
         if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
               @Override
               public Object getObject() throws BeansException {
                  try {
                     // 执行创建 Bean,详情后面再说
                     return createBean(beanName, mbd, args);
                  }
                  catch (BeansException ex) {
                     destroySingleton(beanName);
                     throw ex;
                  }
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }

         // 如果是 prototype scope 的,创建 prototype 的实例
         else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
               beforePrototypeCreation(beanName);
               // 执行创建 Bean
               prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
               afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
         }

         // 如果不是 singleton 和 prototype 的话,需要委托给相应的实现类来处理
         else {
            String scopeName = mbd.getScope();
            final Scope scope = this.scopes.get(scopeName);
            if (scope == null) {
               throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
            }
            try {
               Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                  @Override
                  public Object getObject() throws BeansException {
                     beforePrototypeCreation(beanName);
                     try {
                        // 执行创建 Bean
                        return createBean(beanName, mbd, args);
                     }
                     finally {
                        afterPrototypeCreation(beanName);
                     }
                  }
               });
               bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
            catch (IllegalStateException ex) {
               throw new BeanCreationException(beanName,
                     "Scope '" + scopeName + "' is not active for the current thread; consider " +
                     "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                     ex);
            }
         }
      }
      catch (BeansException ex) {
         cleanupAfterBeanCreationFailure(beanName);
         throw ex;
      }
   }

   // 最后,检查一下类型对不对,不对的话就抛异常,对的话就返回了
   if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
      try {
         return getTypeConverter().convertIfNecessary(bean, requiredType);
      }
      catch (TypeMismatchException ex) {
         if (logger.isDebugEnabled()) {
            logger.debug("Failed to convert bean '" + name + "' to required type '" +
                  ClassUtils.getQualifiedName(requiredType) + "'", ex);
         }
         throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
      }
   }
   return (T) bean;
}

不过在容器初始化阶段此方法只会对单例非懒加载的Bean进行初始化,实现这部分功能的核心方法是getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>),

这是DefaultSingletonBeanRegistry(也是DefaultListableBeanFactory的一个父类)中的一个方法,

这个方法先会从DefaultSingletonBeanRegistry中的以Bean id为key、以实例对象为value的ConcurretnHashMap类型的singletonObjects中,查询是否存在以此为id的对象,

有则直接返回,没有则使用createBean()方法创建一个对象并存入singletonObjects这个哈希表中;

源码如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

 

createBean()方法源码的解读暂且省略,大致知道其原理是通过反射来获取运行时类的构造器,进而使用构造器来创建对象;

 

 

 

 

 

 

 


 

二、AOP

1. 什么是AOP?

AOP面向切面编程就是将那些与业务代码无关,但却被业务模块共同调用的逻辑(比如日志管理、权限控制等封装起来),

这样做的好处是可以减少系统的重复代码,降低模块间的耦合度;

Spring AOP是基于动态代理实现的,对于有接口的对象,可以使用JDK Proxy来代理,对于没有接口的对象,可以使用Cglib来代理;

 

 

2. 实现AOP的方式有哪些?

①Spring AOP中JDK 动态代理和 CGLIB 动态代理对比

Ⅰ.实现原理:

JDK动态代理是jdk原生的实现方式,基于反射机制实现,在运行时通过实现目标接口的方式来代理目标类,因此要求目标类必须实现一个或多个接口,

而CGLIB动态代理是第三方CGLIB库提供的实现方式,在运行时通过继承目标类来代理目标类,因此要求目标类不能是被final修饰的类,或是有被final修饰的方法;

Ⅱ.性能表现:

JDK动态代理因为要实现目标类的接口,所以性能相对较低;

 

 

②AspectJ AOP——暂未使用过

AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了,

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多;

 

 

 

 

 

 


 

三、SpringBoot

1. 什么是starter?

 

 

 

 

2. SpringBoot支持哪些内嵌服务器?如何修改项目使用的服务器?

 

 

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除tomcat服务器-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--改用jetty服务器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

 

 

 

 

3. 启动类注解@SpringBootApplication

@Configuration:声明这是一个配置类,作用是可以在这个类里直接或间接地注册Bean到IoC容器中;

 

通过@Bean直接注册:

@Configuration
public class CacheConfig {
    @Bean
    public CacheManager caffeineCacheManager(){
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        List<CaffeineCache> caches = new ArrayList<>(CacheConsts.CacheEnum.values().length);
        for(var c : CacheConsts.CacheEnum.values()){
            if(c.isLocal()){
                Caffeine<Object, Object> caffeine = Caffeine.newBuilder().recordStats().maximumSize(c.getMaxSize());
                if(c.getTtl() > 0){
                    caffeine.expireAfterAccess(Duration.ofSeconds(c.getTtl()));
                }
                Cache<Object, Object> cache = caffeine.build();
                caches.add(new CaffeineCache(c.getName(), cache));
            }
        }
        simpleCacheManager.setCaches(caches);
        return simpleCacheManager;
    }
}

 

间接注册:

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    private final FileInterceptor fileInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 文件访问拦截
        registry.addInterceptor(fileInterceptor)
                        .addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "/**")
                                .order(1);

        // 权限认证拦截
        registry.addInterceptor(authInterceptor)
                // 拦截用户中心相关所有请求接口,使用此拦截器配置的验证策略进行认证
                .addPathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/**")
                // 放行登录注册相关请求接口,不需要验证
                .excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register",
                        ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login")
                .order(2);
    }
}

(具体怎么注册的不太清楚)

 

 

 

4. 配置文件

①有哪几种?

 

 

②yml配置文件如何读取配置文件?

date: 2024.1.1

my-profile:
  name: 汤志超
  email: 2365786826@qq.com

library:
  location: wuhan
  books:
    - name: 西游记
      description: 神魔小说
    - name: 水浒传
      description: 聚众斗殴
    - name: 三国演义
      description: 阴谋阳谋

Ⅰ. @Value("${propertyName}")——简单的可以用

这种方式比较简单,适合单个或少量读取配置文件,

缺点是只能注入字符串类,且无法自动进行类型检查与空值判断;

    @Value("${date}")
    public String date;

 

 

Ⅱ. @ConfigurationProperties(prefix = "xxx")——推荐使用

可以读取配置文件以“xxx”为前缀的全部字段,并根据名称将每个字段映射成对应的属性,最终将配置文件中以“xxx”为前缀的全部字段与一个类绑定;

例如:

@Data
@Component
@ConfigurationProperties(prefix = "library")
public class LibraryProperties {

    private String location;

    private List<Book> books;

    @Data
    static class Book{
        String name;
        String description;
    }
}

 

还可以结合@Validated注解,对属性进行格式校验,还可以对配置文件中不存在的字段赋默认值;

推荐hibernate-validator包提供的格式校验注解,需要下列依赖:

        <!-- 请求参数校验相关 -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>
@Data
@Component
@ConfigurationProperties(prefix = "my-profile")
@Validated
public class ProfileProperties {

    @NotEmpty
    private String name;

    @Email
    @NotEmpty
    private String email;

    // 配置文件中没有的就取默认值
    private Boolean handsome = Boolean.TRUE;

}

 

 

③不同位置的配置文件的优先级

项目目录下的config包下的配置文件优先级最高,

其次Resouces目录下config包中的配置文件,

最后是Resouces目录下的配置文件;

 

 

 

 

5. 参数校验

①验证JSON格式的请求体

使用@RequestBody将JSON格式的请求体映射为java类,在java类中进行格式校验;

注意:Controller类要加上@Validated注解,入参要加上@Valid注解;

@Data
@Validated
public class Person {

    @NotEmpty(message = "id不能为空")
    private String id;

    @Size(max = 10, message = "姓名长度不能超过10")
    @NotEmpty(message = "姓名不能为空")
    private String name;

    @Pattern(regexp = "^(man|woman)$", message = "性别只能是man或者woman")
    @NotEmpty(message = "性别不能为空")
    private String gender;

    @Email(message = "邮箱格式不正确")
    private String email;

}

 

@RestController
@RequiredArgsConstructor
@Validated
public class ParamController {

    private final ParamService paramService;

    @GetMapping("person")
    public void getPerson(@RequestBody @Valid Person person){
        System.out.println(paramService.getPerSon(person));
    }

}

 

 

②请求路径参数校验

用@Pathvariable或@RequestParam获取请求路径参数,使用各种格式校验的注解设置需要的格式要求即可;

注意:Controller类要加上@Validated注解,入参要加上@Valid注解;

@RestController
@RequiredArgsConstructor
@Validated
public class ParamController {

    @GetMapping("param/{name}")
    public void getParam(@Valid @PathVariable("name") @Size(max = 10, message = "姓名长度不能超过10") String name){
        System.out.println(name);
    }

}

 

 

 

 

 

 


 

posted @ 2023-12-31 10:58  Avava_Ava  阅读(13)  评论(0编辑  收藏  举报