在了解SpringBoot之前我们先要了解SpringBoot的相关注解和流程
SpringBoot相关注解如图:
SpringBoot的启动流程如图:
1、在SpringBoot使用过程中,我们使用到某个模块的功能,为什么依赖场景有的要写版本号,有的不用写呢?
比如webmvc,junit,redis不需要,mybatis需要
我们的工程继承了SpringBoot的夫工程Spring-boot-starter(版本2.3.10.release)
这个工程提供了53个场景,每个场景都搭建好了环境,并进行了版本锁定(比如webmvc场景引入了需要spingmvc包和tomcat插件等,做好了适配)所以对于springboot中提供的场景,无需编写版本号
但是对于springboot中没有提供的场景,而是第三方定义的场景,就需要编写版本号
2、我们使用springboot之后,springboot做了什么,让我们省略了很多繁杂的配置
这就需要了解springboot的自动化配置原理了大致可以简单的描述为:
1)springboot启动的时候都会加载每个场景下的xx-starter的METAINF/spring.factories文件类似于java中的spi机制
2)这个文件下有自动装配类
3)自动装配类中有很多配置类
4)只要满足特定条件下的配置类会被spring加载到IOC
这一套流程为开发者省略了很多繁杂的配置,体现了springboot约定大于配置的思想
3、如果我们搞懂了springboot的自动化配置原理是不是可以自己定义一些场景了?
是的,而且我们开发的项目中也需要根据业务封装一些场景
但是学习springboot的自动化配置原理不是简单的事:
1)spi机制
2)各种注解,接口和类
SPI全程Service Provider Interface ,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。说白了,java的spi机制就是将一些类信息写在约定的文件中,然后由特定的类加载器加载解析文件获取资源;
场景 | 说明 |
---|---|
数据库驱动 | 数据库驱动加载接口实现类的加载不同类型的数据库驱动 |
日志门面SLF4J接口实现类加载 | SLF4J加载不同提供商的日志实现类 |
Spring | Spring中大量使用了spi,比如对servlet3.0规范对ServletContainerInitializer的实现,自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等 |
Dubbo | Dubbo中也大量使用spi的方式实现框架的扩展,不过对java提供的原生spi做了封装,允许用户扩展实现Filter接口 |
SpringBoot | 基于SPI思想实现自动装配 |
java SPI实际上是基于接口编程+策略模式+配置文件组合实现的动态加载机制;
通过依赖能了解SpringBoot管理了那些starter
1.通过依赖spring-boot-dependencies搜索starter发现非常多的官方starter,并且已经帮助我们管理好了版本提供了53个场景依赖
2.项目中使用直接引入对应的starter即可,这个场景下所有的依赖就会自动导入到项目中,简化了繁琐的依赖。
3.引入starter不仅仅是帮助我们管理了依赖,还帮我们做了很多默认的配置信息,简化了大量的配置,使用更加的简单。
4.大多数场景启动器的底层依赖spring-boot-starter【为场景依赖提供spring的核心环境】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.10.RELEASE</version>
<scope>compile</scope>
</dependency>
1.为什么引入SpringBoot默认的场景依赖,无需书写版本号
因为父工程通过dependentManager标签进行了版本的锁定与声明;
2.再配置满足当前开发需要的前提,为什么建议使用默认的配置
1.基于约定优于配置思想,减少开发中的繁琐的配置
2.SpringBoot帮助我们解决了资源依赖间版本兼容和冲突等问题
目的:以spring-boot-starter-web场景依赖中自动化配置原理为例讲解,能够理解web MVC自动化配置加入那些依赖,做了那些默认配置
SpringMvc配置流程
1、首先添加SpringMvc相关的依赖资源spring-webmvc.jar、jackson相关,servlet相关等;
2、定义springmvc.xml配置文件,进行如下的配置
1).扫描controller所在包;
2).配置annotation-driven支持mvc扩展功能
3).试图解析器
4).静态资源
5).拦截器
6).
【1.3】配置web.xml
1、初始化spring容器
2.初始化springmvc DispatcherServlet核心调度器
3.请求乱码过滤器
4.配置tomcat,部署项目
也就是说在我们开发业务代码前,就必须要准备好这些环境,否则业务功能无法实现。
当我们基于springboot使用web场景依赖时,我们发现要搭建一个标准的mvc工程,只需要引入spring-boot-starter-web场景依赖即可,几乎无需做其他配置,在这个过程中SpringBoot为我们做了大量的自动化配置工作;
接下来我们就探索一下SpringBoot是如何帮助我们完成强大而又简单自动化配置的。
引入web开发场景启动器依赖:
<!--web开发的起步依赖 场景启动器依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我们发现spring-boot-starter-web场景依赖帮我们做了很多事:
引入webmvc工程相关的依赖资源:
工程自动加载依赖资源
通过断点测试SpringIOC容器,我们发现SpringBoot几乎把MVC相关的所有资源自动加载到容器中了:
1)自动装配SpringMVC核心组件(包括核心3大组件和文件上传等组件)
引入SpringMVC全套组件;
自动装配好SpringMVC常用组件(三大组件,文件上传等)
2)自动配好web常见功能,如:字符编码问题,静态资源管理,错误页面处理组件等;
3)SpringBoot默认自动扫描启动类同级以及所有子目录下资源;
1.引导类所在包及其下面的所有子包里面的组件会被默认扫描,无需像以前开启注解扫描
2.如果想要改变扫描路径@SpringBootApplication(scanBasePackages="com.xx.xx")或者@ComponentScan指定扫描路径
4)基于约定优于配置思想自动配置Tomcat常规参数
端口号:8080
字符键:UTF-8
参考:org.springframework.boot.autoconfigure.web.ServerProperties配置类
在spring-boot-autoconfigure.jar资源包下META-INF/spring.factories文件下配置:
以org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration类为例,通过翻阅源码:
@Configuration(proxyBeanMethods = false)//定义配置类
@Conditional(DefaultDispatcherServletCondition.class)//满足指定条件则装配
@ConditionalOnClass(ServletRegistration.class)//满足指定类存在则装配
@EnableConfigurationProperties(WebMvcProperties.class)//加载配置对象
protected static class DispatcherServletConfiguration {
//自动装配springMVC核心调度器
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
//.....
}
SpringBoot引入web场景依赖后,有什么好处
1.大量依赖资源自动装配,避免繁杂的XML配置;
2.可以使开发人员的中心聚焦与=于业务开发,提高开发效率;
掌握@Configuration注解的作用及新特性
@Configuration注解使用
注解的作用是替代原始springXML配置文件功能
思考:
配置类中被@Bean修饰的方法被配置类bean对象多次调用返回的bean是否是同一个bean?
演示:
package com.xxx.config;
import com.xxx.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
//proxyBeanMethods = false:方法不会被代理 LITE模式 【多例模式】
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean//方法默认被代理 FULL,实现单例模式
public User myUser(){
User user = new User().setId(1l).setBirthday(new Date()).setUserName("张三");
return user;
}
}
2)在引导类编写代码测试:
package com.xx;
import com.xx.config.MyConfig;
import com.xx.pojo.User;
import org.apache.tomcat.util.net.jsse.JSSEUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Map;
/**
* 编写引动类/启动类
* @SpringBootApplication注解定义当前类为springboot的引导类
*/
@SpringBootApplication
public class SpringConfigurationApp {
/**
* main方法是springboot
* 启动的入口
* @param args
*/
public static void main(String[] args) {
//获取ioc容器
ConfigurableApplicationContext ctx = SpringApplication.run(SpringConfigurationApp.class, args);
//key:bean名称 value:bean对象
// 发现MyConfig被cglib动态代理了:从ioc容器检查指定的bena是否存在,如果存在,直接返回ioc容器中的bean FULL
Map<String, MyConfig> beanInfo = ctx.getBeansOfType(MyConfig.class);
System.out.println(beanInfo);
Map<String, User> userInfo = ctx.getBeansOfType(User.class);
System.out.println(userInfo);
//获取myconfig对象
MyConfig myConfig = ctx.getBean(MyConfig.class);
User user1 = ctx.getBean(User.class);
User user2 = myConfig.myUser();
System.out.println(user1==user2);
}
}
@Configuration注解下proxyBeanMethods属性表示代理bean的方法属性【since spring5.2】
功能:
proxyBeanMethods=true :Full模式(默认),保证每个@Bean方法被调用多少次返回的组件都是单实例的;
proxyBeanMethods=false:Lite模式,每个@Bean方法被调用多少次返回的组件都是新创建的
演示:
1、默认proxyBeanMethods=true,springBoot会检查这个组件是否在容器中有,有则直接引用
默认proxyBeanMethods=true,springBoot会检查这个组件是否在容器中有,有则直接引用
User user3=myConfig1.getUser();
System.out.println(user1==user3);//true
2.修改proxyBeanMethods=false,则每调用一次Spring就会创建一个新的Bean对象
在执行结果则为false,证明两次获取的bean不是同一个bean
【1】在注解@Configuration修饰的配置类下,调用构建bean的方法时,如何实现单例或者多例模式?
# 通过proxyBeanMethods属性指定:
1. proxyBeanMethods=true :full模式,也就是单例模式,每次方法被调用时不会立即创建,会先从IOC容器检查是否有指定类型的bean,如果有,则直接返回,如果没有,调用方法创建;<br>
2. proxyBeanMethods=false:lite模式,也就是多例模式,每次方法被调用,直接创建对象;
【2】在初次构建bean对象时FULL模式与LITE模式哪个执行效率相对较高?
1. LITE模式,因为构建对象时无需从IOC容器检查对象是否存在,相对效率高一些;
2. 这也是自动化装配类选择使用LITE模式的核心原因所在(项目启动快)
04-springBoot2高级-底层原理@Import注解使用1
目的:
@Import注解学习是为接下来的源码阅读做准备的;
理解@Import注解的作用及四种使用方式;
作用:
使用@Import导入的类会被Spring加载到IOC容器中
@Import提供4中用法:
1.直接导入一个类,实例化后作为一个Bean被IOC容器管理
2.导入配置类
3.导入ImportSelector实现类。一般用于加载配置文件中的类
4.导入ImportBeanDefinitionRegistrar实现类
实现:
【1】导入Bean
package com.xxx.xxx;
import com.xxx.xxx.pojo.User;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication
@Import(User.class)
//会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径
public class SpringConfigurationApp {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
//获取bean的名称与对象对应的键值对对象
Map<String, User> map = applicationContext.getBeansOfType(User.class);
System.out.println(map);
User user1 = applicationContext.getBean("com.itheima.sh.pojo.User", User.class);
System.out.println(user1);
}
}
【2】导入配置类
定义配置类:
package com.xxx.config;
import com.xxx.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
@Configuration
public class MyConfig {
@Bean//方法默认被代理 FULL,实现单例模式
public User myUser(){
User user = new User().setId(1l).setBirthday(new Date()).setUserName("张三");
return user;
}
}
导入配置类:
package com.xxx;
import com.xxx.config.MyConfig;
import com.xxx.config.MyImportBeanDefinitionRegistrar;
import com.xxx.config.MyImportSelector;
import com.xxx.pojo.User;
import org.apache.tomcat.util.net.jsse.JSSEUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
/**
* 编写引动类/启动类
* @SpringBootApplication注解定义当前类为springboot的引导类
*/
@SpringBootApplication
//方式1:导入类最终实例化成bena为ioc容器维护 bean名称:类的全限定名称
//@Import(User.class)
//方式2:导入配置类 ,配置类下的@Configuration可以省略
@Import(MyConfig.class)
public class SpringConfigurationApp {
public static void main(String[] args) {
//获取ioc容器
ConfigurableApplicationContext ctx = SpringApplication.run(SpringConfigurationApp.class, args);
//@Import(User.class) bean名称:类的全限定名称
Map<String, User> userBean = ctx.getBeansOfType(User.class);
System.out.println(userBean);
User user = ctx.getBean("com.itheima.pojo.User", User.class);
System.out.println(user);
}
}
目的:注解@Import注解使用另外两种使用方式
步骤:
1.导入ImportSelector实现类。一般用于加载配置文件中的类
2.导入ImportBeanDefinitionRegistrar实现类
实现:
【1】导入ImportSelector实现类
场景:一般用用于批量导入第三方资源bean
1、编写ImportSelector接口的实现类MyImportSelector
package com.xxxx.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.Set;
public class MyImportSelector implements ImportSelector {
/**
* @Import注解打到哪个类下,那么类下的注解元数据信息通过AnnotationMetadata注入到selectImports方法下
* 实现批量导入
* @param annotationMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
Set<String> sets = annotationMetadata.getAnnotationTypes();
return new String[]{"com.itheima.pojo.User"};
}
}
2、引导类导入
package com.xxx;
import com.xxx.config.MyConfig;
import com.xxx.config.MyImportBeanDefinitionRegistrar;
import com.xxx.config.MyImportSelector;
import com.xxx.pojo.User;
import org.apache.tomcat.util.net.jsse.JSSEUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
/**
* 编写引动类/启动类
* @SpringBootApplication注解定义当前类为springboot的引导类
*/
@SpringBootApplication
//方式1:导入类最终实例化成bena为ioc容器维护 bean名称:类的全限定名称
//@Import(User.class)
//方式2:导入配置类 ,配置类下的@Configuration可以省略
//@Import(MyConfig.class)
//方式3:实现批量导入组件
@Import(MyImportSelector.class)
public class SpringConfigurationApp {
/**
* main方法是springboot
* 启动的入口
* @param args
*/
public static void main(String[] args) {
//获取ioc容器
ConfigurableApplicationContext ctx = SpringApplication.run(SpringConfigurationApp.class, args);
//@Import(User.class) bean名称:类的全限定名称
Map<String, User> userBean = ctx.getBeansOfType(User.class);
// System.out.println(userBean);
// User user = ctx.getBean("com.itheima.pojo.User", User.class);
// System.out.println(user);
}
}
1、编写 ImportBeanDefinitionRegistrar接口实现类:MyImportBeanDefinitionRegistrar
package com.xxx.config;
import com.xxxx.pojo.User;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata @Import注解作用的类下的元数据信息封装
* @param registry 注册bean信息的注册对象
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1.创建bena的定义信息
AbstractBeanDefinition userDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
/**
* 参数1:bean的名称
* 参数2:bean的定义对象
*/
registry.registerBeanDefinition("user",userDefinition);
}
}
2、引导类测试
package com.xxx;
import com.xxxx.config.MyConfig;
import com.xxxx.config.MyImportBeanDefinitionRegistrar;
import com.xxxx.config.MyImportSelector;
import com.xxxx.pojo.User;
import org.apache.tomcat.util.net.jsse.JSSEUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
/**
* 编写引动类/启动类
* @SpringBootApplication注解定义当前类为springboot的引导类
*/
@SpringBootApplication
//方式1:导入类最终实例化成bena为ioc容器维护 bean名称:类的全限定名称
//@Import(User.class)
//方式2:导入配置类 ,配置类下的@Configuration可以省略
//@Import(MyConfig.class)
//方式3:实现批量导入
//@Import(MyImportSelector.class)
//方式4:通过ImportBeanDefinitionRegistrar实现类完成导入,也能完成bena的批量导入
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringConfigurationApp {
public static void main(String[] args) {
//获取ioc容器
ConfigurableApplicationContext ctx = SpringApplication.run(SpringConfigurationApp.class, args);
//@Import(User.class) bean名称:类的全限定名称
// Map userBean = ctx.getBeansOfType(User.class);
// System.out.println(userBean);
// User user = ctx.getBean("com.itheima.pojo.User", User.class);
// System.out.println(user);
//@Import(MyConfig.class)
User user2 = ctx.getBean("user",User.class);
System.out.println(user2);
}
}
使用@Import注解的4种方式?
1. 直接导入Bean
2. 导入配置类★
3. 导入ImportSelector 实现类。一般用于加载配置文件中的类 ★★
4. 导入ImportBeanDefinitionRegistrar实现类
目的:理解@Conditional衍生条件装配的作用
作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器
ConditionalOnClass:工程环境中必须存在指定的字节码文件才初始化Bean
ConditionalOnMissingBean:IOC容器中没有对应Bean才初始化Bean
ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
package com.itheima.config;
import com.itheima.pojo.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
@Configuration
//【1】User类存在,那么当前的配置bean就会被加载到ioc容器下
//@ConditionalOnClass(name = {"com.itheima.pojo.User"})
//【2】与@ConditionalOnClass功能相反
//@ConditionalOnMissingClass(value = {"com.itheima.pojo.User"})
//【3】当指定类型的bean在ioc容器中不存在,那么生效
//@ConditionalOnMissingBean(type = {"com.itheima.pojo.User"})
//【4】属性:prefix指定配置文件前缀 name:指定后缀之后的key 如果存在则满足条件,注解作用的bean加载
//配合文件必须配置myredis.enable=true才能被创建
@ConditionalOnProperty(prefix = "myredis",name = "enable",havingValue = "true")
public class MyConfig {
@Bean
public User myUser(){
User user = new User().setId(1l).setBirthday(new Date()).setUserName("张三");
return user;
}
}
测试:
package com.xxx;
import com.xxx.config.MyConfig;
import com.xxx.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringConfigurationApp {
public static void main(String[] args) {
//获取ioc容器
ConfigurableApplicationContext ctx = SpringApplication.run(SpringConfigurationApp.class, args);
User bean = ctx.getBean(User.class);
System.out.println(bean);
}
}
注意:条件注解不仅可以添加到类上也可以作用在方法之上;
【3】小结
1Spring提供的条件注解有什么作用?
spring提供的@ConditionalOnxxx注解作用:满足条件当前类或者Bean才有效,按需导入;
2.Spring常用条件注解有哪些,各有什么作用?
1. @ConditionalOnClass:项目环境中存在指定的字节码文件才初始化Bean对象
2. @ConditionalOnMissingBean:IOC容器中没有指定的Bean对象才初始化注解作用的Bean
3. @ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
回顾 @ConfigurationProperties配置绑定 存在的目的是:
获取配置属性或者是配置文件指定前缀的属性信息,并且初始化Bean对象到 IOC 容器。
由此我们可以想:将来的配置我们可以放在配置文件中,通过这个注解来读取并封装成对象,这样避免了配置属性需要逐个注入获取的问题;
目的:能够理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用能够知道作用。
讲解:
1、SpringBoot是一个组合注解
2、@SpringBootConfiguration注解作用
可以,因为@SpringBootApplication注解中@SpringBootConfiguration注解对@Configuration做了层包装,本质也是一个配置注解
2、为什么启动类路径下的Controller、service类添加Spring bean注解后,不需要添加注解扫描,就可以被加载?
SpringBooot通过@ComponentScan默认加载主类路径及其子包路径;
目的:理解@EnableAutoConfiguration自动化配置核心实现注解
讲解:
本章节我们将通过注解源码阐述SpringBoot加载【工程内部资源】和【第三方starter资源】的加载机制;
通过查看源码,我们发现@EnableAutoConfiguration也是一个组合注解。
作用:利用Registrar给容器中导入一系列组件
点击 Registrar
进入到源码的 register
方法,添加 断点,测试
通过 debug 程序,我们发现@AutoConfigurationPackage注解底层会将SpringBoot启动类所在路径封装到BasePackage 类,并注册到IOC容器中,这样配合@ComponentScan注解完成加载启动类同级以及所有子目录下的资源被加载;
总之,@AutoConfigurationPackage核心作用是确定@ComponentScan注解扫描包的范围;
作用:是利用selectImports
方法中的 getAutoConfigurationEntry
方法给容器中批量导入相关组件;
调用流程分析:
AutoConfigurationImportSelector
类中的selectImports
方法;selectImports
方法底层调用getAutoConfigurationEntry()
方法获取可自动装配的配置类信息集合;getAutoConfigurationEntry()
方法调用getCandidateConfigurations(annotationMetadata, attributes)
方法获取所有基于META-INF/spring.factories文件下的自动配置类的集合;【127个自动装配的信息】loadSpringFactories()
方法扫描当前系统中所有META-INF/spring.factories文件,并加载获取自动配置类信息;configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//根据本工程的配置,过滤不满足@ConditionlOnxxx
configurations = getConfigurationClassFilter().filter(configurations);
小结:
【1】SpringBoot默认自动加载的配置文件路径是哪里?
当前项目系统路径下的所有META-INF/spring.factories文件
【2】简述SpringBoot自动装配流程?
1. 通过@Import注解调用AutoConfigurationImportSelector类中的selectImports方法;
2. selectImports方法底层调用getAutoConfigurationEntry()方法获取可自动装配的配置类信息集合;
3. getAutoConfigurationEntry()方法调用getCandidateConfigurations(annotationMetadata, attributes)方法获取所有基于META-INF/spring.factories文件下的自动配置类的集合;
4. 底层利用Spring工厂加载器调用 loadSpringFactories()方法扫描当前系统中所有META-INF/spring.factories文件,并加载获取自动配置类信息;
5. 根据@Conditional条件过滤,获取最终自动装配类,最后被IOC容器加载;
目的:
META-INF/spring.factories
配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。RedisAutoConfiguration
为例讲解, 进入到 RedisAutoConfiguration
自动化配置类。//定义当前注解作用类是配置类 proxyBeanMethods = false使用后lite模式,项目启动快
@Configuration(proxyBeanMethods = false)
//RedisOperations这个类存在,则满足加载条件 也就是说工程需要引入spring-data-redis-2.3.9.RELEASE.jar
@ConditionalOnClass(RedisOperations.class)
//开启配置属性类的加载,RedisProperties类被加载到IOC容器下
@EnableConfigurationProperties(RedisProperties.class)//配置属性bean通过构造器方式注入到工厂bean下
//导入满足条件的工厂配置类,sringboot推荐使用 Lettuce, 性能最好
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
//如果IOC容器中不存在名称为redisTemplate的bean,那么该方法就满足条件
//好处:为客户端充足的扩展,如果我们自定义了redisTempalte,那么springboot就不会自动装配
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
//StringRedisTemplate仅操纵数据类型是string的场景
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
3、RedisProperties
属性对象,用于加载默认的配置,如果配置文件配置了该属性,则配置文件就生效。
4、LettuceConnectionConfiguration
是当前版本默认使用的Redis的连接池(性能较好)
SpringBoot工程中没有引入的starter,是否能被加载到工程内?为什么?
自动装配配置类中只有满足conditional条件的配置类,才会被加载到IOC容器中;
SpringBoot自动化配置流程总结:
META-INF/spring.factories
内的EnableAutoConfiguration
配置列表;**需求:**自定义heima-redis-spring-boot-starter场景启动依赖,并成功集成到新项目下;
**技术要求:**基于Spring环境使用Jedis和RedisTemplate构建redis场景依赖工程;
步骤:
【1】创建 wunian-redis-spring-boot-starter 工程模块,打包方式为jar,添加相关依赖;
依赖清单 | 说明 |
---|---|
spring-boot-starter | 为自定义redis场景依赖提供spring核心环境 |
spring-boot-configuration-processor | 辅助配置文件时提示说明 |
spring-data-redis | spring整合redis的中间包,提供RedisTemplate等核心API |
jedis | java底层连接redis服务的客户端技术 |
lombok | 方便配置属性类setter方法生成 |
【2】添加配置属性类;
作用:将工程中关于redis的配置信息封装到一个配置类下,方便获取配置参数;
【3】添加自动配置类;
作用:加入条件装配信息,动态构建操纵redis的RedisTemplate对象;
【4】在工程resources下定义META-INF/spring.factories 文件,让SpringBoot自动识别并加载;
操作:将自定义的配置类定义在该文件中,使得SpringBoot识别加载;
【5】在测试模块中引入自定义的 heima-redis-spring-boot-starter 依赖,测试;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<redis.version>2.3.9.RELEASE</redis.version>
</properties>
<packaging>jar</packaging>
<dependencies>
<!--为场景依赖提供spring的基础环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--开发配置提醒-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--spring整合redis的整合包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<scope>compile</scope>
</dependency>
<!--引入jedis客户端依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
package com.xxx.pros;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 自动化默认属性配置类:读取配置文件下的数据映射配置对象
*/
@Data
@ConfigurationProperties(prefix = "myredis.config")
public class MyRedisProps {
//定义redis host主机地址
private String host="localhost";
//端口号
private Integer port=6379;
//定义数据库 0~15
private Integer db=0;
}
【3】添加条件配置类
package com.xxx.redis;
import com.xxx.pros.MyRedisProps;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 定义redis自动装配类
*/
@Configuration(proxyBeanMethods = false)
//必须使用jedis,且必须引入spring-data-redis.jar,完成对redis的操作
@ConditionalOnClass(name = {"redis.clients.jedis.Jedis","org.springframework.data.redis.core.RedisOperations"})
@EnableConfigurationProperties(MyRedisProps.class)
public class MyRedisAutoConfiguration {
private MyRedisProps myRedisProps;
/**
* 构造器注入MyRedisProps类型的bean
* @param myRedisProps
*/
public MyRedisAutoConfiguration(MyRedisProps myRedisProps) {
this.myRedisProps = myRedisProps;
}
@Bean
//容器中不存在名称为redisTemplate的bean则创建
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate redisTemplate(){
//1.创建连接工程
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setHostName(myRedisProps.getHost());
connectionFactory.setPort(myRedisProps.getPort());
connectionFactory.setDatabase(myRedisProps.getDb());
//1.创建RedisTemplate对象
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
//2.设置对象序列化支持
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
在resources下定义META-INF/spring.factories 文件,并配置自动装配信息,让SpringBoot自动加载:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.redis.MyRedisAutoConfiguration
maven安装到本地仓库,方便其它工程引用
目的:验证自定义starter是否可以使用
实现:
【1】新建 boot_xxxedis
项目中引入依赖
<!--1、引入SpringBoot父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
</parent>
<dependencies>
<!--web 启动器 SpringBoot对web的支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sh</groupId>
<artifactId>heima-redis-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
【2】定义yml配置
myredis:
config:
host: 127.0.0.1
port: 6379
db: 1
【2】测试
package com.xxx;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
public class TestMyStarter {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test1(){
redisTemplate.opsForValue().set("name","lisi");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
目的:能够理解健康监控actuator
的作用
背景:
在一些大型的业务应用中,工程会根据业务模块做微服务拆分,后期每一个微服务在云上部署以后,都需要对其进行监控、追踪、审计、控制等操纵,这会给维护人员带来很大的运维压力。
SpringBoot对此就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
实现:
1、被监控工程中引入Actuator依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、启动项目,访问 http://localhost:80/actuator/**
3、暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
enabled: true # 开启健康检查详细信息
show-details: always
访问 http://localhost:80/actuator
会发现内容多了,里面的地址分别都可以访问,记录的是对应的健康监测的信息。
目的:能够搭建 可视化监控平台
讲解:
SpringBoot Admin 有两个角色,客户端(Client)和服务端(Server)。
Spring Boot Admin为注册的应用程序提供以下功能:
快速入门:https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started
实现:
以下为创建服务端和客户端工程步骤:
1、创建 admin_server 模块,引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2、开启注解支持
package com.xxx.sh;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableAdminServer
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
注意端口修改为:9999
1、在任意服务里面引入依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.3.1</version>
</dependency>
2、配置文件
# 执行admin.server地址
spring:
boot:
admin:
client:
url: http://localhost:9999 # admin 服务地址
instance:
prefer-ip: true # 显示IP
application:
name: boot_data # 项目名称
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
enabled: true # 开启健康检查详细信息
show-details: always
启动服务,访问admin Server http://localhost:9999/
SpringBoot 项目开发完毕后,支持两种方式部署到服务器
1. 将要部署的项目打成jar包
1). 注意: 导入springboot打包插件
2. 丢到linux系统上
1). 注意mx在连接linux的时候,如果连不上,查看services.msc中vm开头的服务有没都启动
3. 在linux上启动项目
1). java -jar xx.jar
前端启动,界面不能关闭
2). linux防火墙默认只开放22端口
I. 安装软件的时候,开放了8080,3306,6379
II. 要么开放9999,要么关闭防火墙
systemctl stop firewalld
3). 后端启动
(启动时不在终端打印日志,在文件中打印,默认文件名 nohup.out)
nohup java -jar xx.jar &
4. 在windows上用浏览器访问
http://192.168.109.122:9999/
5. linux命令
1). pwd : print working directory 打印当前工作路径
2). ps -ef | grep java : 查看当前系统中包含有java关键字的进程
3). cat 文件 : 查看
war包部署
1. 将要部署的项目打成war包
1). spring-boot-starter-web环境要排除tomcat.jar
(运行起来用linux系统中的tomcat软件)
2). 引入servlet的jar包,不然编译会报错
3). 插件中声明打war包不需要web.xml,不然会报错
4). 打包方式为war
5). 修改启动类(继承SpringBootServletInitializer重写configure方法)
2. 丢到linux系统的tomcat/webapps目录下
3. 启动tomcat,就会加载我们的项目
1). bin目录
./startup.sh
2). 访问
http://192.168.109.122:8080/war包名字/controller虚拟路径
目的:能够使用Jar包方式部署SpringBoot项目
实现:
1、引入打包插件依赖
<build>
<!--声明jar包名称-->
<finalName>boot_data</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、打包,复制出Jar使用,java -jar
运行程序
复制到任意Linux目录:
正常访问;
注意事项:
1.项目运行必须依赖 JDK 环境;
2. 注意linux的防火墙9999端口需要开放,或者简单点也可以关闭防火墙
systemctl stop firewalld
3. 启动springboot项目时,默认占用一个终端,如果终端窗口关闭,服务进程结束,如何解决?
nohup java -jar boot_data.jar &
nohub java -jar boot-data.jar > 1.log
# 加了nohup后,即使关掉命令窗口,后台程序boot-data.jar也会一直执行
目的:能够使用war包方式部署SpringBoot项目
步骤:
1、更改打包方式为 war
2、配置打包插件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
<build>
<finalName>boot_datafinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-war-pluginartifactId>
<configuration>
<failOnMissingWebXml>falsefailOnMissingWebXml>
configuration>
plugin>
plugins>
build>
3、修改引导类 继承 SpringBootServletInitializer
package com.xxx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
/*
* 启动类要继承SpringBootServletInitializer
* 并重写configure方法
* */
@SpringBootApplication
public class HelloApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(HelloApplication.class);
}
}
4、配置tomcat,war复制到 webapps 目录下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RjuYzX69-1644246254860)(assets/image-20210504160435002.png)]
5、启动tomcat
./startup.sh
6、浏览器访问:http://192.168.200.150:8080/boot_data
实际开发中用哪一种?
如果你的工程用户访问量少(不会面临3高问题:高可用 高并发 高性能),那么推荐使用jar包部署方式,因为部署简单;
如果工程面临3高问题,那么推荐使用war包部署,因为使用外部的tomcat,可以做到最大程度的参数自定义(方便tomcat的优化==》运维人员介入)
【1】如何实现被@Configuration修饰的类中的方法产生的bean是单例还是多例?
proxyBeanMethods=true: Full模式,单例模式,底层被 cglib代理,先从IOC容器中检查当前的bean是否存在,如何存在,则直接返回bean对象,否则创建;
proxyBeanMethods=false:LITE模式,多例模式
【2】Import注解导入bean的4种方式
1.直接导入某个类,作为换一个bean被ioc容器管理
2.导入配置类(配置类可以不写@Configuration注解)
3.导入实现ImportSelector接口的实现类(批量导入)
4.导入实现ImportBeanDefinitionRegistrar接口的实现类(批量注册bean信息,完成导入)
【3】常用的条件装配注解有哪些?
@ConditionalOnBean: 容器中存在bean则创建
@ConditionalOnMissingBean: 容器中不存在bean则创建
@ConditionalOnClass:判断某个类必须存在,才创建
@ConditionalOnMissingClass:判断某个类不必须存在,才创建
@ConditionalOnProperty(prefix="前缀",name="名称",havingvalue="必须是指定的值")配置文件中必须存在指定的key和value值则创建
【4】自定装配的原理:
1)SpringBoot基于SPI思想约定工程以启动加载工程系统环境下META-INF/spring.factories文件获取可自动装配的配置类信息;
2)SringBoot引入了条件装配的概念,这些自动装配类只加载满足条件的配置类;
3)通过自动装配的配置类,将相关的资源加入IOC容器,容器中有了这些bean资源,就有了指定的功能;
【5】自定义starter流程
1.定义一个maven工程,打包方式jar,然后引入spring-boot-starter.jar提供spring核心环境依赖;
2.定义属性配置类,一般使用@ConfigurationProperties注解映射yml中的配置信息;
3.定义自动装配的配置类,主要注解:
类上:
@Configuration(proxyBeanMethods=false):加载快
@ConditionalOnxxx:条件装配条件
@EnableConfigurationProperties:将属性配置类加载到IOC容器下
方法上:
@Bean
@ConditionalOnxxx:满足条件,则方法被执行,构建对应的bean对象
4.在resources包下定义META-INF/spring.factories文件
eg:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.myredis.config.MyRedisAutoConfiguration
5.打包