Spring

74 阅读17分钟

1.springboot自动装配原理

1、从@SpringBootApplication启动注解入手

  • 源码

**

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
//根据包路径扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
//直接根据class类扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

初看@SpringBootApplication有很多的注解组成,其实归纳就是一个"三体"结构,重要的只有三个Annotation:

@Configuration(@SpringBootConfiguration实质就是一个@Configuration)
@EnableAutoConfiguration
@ComponentScan

  • 也就是说我们在开发的时候,加上上面的上个注解会等同于加上@SpringBootApplication注解

(1)@Configuration注解

  • 这个注解实际上就是代表了一个配置类,相当于一个beans.xml文件,可以看我的另一篇文章:
    www.jianshu.com/p/81880251a…

(2)@ComponentScan

  • @ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中,也可以看我的另一篇文章:
    www.jianshu.com/p/a00d2b43e…

(3)@EnableAutoConfiguration

  • 在spring中有关于@Enablexxx的注解是开启某一项功能的注解,比如@EnableScheduling表示开启spring的定时任务。其原理是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到Ioc容器。其有关@Import注解的原理可有看:www.jianshu.com/p/a00d2b43e…
  • EnableAutoConfiguration代表开启springboot的自动装配

**

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//按类型排序不需要自动装配的类
    Class<?>[] exclude() default {};
//按名称排除不需要自动装配的类
    String[] excludeName() default {};
}

从源码中可以知道,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。同时借助于Spring框架原有的一个工具类:SpringFactoriesLoader,@EnableAutoConfiguration就可以实现智能的自动配置。

**

//从这里可以看出该类实现很多的xxxAware和DeferredImportSelector,所有的aware都优先于selectImports
//方法执行,也就是说selectImports方法最后执行,那么在它执行的时候所有需要的资源都已经获取到了
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
//1加载META-INF/spring-autoconfigure-metadata.properties文件
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//2获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration注解)
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3.在classpath下所有的META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,并将其封装到一个List中返回
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4.对上一步返回的List中的元素去重、排序
            configurations = this.removeDuplicates(configurations);
//5.依据第2步中获取的属性值排除一些特定的类
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//6对上一步中所得到的List进行过滤,过滤的依据是条件匹配。这里用到的过滤器是
//org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个ConditionOutcome[]
//数组。(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个
//class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties文件里)
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

...
}
  • SpringFactoriesLoader中加载配置,SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类

**

public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null)
            return result;
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
...

总结:@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。

  • 所以功能类能生效需要的条件:(1)spring.factories里面有这个类的配置类(一个配置类可以创建多个围绕该功能的依赖类)(2)pom.xml里面需要有对应的jar包

二、自动装配案例说明以Redis为例

1、从spring-boot-autoconfigure.jar/META-INF/spring.factories中获取redis的相关配置类全限定名(有120多个的配置类)RedisAutoConfiguration,一般一个功能配置类围绕该功能,负责管理创建多个相关的功能类,比如RedisAutoConfiguration负责:JedisConnectionFactory、RedisTemplate、StringRedisTemplate这3个功能类的创建

\

spring.factories中的redis配置类

\

2、RedisAutoConfiguration配置类生效的一个条件是在classpath路径下有RedisOperations类存在,因此springboot的自动装配机制会会去classpath下去查找对应的class文件。

**

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...}

3.如果pom.xml有对应的jar包,就能匹配到对应依赖class,

**

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

4、匹配成功,这个功能配置类才会生效,同时会注入默认的属性配置类@EnableConfigurationProperties(RedisProperties.class)

**

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
...

5.Redis功能配置里面会根据条件生成最终的JedisConnectionFactory、RedisTemplate,并提供了默认的配置形式@ConditionalOnMissingBean(name = "redisTemplate")

**

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
//用户没定义就使用默认的
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

6.最终创建好的默认装配类,会通过功能配置类里面的 @Bean注解,注入到IOC当中
7.用户使用,当用户在配置文件中自定义时候就会覆盖默认的配置@ConditionalOnMissingBean(name = "redisTemplate")

