Spring/Spring Boot重点知识整理

Why Spring
  • 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类,这些功能赋予了将其与其他技术结合使用的灵活性。例如,我们可以使用@Inject不是基于Spring的注解的注解来注入依赖项。
  • IOC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码,因为代码的修改可能意味着项目的重新构建和完整的回归测试。有了IoC容器,程序员再也不需要自己编写工厂、单例,这一点特别符合Spring的精神“不要重复的发明轮子”。
  • AOP(面向切面编程):将所有的横切关注功能封装到切面(aspect)中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。另一方面,有了AOP程序员可以省去很多自己写代理类的工作。
  • 事务管理:Spring以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
  • MVC:Spring的MVC框架是非常优秀的,为Web表示层提供了更好的解决方案。
  • 异常处理:Spring Framework提供了多个选项,以更好,更灵活的方式处理异常。
Spring IOC

IOC即Inversion of control,直译过来就是控制反转,它的意思是将对象,以及对象之间的引用关系,交给容器管理(The IOC container)。

在IOC出现以前,组件之间的协调关系是由程序内部代码来控制的,或者说,以前我们使用New object()这样子的语法实现两组件之间的依赖关系的。这种方式就造成了组件之间的互相耦合。IOC(控制反转)就是来解决这个问题的,它将实现组件间的关系从程序内部提到外部容器来管理(The IOC container)。也就是说,由容器在运行期将组件间的某种依赖关系动态的注入组件中。

Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

IOC是基于工厂模式加反射机制实现的。

Spring Bean作用域
作用域 含义
singleton 单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例,为Spring的默认值
prototype 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的Bean。
request 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。
session 该作用域将 bean 的定义限制为 HTTP 会话。在web应用中为每一个会话创建一个Bean实例。

通常可以这样子设置Bean的作用域

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

或者这样子

<bean id="xx" class="xx.xx.xx" scope="prototype" />
Bean的生命周期

在这里插入图片描述

  1. Spring对Bean进行实例化
  2. Spring将值和Bean的引用注入到Bean对应的属性中
  3. 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传给setBean-Name()方法
  4. 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将Bean所在的应用上下文的引用传入进来
  6. 如果Bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法
  7. 如果Bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似的,如果Bean使用了init-method生命了初始化方法,该方法也会被调用。
  8. 如果Bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法
  9. 至此,Bean已经准备就绪,可以被应用程序使用了
  10. 如果Bean实现了DisposableBean接口,Spring将调用它的destory()方法。同样,如果Bean使用了destory-method声明了销毁方法,该方法也会被调用。
Bean注入的三种方式
  • 基于XML的bean定义(需要提供setter方法)
public class Student {

	private String name;
	
	private Teacher teacher;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Teacher getTeacher() {
		return teacher;
	}

	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}
}
public class Teacher {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="student" class="com.demo.ioc.Bean.Student">
        <property name="name" value="张三"/>
        <property name="teacher" ref="teacher"/>
    </bean>
 
    <bean id="teacher" class="com.demo.ioc.Bean.Teacher">
        <property name="name" value="李四"/>
    </bean>
</beans>
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.demo.ioc.Bean.Student;
import com.demo.ioc.Bean.Teacher;

public class Main {

	public static void main(String args[]) throws Exception{
        FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean1.xml");
        Student student = (Student) context.getBean("student");
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println("学生的姓名:"+student.getName()+"。老师是"+student.getTeacher().getName());
        System.out.println("老师的姓名:"+teacher.getName());
    }
}
  • 基于注解的bean定义(不需要提供setter方法)
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("student")
public class Student {

	@Value("张三")
	private String name;
	
	@Resource
	private Teacher teacher;

	public String getName() {
		return name;
	}

//	public void setName(String name) {
//		this.name = name;
//	}

	public Teacher getTeacher() {
		return teacher;
	}

//	public void setTeacher(Teacher teacher) {
//		this.teacher = teacher;
//	}
}
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("teacher")
public class Teacher {

    @Value("李四")
	private String name;

	public String getName() {
		return name;
	}

//	public void setName(String name) {
//		this.name = name;
//	}	
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!--扫描组件的包目录-->
    <context:component-scan base-package="com.demo.ioc.Bean"/>
 
</beans>
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.demo.ioc.Bean.Student;
import com.demo.ioc.Bean.Teacher;

public class Main {

