概述

记录通过看源码的方式来解答Spring的常见问题

BeanFactory和FactoryBean区别

BeanFactory是ioc容器的接口,而FactoryBean是用于创建复杂bean的封装方式


通过实现FactoryBean接口,然后将实现类加入到Spring容器中,那么在创建对应的bean时,会通过FactoryBean的方式进行bean的创建(一般用于复杂bean的创建,通过该种方式,可以实现复杂bean创建的良好封装)

代码如下:

@Component
public class FactoryBeanTest implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        System.out.println("在这里能够实现复杂bean的创建,然后返回bean");
        return new B();
    }

    @Override
    public Class<?> getObjectType() {
        return B.class;
    }
}
public class B {
}

主启动类:

@SpringBootApplication
public class DemoApplication {
    @Autowired
    B b;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@Autowired和@Resource区别

  • @Autowired:首先会根据类型进行匹配,如果存在两个同类型的bean,则会根据属性的名称进行匹配,如果匹配不到,则报错
  • @Resource:首先会根据属性的名称(或者指定名称)进行匹配,如果没匹配到,则根据类型进行匹配,如果存在两个同类型的bean,则报错

@ConditionalOnXXX注解

核心内容:

  • 通过org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator#shouldSkip方法处理@Conditional注解,判断是否需要跳过beanDefinition的解析

处理@ConditionalOnXXX注解,判断是否需要跳过通过该方法 org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)进行判断,并确定当前的bean是否需要跳过


@ConditionalOnBean注解:(存在指定的bean才生效)当指定的的bean存在ioc容器中时,才使用当前的bean(如不指定,则默认为当前标注的bean的名称)

由org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchingBeans来处理

@ConditionalOnClass注解:(存在指定的class才生效)(一般标注在类上,一般和@Configuration结合使用)当项目的类路径下存在指定的类时,则不生效当前的配置类(一般用于制作自动配置依赖,导入对应的jar包即可完成自动配置)

@ConditionalOnProperty注解:用于判断在配置文件中是否配置了某个参数或者进一步可以判断配置参数的值,代码如下:

@ConditionalOnProperty(value = "server.port") //如果配置文件中配置了server.port参数,则生效
@Bean
public A a(){
    return new A();
}
@ConditionalOnProperty(value = "server.port",havingValue = "8082") //如果配置文件中配置了server.port=8082,才生效
@Bean
public A a(){
    return new A();
}

@ConditionalOnMissingBean:(不存在指定的bean才生效)

@ConditionalOnMissingClass:(不存在指定的class才生效)

@Configuration是什么时候被处理的

在处理@Configuratio配置类时,是不支持排序的,完全就是按照扫描的顺序进行和文件名的自然排序进行

最优先的就是当前类路径下的配置类,然后是根据该配置类导入的配置类或者其他bean(在此基础上再按照文件名排序)

核心内容:

  • 通过org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法处理@Configuration配置类
    • org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中处理配置类中的注解信息,包含了以下注解:@Component@PropertySources@ComponentScan@Import@ImportResource@Bean

在方法 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan的时候,传入指定的basePackages就可以对指定的class文件进行扫描,并将其解析成beanDefinition,在解析的过程中还会处理其中的一系列注解,如 @Primary@Lazy@DependsOn@Description等,并且在此过程中,还会判断该bean是否需要被代理,如果需要,则会创建一个代理相关的beanDefinition,用于后续的代理创建

所有的beanDefinition什么时候全部扫描完成

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors方法中,将会扫描所有的@Configuration配置类,然后将所有的bean都解析成beanDefinition保存在beanDefinitions集合中

**重点:**实现 BeanDefinitionRegistryPostProcessor接口或者 BeanDefinitionRegistryPostProcessor接口之后,重写 postProcessBeanDefinitionRegistry方法,在该方法中就可以获取到所有原始未被处理的beanDefinition

@Bean是什么时候被处理的

在处理 @Configuration注解类的时候,会遍历配置类的所有方法,如果是存在 @bean注解的方法(beanMethods),则将他们的bean定义加载到beanDefinitions集合中,便于之后在ioc容器中创建bean

具体的处理方法在 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod

在该方法中,会通过@ConditionalOnXXX注解进行判断是否需要跳过将bean解析成beanDefinition到beanDefinitions集合中