三、自动依赖过程总结

1.通过各种注解实现了类与类之间的依赖关系,容器在启动的时候Application.run,会调用EnableAutoConfigurationImportSelector.class的selectImports方法(其实是其父类的方法)--这里需要注意,调用这个方法之前发生了什么和是在哪里调用这个方法需要进一步的探讨

2.selectImports方法最终会调用SpringFactoriesLoader.loadFactoryNames方法来获取一个全面的常用BeanConfiguration列表

3.loadFactoryNames方法会读取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories),获取到所有的Spring相关的Bean的全限定名ClassName,大概120多个

4.selectImports方法继续调用filter(configurations, autoConfigurationMetadata);这个时候会根据这些BeanConfiguration里面的条件,来一一筛选,最关键的是
@ConditionalOnClass,这个条件注解会去classpath下查找,jar包里面是否有这个条件依赖类,所以必须有了相应的jar包,才有这些依赖类,才会生成IOC环境需要的一些默认配置Bean

5.最后把符合条件的BeanConfiguration注入默认的EnableConfigurationPropertie类里面的属性值,并且注入到IOC环境当中

2.springmvc工作流程

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

image.png

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。
  2. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。匹配controller
  3. 返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet
  4. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。匹配具体服务
  5. 执行handler找到具体的处理器
  6. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
  7. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
  8. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
  9. 视图解析器将解析的逻辑视图名传给DispatcherServlet。
  10. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行视图渲染
  11. 将响应数据返回给客户端

3.springmvc的九大组件

1.HandlerMapping匹配controller 根据request找到相应的处理器。因为Handler(Controller)有两种形式,一种是基于类的Handler,另一种是基于Method的Handler(也就是我们常用的)

2.HandlerAdapter匹配具体服务 调用Handler的适配器。如果把Handler(Controller)当做工具的话,那么HandlerAdapter就相当于干活的工人

3.HandlerExceptionResolver 对异常的处理

4.ViewResolver匹配相应的视图 用来将String类型的视图名和Locale解析为View类型的视图

5.RequestToViewNameTranslator 有的Handler(Controller)处理完后没有设置返回类型,比如是void方法,这是就需要从request中获取viewName

6.LocaleResolver 从request中解析出Locale。Locale表示一个区域,比如zh-cn,对不同的区域的用户,显示不同的结果,这就是i18n(SpringMVC中有具体的拦截器LocaleChangeInterceptor)

7.ThemeResolver 主题解析,这种类似于我们手机更换主题,不同的UI,css等

8.MultipartResolver 处理上传请求,将普通的request封装成MultipartHttpServletRequest

9.FlashMapManager 用于管理FlashMap,FlashMap用于在redirect重定向中传递参数

4.spring事务

多个事务方法相互调用时,事务如何在这些方法之间进行传播,spring中提供了7中不同的传播特性,来保证事务的正常执行:

REQUIRED:默认的传播特性,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入这个事务

SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,则以非事务的方式执行

MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常

REQUIRED_NEW:创建一个新事务,如果存在当前事务,则挂起改事务

NOT_SUPPORTED:以非事务方式执行,如果存在当前事务,则挂起当前事务

NEVER:不使用事务,如果当前事务存在,则抛出异常

NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样

NESTED和REQUIRED_NEW的区别:

REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务

NESTED和REQUIRED的区别:

REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚。

5.spring框架中单例bean是线程安全的吗

Spring中的Bean对象默认是单例的,框架并没有对bean进行多线程的封装处理

如果Bean是有状态的,那么就需要开发人员自己来保证线程安全的保证,最简单的办法就是改变bean的作用域把singleton改成prototype,这样每次请求bean对象就相当于是创建新的对象来保证线程的安全

有状态就是由数据存储的功能

无状态就是不会存储数据,你想一下,我们的controller,service和dao本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。

因此在进行使用的时候,不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用ThreadLocal把变量变成线程私有,如果bean的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized,lock,cas等这些实现线程同步的方法了。