	public static void main(String args[]) throws Exception{
        FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean2.xml");
        Student student = (Student) context.getBean("student");
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println("学生的姓名:"+student.getName()+"。老师是"+student.getTeacher().getName());
        System.out.println("老师的姓名:"+teacher.getName());
    }
}
  • 基于JavaConfig定义(需要提供setter方法)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeansConfiguration {
 
    @Bean
    public Student student(){
        Student student=new Student();
        student.setName("张三");
        student.setTeacher(teacher());
        return student;
    }
 
    @Bean
    public Teacher teacher(){
        Teacher teacher=new Teacher();
        teacher.setName("李四");
        return teacher;
    }
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.demo.ioc.Bean.BeansConfiguration;
import com.demo.ioc.Bean.Student;
import com.demo.ioc.Bean.Teacher;

public class Main {

	public static void main(String args[]) throws Exception{
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeansConfiguration.class);
//        FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean2.xml");
        Student student = (Student) context.getBean("student");
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println("学生的姓名:"+student.getName()+"。老师是"+student.getTeacher().getName());
        System.out.println("老师的姓名:"+teacher.getName());
    }
}
Spring如何标记首选的Bean
	@Component
	@Primary
	public class Student {
		// todo...
	}
限定自动装配的Bean
	@Autowired
    @Qualifier("student")
    public void setName(String policyName) {
        this.policyName = policyName;
    }
Spring 自动装配Bean的方法
  • no:默认值,表示没有自动装配,应使用显式 bean 引用进行装配。
  • byName:它根据 bean 的名称注入对象依赖项。
  • byType:它根据类型注入对象依赖项。
  • 构造函数:通过构造函数来注入依赖项,需要设置大量的参数。
  • autodetect:容器首先通过构造函数使用 autowire 装配,如果不能,则通过 byType 自动装配。
BeanFactory和ApplicationContext的区别
  • BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
    • 继承MessageSource,因此支持国际化。
    • 统一的资源文件访问方式。
    • 提供在监听器中注册bean的事件。
    • 同时加载多个配置文件。
    • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
@Autowire如何实现注入Bean
  • 这是@Autowire注解的定义
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}
  • Java中注解的实现方式是通过反射来实现,反射的大概步骤如下:

    • 利用反射机制获取一个类的Class对象
    • 通过这个class对象可以去获取他的每一个方法method,或字段Field等等
    • Method,Field等类提供了类似于getAnnotation的方法来获取这个一个字段的所有注解
    • 拿到注解之后,我们可以判断这个注解是否是我们要实现的注解,如果是则实现注解逻辑
  • 所有的实现逻辑都在org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor,核心代码如下:

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		// 需要处理的目标类
		Class<?> targetClass = clazz;

		do {
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
			// 通过反射获取所有字段,通过findAutowiredAnnotation查找每个字段的注解,如果用autowired修饰则返回autowired相关属性
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				AnnotationAttributes ann = findAutowiredAnnotation(field);
				if (ann != null) {
					// 是否用在了static字段上
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isWarnEnabled()) {
							logger.warn("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
					// required属性是否赋值,默认true
					boolean required = determineRequiredStatus(ann);
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});
			// 同理通过反射获取所有方法并处理被autowired修饰的方法
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
				AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					// 是否用在static方法上
					if (Modifier.isStatic(method.getModifiers())) {
						if (logger.isWarnEnabled()) {
							logger.warn("Autowired annotation is not supported on static methods: " + method);
						}
						return;
					}
					if (method.getParameterCount() == 0) {
						if (logger.isWarnEnabled()) {
							logger.warn("Autowired annotation should only be used on methods with parameters: " +
									method);
						}
					}
					boolean required = determineRequiredStatus(ann);
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});
			// 用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理	
			elements.addAll(0, currElements);
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);
		// 返回所有包含autowire注解修饰的一个InjectionMetadata集合
		return new InjectionMetadata(clazz, elements);
	}

以上的代码就获取到了所有被autowire注解修饰的目标类和所有elements集合。
有了目标类,与所有需要注入的元素集合之后,我们就可以实现autowired的依赖注入逻辑了,实现的方法如下:

	@Override
	public PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
		// 这里是之前找到的目标类和所有elements集合组成的metadata
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			// 然后调用metadata的inject方法
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}

inject也使用了反射技术并且依然是分成字段和方法去处理的

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
				throws Throwable {
			// 如果autowired修饰的是属性,则赋值
			if (this.isField) {
				Field field = (Field) this.member;
				ReflectionUtils.makeAccessible(field);
				field.set(target, getResourceToInject(target, requestingBeanName));
			}
			else {
				if (checkPropertySkipping(pvs)) {
					return;
				}
				// 否则autowired修饰在方法上,则调用method.invoke
				try {
					Method method = (Method) this.member;
					ReflectionUtils.makeAccessible(method);
					method.invoke(target, getResourceToInject(target, requestingBeanName));
				}
				catch (InvocationTargetException ex) {
					throw ex.getTargetException();
				}
			}
		}