**注意:**这个 @Bean的解析成beanDefinition的顺序是按照 @Configuration配置类的解析顺序解析的,因此需要避免在解析beanDefinition冲突,而在注入时就不会冲突,因为Spring会根据对应的注解(例如@Primary、@Priority等)进行确定到底注入哪一个bean

@Primary注解如何生效

Spring在扫描配置类的时候,会将标注有@Primary注解的@bean方法生成的对应的beanDefinition做上标记,将Primary的标志为置为true,那么在自动注入的时候,就会优先将Primary标志位为true的bean优先注入,达到覆盖的目的

需要注意的是:如果存在两个同名的bean,Spring在扫描的时候就会报错的,因为这个是不允许的,并不会因为标注了@Primary注解,就会将另外一个名称相同的bean不添加到ioc容器中

org.springframework.beans.factory.support.DefaultListableBeanFactory#determineAutowireCandidate方法中,会将标注有@Primary注解的bean优先返回

@Autowired自动注入如何寻找对应的属性进行注入

通过 org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates方法来寻找可以所有注入的属性(这里寻找的符合条件的bean都是根据类型来匹配的,即先匹配类型)

先根据类型查找,当可以注入的属性选项大于2个时,会通过 org.springframework.beans.factory.support.DefaultListableBeanFactory#determineAutowireCandidate方法来确定需要注入的对象

最高优先级是标注有@Primary注解的bean

再是标注有@Priority注解的bean

再根据bean的名称


总结:

在使用@Autowired注解时自动注入匹配的优先顺序为:

  • 先通过类型寻找符合条件的bean
    • 如果只找到一个,则直接注入
    • 否则,如果符合条件的bean的个数大于2
      • 优先注入标注有@Primary注解的bean,返回
      • 再优先注入标注有@Priority注解的bean,返回
      • 再根据bean的名称注入

@PostConstruct在什么时候执行

在调用 org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization方法的时候执行

在此之后,会调用 InitializingBean接口的方法

@Profile注解的作用

  • 作为直接或间接地与注释的任何类型的注解@Component ,包括@Configuration类
  • 作为元注解,用于编写定制的典型化注解的目的
  • 为对任何一个方法级注释@Bean方法

使用方式如下:

配置文件设置环境:

spring.profiles.active=dev

代码:

@Profile("dev") // 可以根据配置文件的环境使配置类生效,如果当前处于dev环境,则该配置类生效
@Configuration
public class ConfigurationTest {
}

SpringMVC中的DispatchServlet

核心代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  ...

  ModelAndView mv = null;
  Exception dispatchException = null;

  try {
    ...

      // 确定当前请求的处理程序。
      mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) { // 异常处理
      noHandlerFound(processedRequest, response);
      return;
    }

    // 确定当前请求的处理程序适配器。
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 如果处理程序支持,则处理最后修改的标头。
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
      long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
      if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
        return;
      }
    }

    // 调用拦截器的preHandle方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
      return;
    }

    // 执行真正的handler方法
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    ...

      // 应用默认的viewName
      applyDefaultViewName(processedRequest, mv);
    // 调用拦截器的postHandle方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
  }
  catch (Exception ex) {
    ...
  }

  // 处理调度结果(根据不同的返回结果进行处理,例如文本或视图),在此期间会调用拦截器的afterCompletion方法
  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

SpringMVC中的url是什么时候映射的

通过 RequestMappingHandlerMapping类实现映射的:

  • 该类实现了InitializingBean接口,Spring在初始化该类的时候,就会调用他的afterPropertiesSet方法,在该方法中会进行url的映射

    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods方法中,会循环遍历ioc中的所有bean,找出标注有 @Controller或者 @RequstMapping的bean,然后通过 org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods方法,然后将他们的url和方法进行映射即可

Spring的循环依赖问题

代码:

@Component
class AAA{
    BBB bbb;

    @Autowired
    public void setBbb(BBB bbb) {
        this.bbb = bbb;
    }
}

@Component
class BBB{
    AAA aaa;

    @Autowired
    public void setAaa(AAA aaa) {
        this.aaa = aaa;
    }
}

