博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring | SpringBoot 理论总结
阅读量:2419 次
发布时间:2019-05-10

本文共 16136 字,大约阅读时间需要 53 分钟。

目录

 

spring的体系结构

在这里插入图片描述

1、Test 测试模块:提供对测试的支持
 

2、Core Container 核心容器模块:spring的核心部分,分为4个小模块

  • Beans:提供BeanFactory,用于创建bean
  • Core:提供IoC、DI功能
  • Context:此模块建立在Beans、Core模块的基础上,用来访问对象的定义、配置,以及整合其它框架时提供对第三方库的支持
  • SpEL:提供对Spring EL(表达式语言)的支持
     

3、AOP模块:提供对面向切面编程(AOP)的支持。spring有2种方式实现aop,一种是spring在早期版本中提供的spring aop,一种是spring在后续版本中集成的aspectj。

 

4、Data Access/Integration 数据访问/集成模块:提供对jdbc、orm映射框架、xml解析、消息队列、事务管理的支持。

 

5、Web模块:提供符对web应用的支持。

 

spring的优点

  • 非侵入式设计,解耦
  • 支持AOP
  • 强大的整合能力,可以和很多优秀框架无缝整合

 

spring的2大特性

  • IoC  控制反转
  • AOP  面向切面编程
     

IoC

IoC的原理

  • IoC(控制反转)是一种编程思想,由容器统一创建创建、管理bean。
  • IoC常见的实现方式有DI(依赖注入)、DL(依赖查找),DI是IoC容器主动给bean注入所需依赖,DL是bean主动到IoC容器中寻找所需的依赖,DL是EJB时代的方式,现在的主流方式是DI。

IoC只是一种编程思想,所有实现了IoC的容器都叫做IoC容器,Spring容器只是IoC的其中一种实现,Spring容器≠IoC容器,只是IoC容器中的一种。

 

IoC容器的优势

  • 避免在各处使用new来创建实例,可以统一管理、维护类的实例;
  • 创建实例时不需要了解其中的具体细节

在这里插入图片描述

 

IoC容器初始化的过程

在这里插入图片描述

  • 读取配置文件生成对应的Resource对象
  • 将Resource对象解析为BeanDefinition对象
  • 根据BeanDefinition对象生成对应的Bean,注册到IoC容器中

 

IoC容器提供的功能

  • 依赖检查、依赖注入
  • 自动装配
  • 可以设置bean的初始化方法、销毁方法、回调方法

 

IoC容器的核心接口

  • BeanFactory:BeanFactory是spring最核心的接口,用于建立bean之间的依赖关系、提供IoC的配置机制、管理bean的生命周期。
  • ApplicationContext:ApplicationContext继承了多个接口,提供了处理bean的能力,比如继承了ResourcePatternResolver接口,可以加载解析资源文件;继承了ApplicationEventPublisher接口,可以监听、发布事件;继承了多种类型的BeanFactory,可以管理、装配bean。

这2个接口都有众多的子接口、实现类。

二者的区别:BeanFactory提供spring框架的基础功能,主要在spring框架内部使用,面向spring框架本身;ApplicationContext在BeanFactory的基础上封装了更多的基础功能,主要提供给spring的使用者调用,面向spring的使用者。

 

AOP

  • aop的概念:面向切面编程,把业务代码、切面代码分开,使架构变得低耦合、高内聚。
  • spring aop的实现原理:aop是使用jdk动态代理、cglib动态代理实现的,如果目标类实现了接口,默认使用jdk动态代理(jdk动态代理代理的是接口);如果目标类没有实现接口,则使用cglib代理。

 

aop中的主要名词

  • aspect:通用功能的代码实现
  • target:被织入aspect的对象
  • join point 连接点:目标类中的所有方法都是连接点
  • point cut 切入点:目标类中被增强的方法
  • Weaving 织入:把增强应用到目标对象创建代理对象的过程。spring aop使用动态代理织入,aspectj在编译期、类加载期织入。

 