6.spring框架中设计模式

1.工厂模式,在各种BeanFactory以及ApplicationContext创建中都用到了

2.模版模式,在各种BeanFactory以及ApplicationContext实现中也都用到了

3.代理模式,Spring AOP 利用了 AspectJ AOP实现的! AspectJ AOP 的底层用了动态代理

4.策略模式,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理

5.单例模式,比如在创建bean的时候。

6.观察者模式,spring中的ApplicationEvent,ApplicationListener,ApplicationEventPublisher

7.适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter

8.装饰者模式,源码中类型带Wrapper或者Decorator的都是

7.spring事务的实现方式原理是什么

在使用Spring框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用户自己通过代码来控制事务的处理逻辑,还有一种是声明式事务,通过@Transactional注解来实现。

其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,spring对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,有spring框架来帮助进行控制。

其实事务操作是AOP的一个核心体现,当一个方法添加@Transactional注解之后,spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关闭,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。

8.spring事务什么时候会失效

1、bean对象没有被spring容器管理

2、方法的访问修饰符不是public

3、自身调用问题

4、数据源没有配置事务管理器

5、数据库不支持事务

6、异常被捕获

7、异常类型错误或者配置错误

9.spring支持的bean作用域

① singleton

使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。

② prototype

使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。

③ request

该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。

④ session

该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。

⑤ global-session

该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。

10.Bean生命周期

image.png 1、实例化bean对象

通过反射的方式进行对象的创建,此时的创建只是在堆空间中申请空间,属性都是默认值

2、设置对象属性

给对象中的属性进行值的设置工作

3、检查Aware相关接口并设置相关依赖

如果对象中需要引用容器内部的对象,那么需要调用aware接口的子类方法来进行统一的设置

4、BeanPostProcessor的前置处理

对生成的bean对象进行前置的处理工作

5、检查是否是InitializingBean的子类来决定是否调用afterPropertiesSet方法

判断当前bean对象是否设置了InitializingBean接口,然后进行属性的设置等基本工作

6、检查是否配置有自定义的init-method方法

如果当前bean对象定义了初始化方法,那么在此处调用初始化方法

7、BeanPostProcessor后置处理

对生成的bean对象进行后置的处理工作

8、注册必要的Destruction相关回调接口

为了方便对象的销毁,在此处调用注销的回调接口,方便对象进行销毁操作

9、获取并使用bean对象

通过容器来获取对象并进行使用

10、是否实现DisposableBean接口

判断是否实现了DisposableBean接口,并调用具体的方法来进行对象的销毁工作

11、是否配置有自定义的destory方法

如果当前bean对象定义了销毁方法,那么在此处调用销毁方法

11.如何理解starter

使用spring+springmvc框架进行开发的时候,如果需要引入mybatis框架,那么需要在xml中定义需要的bean对象,这个过程很明显是很麻烦的,如果需要引入额外的其他组件,那么也需要进行复杂的配置,因此在springboot中引入了starter

starter就是一个jar包,写一个@Configuration的配置类,将这些bean定义在其中,然后再starter包的META-INF/spring.factories中写入配置类,那么springboot程序在启动的时候就会按照约定来加载该配置类

开发人员只需要将相应的starter包依赖进应用中,进行相关的属性配置,就可以进行代码开发,而不需要单独进行bean对象的配置

12.如何实现一个IOC容器

1、先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象

2、进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中

3、容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成对象的实例化工作

4、进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中

5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作

6、提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁

13.bean的自动装配

XML方式: bean的自动装配指的是bean的属性值在进行注入的时候通过某种特定的规则和方式去容器中查找,并设置到具体的对象属性中,主要有五种方式:

no – 缺省情况下,自动配置是通过“ref”属性手动设定,在项目中最常用 ​ byName – 根据属性名称自动装配。如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。 ​ byType – 按数据类型自动装配,如果bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。 ​ constructor – 在构造函数参数的byType方式。 ​ autodetect – 如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。
注解方式:@Autowired