Spring会按照扫描bean的顺序进行bean的实例化和初始化,假设AAA比BBB先实例化

  • 进行AAA的实例化,调用getBean("AAA");进行获取AAA,并标记AAA正在创建
    • 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法对AAA进行实例化,调用AAA的构造方法
    • 然后在调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法对AAA进行属性注入
    • 在调用org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency方法解决依赖问题时,发现AAA依赖BBB,此时就需要对BBB进行实例化
    • =======================================
    • 调用beanFactory.getBean("BBB");进行获取BBB,并标记BBB正在创建
      • 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法对BBB进行实例化,调用BBB的构造方法
      • 然后在调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法对BBB进行属性注入,因为BBB依赖AAA,又因为AAA标记了正在创建,并且AAA实例已经创建好了,所以就可以直接注入
      • 接下来将BBB的生命周期执行完成,返回BBB实例
    • ========================================
    • 将返回的BBB实例继续进行属性注入,将其注入到AAA中
    • 接下来将AAA的生命周期执行完成

@Transactional注解的代理是在什么时候,在哪里创建的

在创建标注有@Transactional注解的类时:

  • 先调用类的构造方法实例化对象
  • 然后进行属性的自动注入
  • **重点:**在执行AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)方法进行初始化时,调用InfrastructureAdvisorAutoProxyCreator后置处理器的postProcessAfterInitialization方法返回一个通过cglib代理后的对象

代码如下:

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      // 在这一行创建了代理类
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}

在wrapIfNecessary方法中,使用了createProxy方法创建代理类(该方法用于创建bean的AOP代理)

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy

在createProxy方法中,一直往下调用,会调用到createAopProxy方法

org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy

代码如下:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    Class<?> targetClass = config.getTargetClass();
    if (targetClass == null) {
      throw new AopConfigException("TargetSource cannot determine target class: " +
                                   "Either an interface or a target is required for proxy creation.");
    }
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
      return new JdkDynamicAopProxy(config);
    }
    return new ObjenesisCglibAopProxy(config);
  }
  else {
    return new JdkDynamicAopProxy(config);
  }
}

一般情况下,指定proxyTargetClass执行CGLIB代理,或者指定一个或多个接口使用一个JDK动态代理

如果使用@Transactional注解,则proxyTargetClass就是标注的类本身,则使用cglib代理

AspectJ切面是什么时候被代理的

在扫描完beanDefinition后,通过 ConfigurationClassPostProcessor后置处理器的 postProcessBeanFactory方法对需要的配置类和需要切面的类做cglib增强

  • org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
    • org.springframework.context.annotation.ConfigurationClassPostProcessor#enhanceConfigurationClasses
      • org.springframework.context.annotation.ConfigurationClassEnhancer#enhance

Mybatis是如何与Spring做结合的

通过 MapperScannerConfigurer类,实现了 BeanDefinitionRegistryPostProcessor接口,在Spring启动时,会调用接口的 postProcessBeanDefinitionRegistry方法,其中会调用 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan方法对mapper接口进行扫描,然后创建对应的beadDefinition放到beanDefinitions集合中,代码如下:

public int scan(String... basePackages) {
  int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

  // 对指定包路径做扫描,扫描mapper接口,并对接口的beanDefinition包装成MapperFactoryBean,加到Spring中,便于后面创建jdk代理
  doScan(basePackages);

  ...
}

MapperFactoryBean实现了FactoryBean接口,在该类中提供了getObject()方法用于获取对应mapper的代理对象

org.mybatis.spring.mapper.MapperFactoryBean#getObject

当需要自动注入属性时,才会创建对应的mapper的代理对象,会调用对应mapper(MapperFactoryBean)的getObject()方法获取代理对象,代码如下:

protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

最终会使用jdk的动态代理创建代理对象

这也说明了为什么mapper必须是一个接口,因为只有名称为接口之后才可以使用jdk动态代理

什么时候将代码中的${...}占位符替换成配置文件中的配置

通过 PropertySourcesPlaceholderConfigurer类的 postProcessBeanFactory方法将代码中的${...}占位符通过配置替换掉

org.springframework.context.support.PropertySourcesPlaceholderConfigurer#postProcessBeanFactory

如何通过jdk代理创建动态代理

实现 InvocationHandler接口,重写invoke方法,代码如下:

public class JDKProxyTest implements InvocationHandler {
    // 需要代理的对象
    Object obj;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("jdkProxy-before");
        Object invoke = method.invoke(obj, args);
        System.out.println("jdkProxy-after");
        return invoke;
    }

    // 给传入的对象创建代理对象
    public Object createProxyInstance(Object obj){
        this.obj=obj;
        /**
         * 第一个参数设置代码使用的类装载器,一般采用跟目标相同的类装载器
         * 第二个参数设置代理类实现的接口,所以要求代理的目标对象必须实现一个类
         * 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定对象的invoke方法
         */
        return Proxy.newProxyInstance(this.obj.getClass().getClassLoader(),this.obj.getClass().getInterfaces(), this);
    }

    public static void main(String[] args) {
        Object obj= new JDKProxyTest().createProxyInstance(new JDKProxyTestDemo());
        ProxyInterface proxy = (ProxyInterface) obj;
        proxy.say();
    }
}
//======================================
interface ProxyInterface{
    void say();
}

class JDKProxyTestDemo implements ProxyInterface{
    @Override
    public void say() {
        System.out.println("hello");
    }
}

Spring中的注解问题

组合注解

被注解的注解称为组合注解。

好处:

  • 简单化注解配置,用更少的注解来标注特定含义的多个元注解
  • 提供了很好的扩展性,可以根据实际需要灵活的自定义注解。

注解继承

@Inhberited注解可以让指定的注解在某个类上使用后,这个类的子类也将自动被该注解标记。

测试代码如下:

// @Hello注解定义
@Retention(RetentionPolicy.RUNTIME)
@Inherited //能够让子类也能被当前的Hello注解标记
@interface Hello {
}

@Hello
abstract class Base{
  @Hello
  public void say1(){} //直接继承普通方法
  @Hello
  public abstract void say2(); // 抽象方法
  @Hello
  public void say3(){} //覆盖普通方法
}

class Derived extends Base {
  // 直接继承普通方法say1

  // 实现抽象方法say2
  @Override
  public void say2() {}

  // 覆盖普通方法say3
  @Override
  public void say3() {}
}

public class AnnotationTest {
  public static void main(String[] args) throws Exception {
    System.out.println(Derived.class.getAnnotation(Hello.class));
    System.out.println(Derived.class.getMethod("say1").getAnnotation(Hello.class));
    System.out.println(Derived.class.getMethod("say2").getAnnotation(Hello.class));
    System.out.println(Derived.class.getMethod("say3").getAnnotation(Hello.class));
  }
}

运行结果为:

@com.example.demo.test.Hello() @com.example.demo.test.Hello() null null

需要注意:Base类中的三个方法必须都是public的,不然子类不能够通过反射拿到对应的方法

总结:

@Hello未标注@Inhberited注解 @Hello标注了@Inhberited注解
直接继承父类,类能够继承到对应的注解? 不能
直接继承父类方法,方法能够继承到对应的注解?
实现父类的抽象方法,方法能够继承到对应的注解? 不能 不能
覆盖父类方法,方法能够继承到对应的注解? 不能 不能
  • 通过在元注解中标注@Inhberited注解
    • 能够使得正常的继承类和方法都能继承到对应的注解
    • 但是如果字类实现抽象或者覆盖父类方法,都不能继承到对应的注解
  • 在元注解中没有标注@Inhberited注解
    • 只能通过继承父类普通方法,才能继承到对应的注解
    • 其他的方式都不行

因此,@Inhberited注解起到的作用就是控制类上的注解是否能够继承,如果标注了,则可以继承,否则,不能继承。

正常的方法继承都能够继承到对应的注解,其他的方式都不行。

Spring中的事务问题

Spring事务

@ConfigurationProperties的使用

在配置文件中添加以下配置:

xxx.a=1

创建配置类

@Component
@ConfigurationProperties("xxx") // 设置前缀
public class ConfigurationPropertySourceTest {
  String a; // 成员名称需要和配置参数后缀一致

  // 必须要提供setter方法
  public void setA(String a) {
    this.a = a;
  }

  @PostConstruct
  public void test(){
    System.out.println(a);// 结果为1
  }
}

@ConfigurationProperties一般都用于自动配置类:

创建自动配置类:

@Configuration
@EnableConfigurationProperties(ConfigurationPropertySourceTest.class) // 使得参数配置类生效
public class AutoConfigurationTest{}

@ConfigurationProperties("xxx") // 设置前缀
class ConfigurationPropertySourceTest {
  String a; // 成员名称需要和配置参数后缀一致

  // 必须要提供setter方法
  public void setA(String a) {
    this.a = a;
  }

  @PostConstruct
  public void test(){
    System.out.println(a);// 结果为1
  }
}
0条评论
头像
ICP证 : 浙ICP备18021271号