以上就是@Autowired注解实现的所有逻辑。

Spring中的循环依赖问题
  • 构造器的循环依赖

    <bean id="testA" class="com.bean.TestA">
        <constructor-arg index="0" ref="testB" />
    </bean>
    <bean id="testB" class="com.bean.TestB">
        <constructor-arg index="0" ref="testC" />
    </bean>
    <bean id="testC" class="com.bean.TestC">
        <constructor-arg index="0" ref="testA" />
    </bean>
    
    • 解决方法:spring无法解决这种问题,为何?只能抛出BeanCurrentlyInCreationException异常来终止该依赖。
    • 具体的处理步骤如下:
    1. Spring将每个正在创建的bean的标识符放在一个池子里
    2. 如果bean在创建的时候发现自己已经存在于这个池子里
    3. 则抛出异常,中断循环依赖。
  • 属性的循环依赖, setter循环依赖

    <bean id="testA" class="com.bean.TestA">
        <property name="testB" ref="testB" />
    </bean>
    <bean id="testB" class="com.bean.TestB">
        <property name="testC" ref="testC" />
    </bean>
    <bean id="testC" class="com.bean.TestC">
        <property name="testA" ref="testA" />
    </bean>
    
    • 解决方法:
    1. spring将已经完成了构造注入但是未完成setter注入的bean暴露出来
    2. 当进行setter注入时,发现这个需被注入的bean已经被暴露出来(存在该bean的引用),直接注入即可,而不需要再次加载所需的bean(无需再经历从头开始加载一个bean的过程,也就不会报错了)。
Spring AOP
  • Spring AOP是Spring框架中的重中之重,是基于JDK动态代理和CGLib动态代理实现的,至于动态代理的知识点可以参考我的这篇文章,Spring AOP默认采用JDK动态代理实现机制。

  • AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

  • 通俗来讲,在我们的应用中,诸如常见的日志、安全、异常处理和事物等逻辑都很重要,在不适用AOP的过去,我们需要将共同部分的这段代码写在一个独立的类独立的方法里,然后再去调用。但是有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

  • AOP的基本概念

    • 通知(Advice)
      通知定义了切面是什么以及什么时候使用。Spring切面可以应用五种类型的通知:
      • 前置通知(Before): 在目标方法被调用之前通知功能。
      • 后置通知(After):在目标方法被调用之后通知功能。
      • 返回通知(After-returning):在目标方法执行成功之后调用通知。
      • 异常通知(After-throwing):在目标方法抛出异常之后调用通知。
      • 环绕通知(Around):通知包含了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
    • 连接点(Join Point):在应用中执行过程中能够插入切面的一个点。
    • 切点(Pointcut):切点定义了匹配通知在何处织入。
    • 切面(Aspect):切面是通知和切点的集合。通知和切点共同定义了切面的全部内容–它是什么、在何时在何处完成功能
  • AOP切点表达式

AspectJ指示器 描述
arg() 限制连接点匹配参数由指定类型的执行方法
@arg() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型
@annotation 限制连接点匹配有指定注解的连接点
  • AOP使用场景
    • Authentication 权限
    • Caching 缓存
    • Context passing 内容传递
    • Error handling 错误处理
    • Lazy loading 懒加载
    • Debugging  调试
    • logging, tracing, profiling and monitoring 记录跟踪 优化 校准
    • Performance optimization 性能优化
    • Persistence  持久化
    • Resource pooling 资源池
    • Synchronization 同步
    • Transactions 事务
Spring事物

事务主要有四个特性,我们根据它的英文大写字母简写成:ACID

  • 原子性(Atomicity)

事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。所以你可以把一个事务看成一个原子操作。

  • 一致性(Consistency)

一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。比如在银行转账的例子中,无论转账成功与否,都需要保证银行的总存款是不变的,这才符合业务的“一致”状态。

  • 隔离性(Isolation)

可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。这里涉及到事务的几种隔离级别,将在下文详细介绍。比如多个人都像B转账,他们之间的事务互相不能影响。

  • 持久性(Durability)

一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器(如数据库)中。

  • 事物的隔离级别

    • ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
    • ISOLATIONREADUNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
    • ISOLATIONREADCOMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
    • ISOLATIONREPEATABLEREAD:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
    • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
    • 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
    • 不可重复读 :是指在一个事务内,多次读同一数据。
    • 幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
  • 更多详细内容请看我的和篇文章

