一、前言
两大优势:自动装配,起步依赖
优先级配置
有三种配置文件类型 (执行优先级由高到低排序)
properties
yml
yaml
打包测试
需要在 pom.xml 中引入插件 但这个插件总爆红,但是不会影响运行,不用搭理他。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
二、Bean 管理
1 注解及衍生注解 注入
spring 提供的注解有注解@Component以及它的三个衍生注解(@Controller、@Service、@Repository)来声明IOC容器中的bean对象
2 如何获取 Bean
创建测试类
Spring 在启动时会将 ApplicationContext 对象纳入容器中。表示容器对象
如何获取容器?
Java中万物皆为对象,容器我们也抽取成为对象 ApplicationContext(程序员) --> BeanFactory(底层)
三种方式 getBean 获取
根据 name 名字默认类名,首字母小写 (缺点:要强转不安全)(若给的名字错误 -- NoSuchBeanDefin)
根据 type 同一类型出现多个(如果一个接口有两个实现类)则抛异常 -- NoUnquieBeanDefin
根据 name 和 type
@Autowired
private ApplicationContext ac;
@Test
public void BeanText(){
// 按类名字获取
EmployeeController employeeControllerByName = (EmployeeController) ac.getBean("employeeController");
// 按类型获取
EmployeeController employeeControllerByType = ac.getBean(EmployeeController.class);
// 按名字和类型获取
EmployeeController employeeControllerByNameAndType = ac.getBean("employeeController", EmployeeController.class);
System.out.println("我是employeeControllerByName" + employeeControllerByName);
System.out.println("我是employeeControllerByType" + employeeControllerByType);
System.out.println("我是employeeControllerByNameAndType" + employeeControllerByNameAndType);
}
测试发现这三种都是获取同一个 Bean 因为用的是单例设计模式
启动的时候会创建好所有的Bean 为了增加用户体验度
实际开发最多用的还是Autoware获取
3 Bean作用域问题
五种作用域:singleton prototype request session application
主要使用的是 singleton (单例,默认)和prototype(多例)
贴注解 Scope -- 默认为singleton 改成 prototype 变成多例但是变成多例就不归spring管理
贴注解Lazy -- 懒加载 -- 启动时不创建,用时才创建 (循环调用的时候用)
3 使用第三方Bean
例如Dom4j(解析xml的)
引入依赖
<!--Dom4j-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
创建个类作为spring容器的配置类
贴注解 证明他是配置类,需要在配置类中添加第三方 Bean 并对其配置
写个方法返回第三方对象(也需要new)贴上bean 将方法的返回值纳入Spring容器中 方法名小写就是首字母小写后的名字
@Configuration
public class MyBeanConfig {
@Bean
public SAXReader MyBeanConfig(){
return new SAXReader();
}
}
使用参数时,会从 Spring 容器中现根据类型再根据名字找到 Bean 并将其拿出作为实参传递给该方法的形参中
@Bean
public SAXReader MyBeanConfig(EmployeeController employeeController){
return new SAXReader();
}
@Autowired
private SAXReader saxReader;
@Test
public void SAXReaderTest() throws DocumentException {
Document document = saxReader.read(this.getClass().getClassLoader().getResource("test.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
住:这个bean是可以直接写在启动类中
三、SpringBoot 原理
SpringBoot 只是简化 Spring
1 两大优势:自动装配,起步依赖
I 起步依赖 -- 带 starter 的依赖
起步依赖的原理是maven依赖传递
II 自动装配
启动时如何将 jar 包中的所有对象加载到容器中?
以Gson为例
@Autowired
private Gson gson;
@Test
public void testJson(){
String json = gson.toJson(" {\"code\":1,\"msg\":\"success\",\"data\":null}");
System.out.println(json);
}
IIII 自动装配原理实操
创建springboot项目
添加web依赖
修改版本
创建几个类
使用装配类的方法将类存进spring
在其他项目(后用A项目表示)中导入写好的项目的pom.xml(后用B项目表示) -- 模拟引 jar 包的过程
这里要特别注意一下引入其他项目的时候总会自动给你改maven记得改回来!!!
此时B项目中的Bean并没有在A项目中显示
在 pom.xml 中引入B项目的依赖
注入
会发现找不到 Bean
原因:ComponentScan 注解 若没有 参数,则从当前自身所在位置开始向下扫描(包含平级,说人话就是扫描平级及子级的包)我们也可以传递指定路径,让其扫描指定的包,默认扫描的是自己项目下面 java 下面的
解决方法:
1 在主方法上贴 ComponentScan({"要扫描的路径1","要扫描的路径2"}) (不推荐)
问题:导入大量依赖就要扫描很多,繁琐,不利于维护且降低性能
@SpringBootApplication
@ComponentScan({"cn.wolfcode","com.example"})
public class SpringWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebConfigApplication.class, args);
}
}
2 在主方法上贴 @Import方式导入 -- 三种
1 导入普通类
@Import(类名.class)
@SpringBootApplication
@Import(TokenParser.class)
public class SpringWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebConfigApplication.class, args);
}
}
2 导入配置类 -- 导入配置类可以搞到他下面的所有 Bean
@Import(配置类名.class)
@SpringBootApplication
@Import(HeaderConfig.class)
public class SpringWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebConfigApplication.class, args);
}
}
3 导入 ImportSelector --- spring用的这个 因为spring并不知道第三方要装配什么类,这个类只有第三方知道,所以需要将要装的类写配置文件中。
先在自己的项目中创建 select 包写 ImportSelector 类 实现 ImportSelector 内的 selectImports 方法
需要返回一个字符串数组封装的就是需要装配类的全限定名
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.config.HeaderConfig"};
}
}
@Import(自己注册的ImportSelector .class)
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebConfigApplication.class, args);
}
}
第三方可以带一个配置文件告诉springboot需要自动装配的名字
在B项目中resources下新建一个配置文件
classNames=com.example.config.HeaderConfig,com.example.config.TokenParseConfig
这时候在ImportSelector 类中值需要读取配置文件信息,切割为字符串数组最后返回,这样就可以读取到这些配置类或Bean信息,此时需要再 springboot 导入其中读取第三方的配置文件信息,根据key获取文件中对应的Value(全类名)
创建 properties 集合对象,接受流
用流读取文件
将流转成集合并加载到properties中
通过key去读value拿到字符串
最后用 "," 切割成字符串数组返回给Import
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Properties properties = new Properties();
InputStream is = this.getClass().getClassLoader().getResourceAsStream("configuration.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
String url = properties.getProperty("classNames");
return url.split(",");
}
}
4 自定义注解 --- 在自己的项目写注解 -- 就是模仿了 这个@SpringBootApplication注解 这个真正模仿了springboot的自动装配
anno包下创建一个注解
除了正常要用的注解之外在封装一个@import(自己注册的ImportSelector .class)
直接将自定义的注解贴在启动类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface MyAutowareConfigration {
}
@SpringBootApplication
@MyAutowareConfigration
public class SpringWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebConfigApplication.class, args);
}
}
5 上面方法比较草率,SpringBoot 真正的封装比他高级一点,去可以被自动装配的 jar 包下去找 META-INF 的 spring.factories 其中有一个 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 只需要把自己需要自动装配的配置类或者普通类作为 value 传进去,就通过 springboot 自动装配到A项目中
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.HeaderConfig,com.example.config.TokenParseConfig
IV 主启动类解析 -- 源码分析
1 方法中的:SpringApplication.run(SpringWebConfigApplication.class, args);
作用:启动springboot程序后,将springboot 程序加载在tomcat服务器并启动服务器
参数解析:
参数1:传递了一个类,会在传递的类上面去找注解@SpringBootApplication(一个牛逼的复合注解)有这个注解的就是启动类
参数2:启动springboot程序后从外界(例如控制台)传递进来的一切参数都有 args 去接收tomcat
2 一个非常牛逼的注解 @SpringBootApplication
@SpringBootApplication 中的注解解释
元注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited
配置类:@SpringBootConfiguration -- 因为有他所有在主启动类中配置 Bean 有效
扫描注解(注意扫描范围):@ComponentScan() 里面那一大堆就是排除文件没啥用
可以自动装配的注解:@EnableAutoConfiguration 其中有 @Import()
3 查看 @EnableAutoConfiguration 下的 AutoConfigurationImportSelector.class
这个类实现了 ImportSelector 的子接口, 同时重写找到 ImportSelector 下的 selectImports 方法,最终返回一个 String[] 。
返回的字符串数组是从 AutoConfigurationImportSelector 类中下面的 getAutoConfigurationEntry 获取
这个字符串数组所需的路径则是通过 getAutoConfigurationEntry 下 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 中去获取,这个 configurations 的结果是所有要自动装配的类,但是这些类不是全都要装进去。
为了研究这些需要自动装配的类从哪来,所以要进入 getCandidateConfigurations 方法中,这个方法需要返回一个 configurations ,而这个 configurations 是从 SpringFactoriesLoader 中 的 loadFactoryNames 得到,而 loadFactoryNames 则是调用本类方法 loadSpringFactories ,由下面的代码中拿到
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
这些url从本地仓拿到依赖,经过遍历获取并添加所有带有META-INF 的 spring.factories的类
注意:会拿到很多自动装配的的类,但是并不是全都自动装配了,因为
V @Conditional 注解
作用:按照一定的条件去判断,满足指定条件之后才会将 Bean 对象注入到 Spring 的 IoC 容器中
位置:方法、类
常用的三种子注解:
-
@ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
@Configuration public class HeaderConfig { @Bean @ConditionalOnClass(name="io.jsonwebtoken.Jwts") public HeaderParser headerParser(){ return new HeaderParser(); } }
<!--JWT令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
@SpringBootTest public class AutoConfigurationTests { @Autowired private ApplicationContext applicationContext; @Test public void testHeaderParser(){ System.out.println(applicationContext.getBean(HeaderParser.class)); } }
-
@ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
@Configuration public class HeaderConfig { @Bean @ConditionalOnMissingBean public HeaderParser headerParser(){ return new HeaderParser(); } }
这个注解可以传参数,可以指定不存在的 bean(通过bean名和类)
@Configuration public class HeaderConfig { @Bean @ConditionalOnMissingBean(name="deptController2") public HeaderParser headerParser(){ return new HeaderParser(); } @Bean @ConditionalOnMissingBean(HeaderConfig.class) public HeaderParser headerParser(){ return new HeaderParser(); } }
如果指定的Bean存在则会报错,所以不要慌!
-
@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
先在 application.yml 配置文件中添加如下的键值对:
name: testName
在声明bean的时候就可以指定一个条件 @ConditionalOnProperty
@Configuration public class HeaderConfig { @Bean @ConditionalOnProperty(name ="name",havingValue = "testName") public HeaderParser headerParser(){ return new HeaderParser(); } @Bean public HeaderGenerator headerGenerator(){ return new HeaderGenerator(); } }