aop的三种织入时机|方式

  • 编译时织入
  • 类加载时织入:这2种都需要特殊的java编译器支持,比如aspectj
  • 运行时织入:在运行时通过动态代理生成对应的代理对象,借助代理对象进行操作。spring采用的即此种方式,可以指定要使用哪种动态代理,spring默认使用jdk动态代理实现aop,如果不能使用jdk动态代理生成代理对象(不满足jdk动态代理的使用要求),则使用cglib代理。

 

advice(通知|增强的种类)

  • Beafore 前置通知
  • AfterReturning 后置通知
  • AfterThrowing 异常通知
  • After 最终通知
  • Around 环绕通知

 

bean

bean的生命周期

1、检查所依赖的bean是否已实例化,如果未实例化,则先实例化所依赖的bean;

  如果依赖的bean都已实例化,则调用bean的构造方法或工厂方法创建bean的实例

2、注入所需依赖

3、如果bean实现了一些接口,则执行一些相应的方法

4、如果使用了aop,则包装实例进行增强

5、容器前处理

至此,实例创建完成,可以正常使用了

6、如果作用域是singleton,则放到Spring IoC缓存池中,触发spring容器的生命周期管理;如果指作用域是prototype,交给调用者,由调用者管理。

7、销毁实例或spring容器关闭时进行容器后处理(singleton)

 

bean的后置处理器BeanPostProcessor

bean的后置处理器BeanPostProcessor可以对bean做二次处理

/** * 1、实现 BeanPostProcessor 接口,重写2个方法。处理对象的是所有的bean,可根据beanName进行过滤。 * 2、如果bean二次处理的类有多个,还需要实现 Ordered 接口,指定这多个类执行的先后顺序 * 3、需要放到spring容器中 */@Componentpublic class Xxx implements BeanPostProcessor, Ordered {
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//... return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//... return bean; } @Override public int getOrder() {
return 0; }}

执行顺序依次为

  • postProcessBeforeInitialization()
  • initMethod指定的初始化方法
  • postProcessAfterInitialization()

在这里插入图片描述

 

bean常见的5种作用域

作用域 描述 优点 缺点
singleton 单例 (默认) 该类在spring容器中只有一个实例,无论引用、获取多少次,使用的都是同一个对象。spring容器启动时就创建实例,由spring容器管理bean的生命周期,实例的创建、销毁均由spring容器负责。 不用重新生成实例,减少了新生成实例的资源开销,降低了GC频率,可以快速获取到bean。 不是线程安全的,多线程下访问同一个单例的bean,如果该bean存在描述状态的字段,可能出现并发问题。
prototype 原型 每次获取该类的实例时,都会重新创建一个实例。需要时(从容器中获取该类的实例时)才创建,由spring容器负责创建,创建好之后交给调用者,由调用者负责管理。 是线程安全的,多线程下操作的是不同的实例,不会导致线程安全问题。 频繁创建bean会加大内存占用,增加GC频率,慎用。
request 在web项目中使用。在一次http请求中,获取的是同一个实例,该实例只在此次请求中有效。新的http请求,会创建新的实例。
session 在web项目中使用。在一次会话中获取的是同一个实例,只在本次session中有效。新的session,会创建新的实例。
globalSession 在web项目中使用,会为每个全局的Http Session创建一个Bean实例,此作用域仅对Portlet有效
@Component@Scope("prototype")  //指定作用域,缺省默认为singletonpublic class Xxx {
}

 

标注bean的注解

  • @Controller
  • @RestController:这个是spring-web的注解,等于@Controller+@ResponseBody
  • @Service
  • @Repository
  • @Component
  • @Bean:标在方法上,会把方法的返回值作为bean放到IOC容器中

都可以用value属性指定bean的名称

//可指定bean的name,缺省一般默认为类名的camel写法,类名前2个字母都是大写时除外@Controller("userController")//可以指定bean的初始化、销毁方法,相当于
标签。name属性的别名是value@Bean(name = "xxx", initMethod = "init", destroyMethod = "destroy")

 

组件扫描

组件扫描:自动扫描指定包下标注bean的注解。

  • spring需要手动开启组件扫描才会解析识别标注bean的注解
  • springboot的@SpringBootApplication已经包含了@ComponentScan,但默认只扫描引导类的所在包,如果引导类位置比较特殊、不在根包下,可在引导类上用@ComponentScan指定要扫描的包