Spring MVC有哪些组件
  • 前置控制器 DispatcherServlet。
  • 映射控制器 HandlerMapping。
  • 处理器 Controller。
  • 模型和视图 ModelAndView。
  • 视图解析器 ViewResolver。
Spring MVC的运行流程
  • 先将请求发送给 DispatcherServlet。
  • DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller。
  • DispatcherServlet 再把请求提交到对应的 Controller。
  • Controller 进行业务逻辑处理后,会返回一个ModelAndView。
  • Dispathcher 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。
  • 视图对象负责渲染返回给客户端。
Why Spring Boot
  • 自动配置
  • 上手简单,简化配置,使程序人员注重在业务开发上,节省配置的时间,提升开发效率
  • Spring Boot Stater提供了开箱即用的整合,配合Maven或者Gradle构建项目
  • 内嵌式Servlet容器(spring-boot-starter-web),默认是tomcat。如果想使用jetty,则需要从spring-boot-starter-web中将spring-bootstarter-tomcat排除,只留有spring-boot-starter-jetty
  • 无代码生成和 xml 配置
Spring Boot的核心配置文件

Spring Boot 核心的两个配置文件:

  • bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,且 boostrap 里面的属性不能被覆盖;
  • application (. yml 或者 . properties):用于 spring boot 项目的自动化配置。
Spring Boot是如何实现自动配置的

众所周知,Spring Boot程序中启动类上有一个@SpringBootApplication注解,包含了@SpringBootConfiguration(打开是@Configuration),@EnableAutoConfiguration,@ComponentScan注解。点进去看到如下:

@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 {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}
  • @SpringBootConfiguration,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
  • @ComponentScan,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
  • 可以看到有一个注解@EnableAutoConfiguration,这个注解就是开启了自动配置,借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,在点进去看看
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

里面最关键的是@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。接下来我们看一下AutoConfigurationImportSelector,核心代码如下:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 获取元数据
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 扫描具有META-INF/spring.factories文件的jar包
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		// 去重
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return StringUtils.toStringArray(configurations);
	}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		// 加载spring.factories
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), 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;
	}
	// 扫描具有MEAT-INF/spring.factories文件的jar包,得到所有的配置类
	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		// 自动配置器会跟根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
	// 调用loadSpringFactories
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }

至此实现了自动配置。

Spring Boot是如何启动的
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		// 通过SpringFactoriesLoader查找并加载所有的 SpringApplicationRunListeners,通过调用starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// 创建并配置当前应用将要使用的 Environment
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			// SpringBoot应用在启动时会输出一个大大的banner
			Banner printedBanner = printBanner(environment);
			// 创建不同的ApplicationContext容器
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 初始化ApplicationContext
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 调用ApplicationContext的refresh()方法
			refreshContext(context);
			// 空方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			// 由 SpringApplicationRunListener 来发出 started 消息,完成最终的程序启动
			listeners.started(context);
			// 查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			// 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

SpringApplicationRunListener的源码:

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log,
			Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}

	public void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

	public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

	public void contextPrepared(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextPrepared(context);
		}
	}

	public void contextLoaded(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextLoaded(context);
		}
	}

	public void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

	public void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		for (SpringApplicationRunListener listener : this.listeners) {
			callFailedListener(listener, context, exception);
		}
	}

	private void callFailedListener(SpringApplicationRunListener listener,
			ConfigurableApplicationContext context, Throwable exception) {
		try {
			listener.failed(context, exception);
		}
		catch (Throwable ex) {
			if (exception == null) {
				ReflectionUtils.rethrowRuntimeException(ex);
			}
			if (this.log.isDebugEnabled()) {
				this.log.error("Error handling failed", ex);
			}
			else {
				String message = ex.getMessage();
				message = (message != null) ? message : "no error message";
				this.log.warn("Error handling failed (" + message + ")");
			}
		}
	}

}
Spring Boot定义不同的环境配置
applcation.yml

application-dev.yml
 
application-test.yml
 
application-prod.yml

通过@Profile("dev")来切换不同的环境配置

Spring Boot如何实现跨域
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    // 设置允许跨域的路径
    registry.addMapping("/**")
        // 设置允许跨域请求的域名
        .allowedOrigins("*")
        // 是否允许证书 不再默认开启
        .allowCredentials(true)
        // 设置允许的方法
        .allowedMethods("*")
        // 跨域允许时间
        .maxAge(3600);
  }
}

Spring Boot实现多数据源配置

请参考这篇文章

发布了142 篇原创文章 · 获赞 89 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/wangchengming1/article/details/104375072
今日推荐