//值是string[]@ComponentScan(basePackages = {
"com.xxx.xxx1", "com.xxx.xxx2"})//注解特性:值是只有一个元素的数组时,数组可以直接写成元素形式@ComponentScan(basePackages = "com.xxx")//basePackages的别名是value,注解特性:只设置value属性时,可以直接写值@ComponentScan("com.xxx")

 

自动装配

自动装配:IoC容器自动注入bean所需的其它bean。

自动装配的2种方式

  • byName:根据bean的name进行装配
  • byType:根据bean的type进行装配
     

自动装配的贪婪原则:自动装配会尽可能多的注入依赖(数据),setter注入方式会注入尽可能多的值,构造器注入方式会优先使用参数个数多的构造器。

 

如果自动装配时报错找不到对应的bean,常见原因如下

  • 没有放到spring容器中,比如类上没有标注@Service、方法上没有标注@Bean;
  • 组件扫描的时候没有扫描到,检查引导类位置、ComponentScan配置;
  • 使用的byName方式注入,检查beanName是否一致。

 

依赖注入的4种方式

  • 构造方法注入|构造器注入:调用构造方法注入属性值
  • set方法注入|设值注入:调用setter方法注入属性值
  • 字段注入|注解注入:在字段上使用@Autowired之类的注解注入
  • 工厂方法注入:调用工厂方法进行注入,可以细分为静态工厂注入(static修饰的工厂方法)、实例工厂注入(普通工厂方法),不常用。

 

依赖注入的注解

  • @Value:只能注入基本类型、String,可标注在成员变量、setter方法上
@Value("1")  //值是String形式private int xxx;

 

以下3个是自动装配的注解

  • @Autowired:按类型自动装配(byType),可标注在成员变量(官方不推荐)、setter方法、构造方法上。
  • @Qualifier:按名称自动装配(byName),需要和@Autowired一起使用,@Autowired限定Class,@Qualifier限定名称,即注入该类的指定name的实例。可标注在成员变量(官方不推荐)、setter方法上。
  • @Resource:可指定装配方式,未指定装配方式时默认先按名称装配(byName),如果找不到指定名称的bean则按类型装配(byType)。可标注在成员变量、setter方法上
@Autowired(required = false)  //默认required=true 容器中必需要有该类型的bean。@Autowired@Qualifier("xxx")  //@Qualifier可指定beanName,未指定时默认取变量名//可指定装配方式,未指定时先按byName方式装配,如果容器中找不到指定名称的bean,则按byType方式装配@Resource(name = "xxx")  @Resource(type = Xxx.class)  @Resource

一个接口|类可能有多个可用的bean实例,用byType方式注入bean实例时可能会报错:存在多个可用实例。常见的解决方式有2种

  • 改为byName方式注入,适合局部修改。
  • 标注bean时额外使用@Primary标注首选的bean,注入该类型的实例时优先注入@Primary标注的实例,多数据源常使用此种方式实现,但此种方式会影响到所有byType方式注入该类型实例的地方,适合全局修改。影响范围较广,只需要局部修改的时候慎用@Primary。

 

bean的循环依赖问题

循环依赖:bean之间互相依赖,形成闭环,eg. A -> B -> A,A -> B -> C -> A 。

bean的创建过程

  • 创建实例,分配内存空间
  • 填充bean的各个成员字段(循环依赖发生在此阶段)
  • 如果实现了接口,则做一些对应的工作

循环依赖有2种情况

  • 构造方法注入方式产生的循环依赖,spring自己解决不了
  • set注入方式产生的循环依赖,spring能解决
  • 字段注入,spring能解决

在这里插入图片描述

spring只解决了set注入方式的单例循环依赖。单例模式会使用三级缓存缓存bean实例,原型模式每次都是创建实例,不会缓存创建的bean的实例,三级缓存不能解决原型模式的循环依赖问题。

https://blog.csdn.net/u010853261/article/details/77940767

https://zhuanlan.zhihu.com/p/84267654

https://blog.csdn.net/weixin_38405253/article/details/113449152

https://www.cnblogs.com/leeego-123/p/12165278.html

https://www.jianshu.com/p/8bb67ca11831

https://www.cnblogs.com/tiger-fu/p/8961361.html

 

bean名称的默认生成策略

使用@Controller、@Service之类的注解标注bean时,bean的名称由AnnotationBeanNameGenerator类的generateBeanName()方法获取

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = this.determineBeanNameFromAnnotation((AnnotatedBeanDefinition)definition); //如果设置了beanName,则直接使用设置的beanName if (StringUtils.hasText(beanName)) {
return beanName; } } //否则使用默认的生成策略生成beanName return this.buildDefaultBeanName(definition, registry);}//默认生成策略的核心代码如下,参数name是类名,返回值是默认的beanNamepublic static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name; } //如果类名的前2个字母都是大写,则直接取类名作为默认的beanName if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
return name; } //否则取类名的camel写法作为默认的beanName char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars);}

使用注解标注bean,如果类名的前2个字母都是大写,且用byName=类名的camel写法的方式注入该Bean,会报NoSuchBeanDefinitionException。

类名的前2个字母都是大写时

  • 如果要用byName方式注入,应该手动在注解中指定beanName;
  • 如果没有手动在注解中指定beanName,则应该以byType方式注入。

 

应用上下文

spring的核心是spring容器,spring容器的核心是负责bean的创建、管理。

spring提供了2个核心接口用于操作spring容器,也是spring容器的2种实现方式,可以把bean放到容器中,也可以从容器中获取bean

  • BeanFactory:只提供基本的DI功能,是spring容器最基本、最简单的实现,一般是spring框架自身使用,
  • ApplicationContext:应用上下文,是BeanFactory的间接子接口,在BeanFactory的基础上提供了更丰富的功能,可以解析配置文件、注册管理bean、获取容器中的bean,是spring容器的高级实现,提供给开发人员使用。

 

获取应用上下文的4种方式

springboot的大致启动流程
//原有的main()方法SpringApplication.run(DemoApplication.class, args);//实质是调用SpringApplication的构造方法创建spring容器,并启动容器new SpringApplication(DemoApplication.class).run(args);//调用的SpringApplication的构造方法如下public SpringApplication(Class
... primarySources) {
this((ResourceLoader)null, primarySources);}public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = Collections.emptySet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.applicationContextFactory = ApplicationContextFactory.DEFAULT; this.applicationStartup = ApplicationStartup.DEFAULT; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories(); //设置应用上下文的初始化器 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //设置应用监听器 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass();}//run()方法源码,返回值是ConfigurableApplicationContextpublic ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try {
listeners.running(context); return context; } catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); }}

 

ApplicationContext的工具类
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContext;/** * ApplicationContext(应用上下文)常用操作的工具类 */@Slf4jpublic class ApplicationContextUtils {
/** * 应用上下文的引用 */ private static ApplicationContext applicationContext; public static void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextUtils.applicationContext = applicationContext; log.info("已保存应用上下文的引用"); } public static ApplicationContext getApplicationContext() {
return ApplicationContextUtils.applicationContext; } /** * 通过name获取bean * * @param name beanName * @return 获取到的bean */ public static Object getBean(String name) {
return applicationContext.getBean(name); } /** * 通过class获取bean * * @param clazz bean的Class对象 * @return 获取到的bean */ public static
T getBean(Class
clazz) {
return applicationContext.getBean(clazz); } /** * 通过name+class获取bean * * @param name beanName * @param clazz bean的Class对象 * @return 获取到的bean */ public static
T getBean(String name, Class
clazz) {
return applicationContext.getBean(name, clazz); }}

 

1. 直接在引导类中设置应用上下文

最简单的方式

@SpringBootApplicationpublic class DemoApplication {
public static void main(String[] args) {
//ApplicationContext是接口,run()方法返回的ConfigurableApplicationContext是ApplicationContext的子接口 ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args); ApplicationContextUtils.setApplicationContext(applicationContext); }}

 

2. 使用应用上下文初始化器ApplicationContextInitializer
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContextInitializer;import org.springframework.context.ConfigurableApplicationContext;/** * 自定义的应用上下文初始化器 * 实现ApplicationContextInitializer
,重写方法即可 */@Slf4jpublic class MyApplicationContextInitializer implements ApplicationContextInitializer
{
@Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
//保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(configurableApplicationContext); }}

 

修改引导类的main()方法如下

@SpringBootApplicationpublic class DemoApplication {
public static void main(String[] args) {
//创建spring容器 SpringApplication application = new SpringApplication(DemoApplication.class); //添加应用上下文的初始化器。此时已经生成了应用上下文,应用上下文初始化器可以对应用上下文做进一步处理 application.addInitializers(new MyApplicationContextInitializer()); //启动容器 application.run(args); }}

 

3. 使用应用监听器ApplicationListener

监听器是观察者模式的典型应用,主题对象发生更新时会自动通知所有的观察者。

抽象类ApplicationContextEvent有4个实现类,对应应用上下文的四种事件

  • ContextStartedEvent
  • ContextStoppedEvent
  • ContextClosedEvent
  • ContextRefreshedEvent
import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationListener;import org.springframework.context.event.ApplicationContextEvent;/** * 自定义的应用监听器 * 实现ApplicationListener
,泛型指定要监听的应用事件 */@Slf4j// @Componentpublic class MyApplicationListener implements ApplicationListener
{
/** * 触发应用上下文事件后的处理 * * @param applicationContextEvent 触发的应用上下文事件 */ @Override public void onApplicationEvent(ApplicationContextEvent applicationContextEvent) {
//保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(applicationContextEvent.getApplicationContext()); }}

ApplicationListener可以用@Component直接放在容器中,也可以在引导类中手动添加

@SpringBootApplicationpublic class DemoApplication {
public static void main(String[] args) {
//创建spring容器 SpringApplication application = new SpringApplication(DemoApplication.class); //设置应用监听器 application.addListeners(new MyApplicationListener()); //启动容器 application.run(args); }}

 

4. 使用应用上下文的Aware接口ApplicationContextAware

aware 感知、察觉,类似于listener。Aware接口常见的子接口如下

  • BeanFactoryAware
  • ApplicationContextAware
  • ResourceLoaderAware
  • BeanNameAware
  • EnvironmentAware
  • ApplicationStartupAware
  • ApplicationEventPublisherAware
import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * 自定义的ApplicationContextAware */@Component  //放到容器中public class MyApplicationContextAware implements ApplicationContextAware {
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//保存应用上下文的引用 ApplicationContextUtils.setApplicationContext(applicationContext); }}

最常用的是第三四种,通过ApplicationListener、ApplicationContextAware保存应用上下文。

 

你可能感兴趣的文章
不止 5G 和鸿蒙,华为最新大招,扔出 AI 计算核弹
查看>>
【早报】做Java半年,挣的不如AI 1个月?第二句泪目..
查看>>
反转!2019程序员吸金榜来了,AI程序员刷爆了..
查看>>
学Python后到底能干什么?网友:我太难了
查看>>
华为、BAT力捧!程序员:我彻底慌了...
查看>>
刷爆了!BAT这场AI芯片之战,你更支持谁?
查看>>
定了!刚面完AI岗位,这些题全都考了!程序员:有黑幕!
查看>>
GitHub 热榜第一!这个 Python 项目超 8.4k 标星,网友:太实用!
查看>>
阿里云部署Django项目(nginx+uWSGI)
查看>>
程序员必看,这本深度学习宝典刷爆IT圈!
查看>>
python学习心得体会(一)
查看>>
程序员薅羊毛神器来了!
查看>>
自学 Python后,自己一个人可以通过此技能挣什么钱?
查看>>
Java三种面试者是面试官最讨厌的,见之即毙!
查看>>
当程序员要具备什么条件?
查看>>
行啊,人工智能玩大了!
查看>>
手拿3份AI的offer?这些人凭什么这么刚?
查看>>
给大家推荐一本Python书,京东断货王,火遍IT圈!
查看>>
会Python,程序员必备的软技能,你会吗?
查看>>
Python小白说:“看完这篇文章才知道这样学习最高效”
查看>>