当前位置: 首页 > news >正文

springboot速通

SpringBoot低级

SpringBoot概述

SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置,可以让开发人员不必配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

Spring缺点:

  • 配置繁琐
  • 依赖繁琐

SpringBoot功能:

  • 自动配置
    • springboot的自动配置是一个运行时的过程,考虑了众多因素才决定Spring配置应该用哪个,不该用哪个。该过程是SpringBoot自动完成的
  • 起步依赖
    • 就是Maven将具备某种过功能的某些包打包到一起,并提供了一些默认的功能,然后传给项目
  • 辅助功能
    • 提供了一些大型项目的非功能性特性,如嵌入式服务器、安全、指标、健康检测等

SpringBoot并不是对Spring功能的增强,而是提供了一种快速使用Spring的方式

SpringBoot快速入门

引导类Demo1Application

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Demo1Application {public static void main(String[] args) {SpringApplication.run(Demo1Application.class, args);}}

HelloApplication

package com.example.demo.demos.web;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloSpringBoot {@RequestMapping("/hello")public String hello(){return "hello Spring Boot 嘤嘤嘤";}
}

application.yml

server:port: 8080

index.html

<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

运行项目后,我们可以在浏览器网页栏搜索localhost:8080就会打开对应的web程序

小结

  • SpringBoot在创建项目时,使用jar的打包方式
  • SpringBoot的引导类,是项目入口,运行main方法就可以完全启动项目
  • 使用SpringBoot和Spring构建的项目,业务代码编写方式完全一样

SpringBoot起步依赖原理分析

SpringBoo;t提供的一种依赖管理机制,极大地简化了Spring应用程序的开发过程

  • 起步依赖是一种特殊的依赖,它将一组相关的依赖项打包在一起,方便开发者快速引入。例如,spring-boot-starter-web 包含了开发 Web 应用所需的所有依赖,如 Spring MVC、Tomcat 等。

  • 作用:

    • 简化依赖管理:无需依次添加每个组件,秩序引入一个起步依赖即可
    • 版本一致性:依赖版本由Spring Boot进行维护,确保版本兼容
    • 自动配置:引入岂不依赖后,SpringBoot会自动进行相关配置,减少手动配置的工作量
  • 原理:

    • Maven依赖传递机制:当引入一个起步依赖时,Maven 会自动解析并引入该依赖中声明的所有传递依赖。
    • 自动配置机制:Spring Boot 通过 @EnableAutoConfiguration 注解(通常由 @SpringBootApplication 指定)来启用自动配置。
  • 小结:

    • 在spring-boor-start-parent中定义了各种技术的版本信息,组合了一套最优搭配的技术版本
    • 在各种starter中,定义了完成该功能需要的坐标集合,其中大部分版本信息来自于父工程
    • 我们的工程继承parent,引入starter后,通过依赖传递,就可以简单方便的获取jar包,并且不会存在版本冲突等问题

SpringBoot配置

配置文件分类

SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置

  • 特点:
    • 全局作用域:配置项对整个应用程序有效
    • 自动加载:SpringBoot会自动加载application.propertiesapplication.yml中的配置
    • 优先级:同一级目录下,properties>yml>yaml
    • 默认配置文件名称:application

yaml

  • 是一种能够直观的被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互,可以被支持YAML库的不同变成语言导入
  • YAML文件是以数据为核心的,比传统的xml方式更加简洁
  • YAML文件的拓展名可以使用.yml或者.yaml

比较各种配置文件

  • properties:

  • server.port=8080
    servet.address=127.0.0.1
    
  • xml:

  • <server><port>8080</port><address>127.0.0.1</address>
    </server>
    
  • yml: (简洁,直观,以数据为核心)

  • server:port: 8080address: 127.0.0.1
    

YAML:基本语法

  • 大小写敏感
  • 数据值前边必须有空格作为分隔符
  • 使用缩进表示层级关系
  • 缩进下空格数目不重要,但必须有,并且相同层级的元素左侧对齐即可
  • ‘# ’表示注释,从这个字符一直到行尾,都会被解析器忽略

YAML:数据格式

  • 对象(map): 键值对的集合
  • 数组(address): 一组按次序排序的值
  • 纯量: 单个的、不可再分的值
server:port: 8080person:name: zhangsan# map的行内写法
person2: {name: zhangsan}address:- beijing- shanghai# 数组的行内写法
address2: [beijing,shanghai]mag1: 'hello \n world'   # 单引号忽略转义字符
map2: "hello \n world"   # 双引号识别转义字符# 参数引用
name = lisi
person:name: ${name}

读取配置文件内容

HelloController类

package com.example.demo.demos.web;import com.example.demo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@Value("${name}")private String name;@Value("${person.name}")private String name2;@Value("${address[0]}")private String address;@Value("${mag1}")private String mg1;@Value("${mag2}")private String mg2;@Autowiredprivate Environment evn;@Autowiredprivate Person person;// 方式1: 以@Value注解方式进行@RequestMapping("/hello3")public String hello3() {System.out.println(name);System.out.println(name2);System.out.println(address);System.out.println(mg1);System.out.println(mg2);System.out.println(person);return "hello3";}// 方式2: 以Environment类进行@RequestMapping("/hello2")public String hello2() {System.out.println(evn.getProperty("name"));System.out.println(evn.getProperty("address[0]"));System.out.println(evn.getProperty("mag1"));System.out.println(evn.getProperty("mag2"));System.out.println(evn.getProperty("person.name"));return evn.getProperty("person.name");}}

application.yml

server:port: 8080name: abcperson:name: zhangsamage: 200# map的行内写法
person2: {name: zhangsan}address:- beijing- shanghai# 数组的行内写法
address2: [beijing,shanghai]mag1: 'hello \n world'   # 单引号忽略转义字符
mag2: "hello \n world"   # 双引号识别转义字符

Person类

package com.example.demo;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "person")
public class Person {// 这些属性名要和对应的yaml里面的一一对应private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}

小结:三种方式获取数据

  1. 用@Value注解修饰属性
  2. 使用@Autowired注入一个Environment对象,然后通过该对象的getProperty属性获取
  3. 定义一个新Bean容器管理的类,在类上添加注解@Configuration,默认识别第一级的,可以通过修改参数prefix定义前缀

profile

我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务器端口等等配置都不同,如果每次打包都要修改配置文件,那么非常麻烦,profile功能就是来进行动态配置切换的

  • profile配置方式
    • 多profile文件方式
    • yml多文档方式
  • prifile激活方式
    • 配置文件
    • 虚拟机参数
    • 命令行参数

多profile文件方式

  • application.properties
name = abc
spring.profiles.active==dev
  • application-dev.properties
spring.profiles.active==dev
  • application-pro.properties
spring.profiles.active==pro
  • application-test.properties
spring.profiles.active==test

yml多文档方式

# 通过激活不同的 Profile,Spring Boot 会加载与该 Profile 对应的配置文件
---
server:port: 8081
spring:config:activate:on-profile: pro
---
server:port: 8082
spring:config:activate:on-profile: dev
---
server:port: 8083
spring:config:activate:on-profile: test
---spring:profiles:active: dev

Profile-小结

  1. profile 是用来完成不同环境下,配置动态切换功能的。
  2. profile 配置方式
    • 多profile文件方式:提供多个配置文件,每个代表一种环境。
      • application-dev.properties/yml 开发环境
      • application-test.properties/yml 测试环境
      • application-pro.properties/yml 生产环境
    • yml多文档方式:
      • 在yml中使用 --- 分隔不同配置
  3. profile 激活方式
    • 配置文件:再配置文件中配置:spring.profiles.active=dev
    • 虚拟机参数:在VM options 指定:-Dspring.profiles.active=dev
    • 命令行参数:java -jar xxx.jar --spring.profiles.active=dev

内部配置加载顺序

  • 我们之前的配置文件都是放在配置文件,都是放在resources文件夹,根据当前目录下,优先级的高低判断谁先被加载,比如properties > yml > yaml

  • 实际开发这种,我们写的配置文件并不是都放在一起的,甚至“东一个,西一个”,这时候就需要判断谁先被加载

SpringBoot程序启动时,会从以下位置加载配置文件

  1. file:/config/: 当前项目下的/config目录下
  2. file:/ 当前项目的根目录
  3. classpath:/config: classpath的/config目录下
  4. classpath: classpath的根目录下

加载顺序为上文的排序顺序,高优先级配置的属性会生效

小结

  • 内部配置,是寄托于IDEA编辑器实现的。对于编写配置(命令,实现功能),我们不仅可以在IDEA中实现,还可以打开黑窗口,输入命令,在外部实现。例如把spring项目打成jar包,在“黑窗口”运行,大家可以看我SpringBoot配置–Profile这篇博客的命令行参数内容。

对于内部配置加载顺序,记住三点

  1. 靠近项目,优先被加载

  2. 具有config下的文件,在同等情况下,优先被加载

  3. 同一级目录下,优先级:properties >yml >yaml

  4. 上面配置的第一种和第二种是不会被打包到对应的jar包里面的,因此如果我们使用package的jar包运行只会识别classpath路径下的配置文件

外部配置加载顺序

外部配置一般都是通过命令行参数实现,我们可以运行

java -jar yourapp.jar --serve.port=8090

后面那部分就是修改我们的端口号,多个参数可以之间空格隔开

java -jar yourapp.jar --serve.port=9090 --serve.servelet.context-path=/hehe

甚至我们可以直接加载外部配置文件

java -jar yourapp.jar --spring.config.location="路径"

当然,我们也可以直接将配置属性复制到对应的jar包的同一目录下,jar包运行时也会自动加载

SpringBoot官网外部配置加载顺序

  • 默认属性(通过设置SpringApplication.setDefaultProperties指定)。
  • 在你的@Configuration类上的@PropertySource注释。请注意,这些属性源在应用上下文刷新之前不会添加到Environment中。因此,这对于配置某些属性(如logging.和spring.main.)来说为时已晚,因为这些属性在应用上下文刷新之前被读取。
  • 配置数据(例如application.properties文件)。
  • 随机值属性源(RandomValuePropertySource),其属性仅在random.*。
  • 操作系统环境变量。
  • Java系统属性(System.getProperties())。
  • 来自java:comp/env的JNDI属性。
  • ServletContext init参数。
  • 来自SPRING_APPLICATION_JSON的属性(内联JSON嵌入在环境变量或系统属性中)。
  • 命令行参数。
  • 测试中的属性属性。在@SpringBootTest和用于测试应用特定切片的测试注释中可用。
  • 测试中的@DynamicPropertySource注释。
  • 测试中的@TestPropertySource注释。
  • 当devtools激活时,在$HOME/.config/spring-boot目录中的Devtools全局设置属性。

越往下优先级越高

SpringBoot整合其他框架

Junit

实现步骤

  1. 搭建SpringBoot工程
  2. 引入starter-test依赖
  3. 编写测试类
  4. 添加测试相关注解
    1. RunWith(SpringRunner.class)
    2. SpringBootTest(classer = 启动类.class)
  5. 编写测试方法

如果我们是在源根目录下写的test测试类,那么测试类中的注解@SpringBootTest的括号中不需要添加任何信息

UserService类

package com.lele.springboottest;import org.springframework.stereotype.Service;@Service
public class UserService {public void add(){System.out.println("add...");}
}

测试类UserServiceTest

package com.lele.springboottest;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;/*** UserService的测试类*/@SpringBootTest(classes = SpringbootTestApplication.class)
public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void testadd(){userService.add();System.out.println("hello ");}
}

mybatis

实现步骤

  1. 搭建SpringBoot工程
  2. 引入mybatis起步依赖,添加mysql驱动
  3. 编写DataSource 和MyBatis相关配置
  4. 定义表和实体类
  5. 编写dao和mapper文件/纯注解开发
  6. 测试

首先创建数据库并添加两条记录

create database springboot;
use springboot;
create table t_user(id int(11) NOT NULL primary key auto_increment,username varchar(32) default null,password varchar(32) default null
);insert into t_user (id,username,password) values(1,'zhangsan',123),(2,'lisi',234);

​ 我们在下面实现了两种方式的mybatis的注入:注解xml文件

User类

package com.lele.springbotmybatis.domain;import org.springframework.stereotype.Component;public class User {int id;String name;int age;public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

UserMapper接口

package com.lele.springbotmybatis.mapper;import com.lele.springbotmybatis.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;import java.util.List;@Mapper
@Repository
public interface UserMapper {@Select("select * from t_user")public List<User> findAll();
}

UserXmlMapper接口

package com.lele.springbotmybatis.mapper;import com.lele.springbotmybatis.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;import java.util.List;@Mapper
@Repository
public interface UserMapper {@Select("select * from t_user")public List<User> findAll();
}

启动类省略,都一样没任何变化

UserXmlMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mbatis-3-mapper.dtd">
<mapper namespace="com.lele.springbotmybatis.mapper.UserXmlMapper"><select id="findAll" resultType="user">select * from t_user</select>
</mapper>

appliaction.yml

# dataSource
spring:datasource:url: jdbc:mysql://localhost:3306/springbootusername: rootpassword: 159357258zx.driver-class-name: com.mysql.cj.jdbc.Drivermybatis:mapper-locations: classpath:mapper/*Mapper.xmltype-aliases-package: com.lele.springbotmybatis.domain.User
# config-location:        # 指定mybatis核心配置文件

测试类

package com.lele.springbotmybatis;import com.lele.springbotmybatis.domain.User;
import com.lele.springbotmybatis.mapper.UserMapper;
import com.lele.springbotmybatis.mapper.UserXmlMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
class SpringbotMybatisApplicationTests{@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserXmlMapper userXmlMapper;@Testpublic void testfindALl() {List<User> all = userMapper.findAll();for (User user : all) {System.out.println(user);}}@Testpublic void testfindALl2() {List<User> all = userXmlMapper.findAll2();for (User user : all) {System.out.println(user);}}}

SpringBoot高级(原理分析)

SpringBoot其实是对Spring的高度封装,虽然用起来很方便,但是我们不知道它为什么这么方便、或者它在哪方面还能进行拓展。通过学习SpringBoot的原理分析,我们能够更好的使用SpringBoot,同时也能学到很多好的设计思想

自动配置

Condition

  • Condition:在Spring4.0增加的条件判断功能,可以实现选择性的创建Bean操作

思考:SpringBoot是如何知道要创建哪个依赖的?

可以看到,当我没有导入redis依赖而直接获取Bean时,运行出错

package com.lele.springbootcondition;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {// 启动Springboot项目,并返回IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);// 获取Bean,redisTemplateObject redisTemplate = context.getBean("redisTemplate");System.out.println(redisTemplate);context.close();}
}

错误信息:No bean named ‘redisTemplate’ available

但是当我添加依赖后,成功输出

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
  • 因此我们思考,Condition可能与依赖有关,但它怎么知道我导入了相应的坐标呢

案例:在Spring的IOC容器中有一个User的Bean,现要求导入Jedis坐标后加载Bean,没导入则不加载

核心配置类UserConfig

package com.lele.springbootcondition.config;import com.lele.springbootcondition.Condition.ClassCondition;
import com.lele.springbootcondition.Condition.ConditionOnClass;
import com.lele.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.alibaba.fastjson.JSON;@Configuration
public class UserConfig {// 下面可以添加一个注解@Conditional,它需要传入一个Condition接口的实现类// 接口Condtion里面定义了一个方法,我的实现类里面需要重写该方法// 该方法返回一个boolean类型,true表示加载Bean,否则不加载@Bean//@Conditional(ClassCondition.class)@ConditionOnClass("com.alibaba.fastjson.JSON")public User user(){return new User();}
}

第一种方式 是直接使用直接使用自带的@Conditional

  • //@Conditional(ClassCondition.class)
    

然后定义一个实现Condition接口的方法

ClassCondition类

package com.lele.springbootcondition.Condition;import com.alibaba.fastjson.JSON;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;import java.util.Map;public class ClassCondition implements Condition {/**** @param context   上下文对象,用于获取环境、IOC容器、ClassLoader对象* @param metadata      注解元对象,用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 1. 需求:导入Jedis类则加载Bean,否则不加载/*boolean flag;try {Class<?> cls = Class.forName("com.alibaba.fastjson.JSON");flag = true;} catch (ClassNotFoundException e) {flag = false;}return flag;*///2. 需求: 导入通过注解属性值value指定的坐标后创建Bean// 获取注解属性值 valueMap<String, Object> map =metadata.getAnnotationAttributes(ConditionOnClass.class.getName());// System.out.println(map);String[] value = (String[])map.get("value");boolean flag = false;try {for (String className : value) {Class<?> cls = Class.forName(className);}flag = true;} catch (ClassNotFoundException e) {flag = false;}return flag;}
}

第二种方式是我们自己定义一个注解,但还是要用系统的注解@Conditino覆盖我们自己的

ConditionOnClass注解

package com.lele.springbootcondition.Condition;import org.springframework.context.annotation.Conditional;import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {String[] value();
}

启动类SpringbootConditionApplication

package com.lele.springbootcondition;import com.lele.springbootcondition.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {// 启动Springboot项目,并返回IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);//        // 获取Bean,redisTemplate
//        Object redisTemplate = context.getBean("redisTemplate");
//        System.out.println(redisTemplate);Object bean = context.getBean("user");System.out.println(bean);context.close();}
}
  • 除了Conditional注解外,我们能还有其他注解就不一一演示了,都放在小结里面

切换内置Web服务器

SpringBoot的web环境中默认使用tomcat作为内置服务器,其中SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换

我们可以在外部库/Maven:org:springframework.boot/spring-boot-autoconfigure-2.6.13,jar

/org/springframework/boot/autofigure/web/embedded里面

  1. Jetty
  2. Metty
  3. Tomcat
  4. Undertow

我们默认都是选择tomcat进行切换的,但是如果想要切换,可以修改配置文件pom.xml,在web的大包里面排除Tomcat的,然后外面再导入对应其他web内置服务器即可

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency

@Enable*注解

  • SpringBoot种提供了很多Enable开头的注解,这些注解都是用于东岸提开启某些功能的。而其底层原理就是使用@Import注解导入一些配置类,实现Bean的动态加载

思考

SpringBoot工程是否可以直接获取Jar包种定义的Bean?

答案肯定是不行的,SpringBoot无法直接引用别人jar包里的Bean

那么为什么我们之前引入一个Redis的起步依赖就可以直接获取到RedisTemplate呢?

演示

接下来我们引入了两个模块工程springboot-enable和springboot-enable-other

springboot-enable-other的作用单纯就是提供bean类

  • User类
package com.xh.config;public class UserConfig {
}
  • UserConfig配置类
package com.xh.config;import com.xh.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/** 表示配置类 */
@Configuration
public class UserConfig {/** 注入 */@Beanpublic User user(){return new User();}
}

然后就是在springboot-enable工程中pom.xml中添加新的依赖

        <dependency><groupId>com.xh</groupId><artifactId>springboot-enable-other</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

然后修改启动类中添加获取User类 的代码

public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}

运行项目,然后就报错了,提示错误信息是No qualfied bean named ‘user’ available

  • 通过演示我们可以看到SpringBoot不能直接获取我们在其他工程中定义的Bean

  • 原因:在启动类中@SpringBootApplication注解中有一个@ComponentScan注解,这个注解扫描的范围是当前引导类所在包及其子包

  • 我们项目的引导类包路径为:om.xh.springbootenable
    而 UserConfig 所在的包路径为:com.xh.config

  • @ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
    ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    

方案:

  1. 使用@ComponentScan:我们可以在引导类上使用@ComponentScan注解扫描配置类所在的包
@SpringBootApplication
@ComponentScan("com.xh.config")
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

这样的方案虽然可以解决这个问题,但是看起来不太行,如果获取的包的特别多的话,那么可能太多了,所以这种方案不推荐

  1. 使用@Import注解:被@Import注解所导入的类,都会被Spring创建,并放入IOC容器中,如图可以看到@Import注解的value值是一个数组,可以传多个值
// @Import的定义
package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {Class<?>[] value();
}

修改引导类

@SpringBootApplication
//@ComponentScan("com.xh.config")
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

启动类后可以正常运行,方案稍微比方案一好一些,需要记住很多类的名字,所以仍然不是很方便

  1. 对@Import注解进行封装

对springboot-enable-other工程中编写注解@EnableUser,在注解中使用@Import注解导入userConfig,并且添加@Import的元注解

package com.xh.config;import org.springframework.context.annotation.Import;
import java.lang.annotation.*;@Import(UserConfig.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableUser {
}

修改springboot-enable工程的引导类,现在可以使用我们自定义的注解

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

这种自定义注解的方式和方案二是一个原理,只不过是在使用上简化了一下。

@Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中,而@Import提供4种用法。SpringbootAppliation中选择用第三种实现方式

  1. 导入Bean
  2. 导入配置类
  3. 导入ImportSelector实现类,一般用于加载配置文件中的类
  4. 导入ImportBeanDefinitionRegistrat实现类

导入Bean

/*** Import 4 种用法* 1. 导入Bean* 2. 导入配置类* 3. 导入 ImportSelector 实现类。一般用于加载配置文件中的类* 4. 导入 ImportBeanDefinitionRegistrar 实现类。*/
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
//@EnableUser
@Import(User.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值Map<String, User> map = context.getBeansOfType(User.class);System.out.println("map:" + map);}
}

导入配置类

这里我们创建了一个新的对象Role,看看一个配置类是否可以加载两个对象

Role

package com.xh.domain;public class Role {
}

在UserConfig里面添加对应方法

    @Beanpublic Role role(){return new Role();}

然后在启动类上修改为对应的注解

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
//        Map<String, User> map = context.getBeansOfType(User.class);
//        System.out.println("map:" + map);}
}

导入ImportSelector实现类

首先我们先来看看ImportSelector接口,里面有一个方法selectImports,参数是一个注解元对象,可以用来获取一些注解的属性等,然后返回String类型数组,一些类的全限定名

package org.springframework.context.annotation;import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);
}

然后我们就来实现该接口

package com.xh.config;import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 放入 user、role 的全限定名之后,就会自动去加载 user 和对应的 role 了// return new String[]{"com.xh.domain.User", "com.xh.domain.Role"};// 或者可以使用方法获取// 或者我们可以直接导入配置文件,然后从配置文件上动态获取return new String[]{User.class.getName(), Role.class.getName()};}
}

最后修改为对应注解

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
//@Import(UserConfig.class)
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);
//        // 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
//        Map<String, User> map = context.getBeansOfType(User.class);
//        System.out.println("map:" + map);}
}

导入ImportBeanDefinitionRegistrar实现类

package com.example.springbootenableother.config;import com.example.springbootenableother.domain.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;/*** @author XH* @create 2021/12/13* @since 1.0.0*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//在 IOC 容器中注册 Bean,Bean 名称为 user,类型为 User.class// 获取 beanDefinitionAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();// 注入 userregistry.registerBeanDefinition("user", beanDefinition);}
}
@SpringBootApplication
//@EnableUser
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 根据类型获取Object user = context.getBean(User.class);System.out.println(user);// 根据 Bean 名称获取Object user1 = context.getBean("user");System.out.println(user1);
//        Object role = context.getBean(Role.class);
//        System.out.println(role);}
}

@EnableAutoConfiguration注解

  • @EnableAutoConfiguration注解内部使用@Import(AutoConfigurationImportSelector.class)来加载配置类
  • 配置文案金位置:META/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
  • 并不是所有的Bean都会被初始化,在配置类中使用了Condition注解来加载满足条件的Bean

小结

  • 自定义条件
    • 定义条件类:自定义类实现接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数
      • context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等
      • metadata:元数据对象,用于获取注解属性
    • 判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解
  • SpringBoot提供的其他常用条件注解:
    • ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
    • ConditionalOnClass: 判断环境中是否有对应字节码文件才初始化Bean
    • ConditionalOnMissingBean: 判断环境中没有对应Bean才初始化
  • 切换web内置服务器:Jetty、Metty、Tomcat、UnderTow
  • SpringBoot 底层是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。
    例如 @SpringBootApplication 其中的 @EnableAutoConfiguration 就使用了 @Import 来实现导入其他的类
  • @EnableAutoConfiguration注解通过注解的方式启动自动配置类

SpringBoot 监听机制

SpringBoot的监听机制,其实就是对java提供的事件监听机制的封装

java监听机制

java中的事件监听机制定义了以下几个角色

  • 事件:Event,继承java.util.EventObject类的对象
  • 事件源:Source,任意对象Object
  • 监听器:Listener,实现java.util.EventListener接口的对象

SpringBoot监听机制

SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作

ApplicationContextInitializer、SpringApplicationRunListener、CommandLineRunner、AapplicationRunner

基于上面四个接口我们分别实现了四个类

  • MyApplicationContextInitializer
package com.lele.springbootlistener.listener;import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("ApplicationContextInitializer... + initializing...");}
}
  • MyApplicationRunner
package com.lele.springbootlistener.listener;import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;@Component
public class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("MyApplicationRunner run");}
}
  • CommandLineRunner
package com.lele.springbootlistener.listener;import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("MyCommandLineRunner.run(String[])");}
}
  • MySpringApplicationRunListener
package com.lele.springbootlistener.listener;import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;import java.time.Duration;
// 这里一开始和前面一样可以使用@Component注解并且去掉构造函数
// 但是我们看springboot自己定义的发现需要两个参数和构造但是又不能自动注入,所以要驱动@Component注解
public class MySpringApplicationRunListener implements SpringApplicationRunListener {public MySpringApplicationRunListener(SpringApplication application, String[] args) {}@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {System.out.println("MySpringApplicationRunListener starting");}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {System.out.println("MySpringApplicationRunListener environmentPrepared");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("MySpringApplicationRunListener contextLoaded");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("MySpringApplicationRunListener contextPrepared");}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("MySpringApplicationRunListener started");}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("MySpringApplicationRunListener ready");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("MySpringApplicationRunListener failed");}
}

定义好后我们可以启动项目,发现只有MyApplicationRunner和MySpringApplicationRunListener成功运行了,这表明系统会自动运行,但是如果我们想要运行另外两个该如何实现呢?

  • 在classpath:(即resources目录下)新建一个META-INF/spring.factories,系统会自动调用里面的配置文件

  • 配置文件里面格式context.listener.classes = [ 监听器全类名 ],如:

  • org.springframework.context.ApplicationContextInitializer=\com.lele.springbootlistener.listener.MyApplicationContextInitializer
    org.springframework.boot.SpringApplicationRunListener=\com.lele.springbootlistener.listener.MySpringApplicationRunListener
    

SpringBoot启动流程分析

┌───────────────────────────────────────────┐
│ 1. main() 调用入口                         │
│                                           │
│ public static void main(String[] args) {  │
│     SpringApplication.run(App.class, args);│
│ }                                         │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 2. 创建 SpringApplication 实例           │
│   - 绑定 sources、listeners、initializers │
│   - 推断应用类型(SERVLET/REACTIVE/NONE) │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 3. 预备阶段(prepareEnvironment)         │
│   - 创建并配置 Environment(PropertySources)│
│   - 应用 `EnvironmentPostProcessor`       │
│   - 触发 `ApplicationEnvironmentPreparedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 4. 创建 ApplicationContext               │
│   - 根据应用类型选择:                     │
│     • AnnotationConfigServletWebServerApplicationContext │
│     • AnnotationConfigReactiveWebServerApplicationContext│
│     • AnnotationConfigApplicationContext  │
│   - 触发 `ApplicationContextInitializedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 5. 上下文预处理(prepareContext)         │
│   - 注册 `BeanDefinition`(扫描 @SpringBootConfiguration 来源)│
│   - 执行 `ApplicationContextInitializer`  │
│   - 触发 `ContextRefreshedEvent` 前的事件│
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 6. 刷新上下文(refresh)                  │
│   • BeanFactory 后处理:                     │
│     – `BeanDefinitionRegistryPostProcessor`│
│     – `BeanFactoryPostProcessor`           │
│   • 注册 BeanPostProcessor                │
│   • 初始化单例 Bean(依赖注入、生命周期回调) │
│   • 触发 `ContextRefreshedEvent`          │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 7. 启动 Web 容器(仅 Servlet/Reactive)    │
│   - 嵌入式 Tomcat/Jetty/Netty 启动         │
│   - 触发 `ServletWebServerInitializedEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 8. 执行 Runner(CommandLineRunner、ApplicationRunner)│
│   - 按 `@Order` 顺序执行                    │
│   - 触发 `ApplicationStartedEvent` & `ApplicationReadyEvent` │
└───────────────────────────────────────────┘↓
┌───────────────────────────────────────────┐
│ 9. 应用启动完成,进入正常运行态           │
└───────────────────────────────────────────┘

SpringBoot监控概述

SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性、日志信息等。

使用步骤

  1. 导入依赖坐标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 访问http://localhost:8080/acruator

可以看到所有支持的连接,默认只有:

/actuator
/actuator/health
/actuator/health/{component}
/actuator/health/{component}/{instance}
/actuator/info

具体的使用方法

  • 引入上述的依赖

  • 通过下面的配置启用所有的监控端点,这些端点默认是禁用的

  • management:endpoints:web:exposure:include: "*"
    
  • 或者启用下面配置开启部分监控端点

  • management:endpoints:web:exposure:exclude: beans,trace
    
  • “*”号代表启用所有的监控端点,可以单独启用,例如,healthinfometrics

  • Actuator 默认所有的监控点路径都在/actuator/*,当然如果有需要这个路径也支持定制。

  • management:endpoints:web:base-path: /manage #记得要加上/
    
  • 设置完重启后,再次访问地址就会变成/manage/*

Autuator的REST接口

Actuator监控分为两类:原生端点和用户自定义端点

  • 自定义端点:主要指拓展性,根据自己的实际应用,比如Redis、MyBatis等
  • 原生端点:在应用程序里提供众多Web接口,通过它们了解应用程序运行时的内部状态
    • 应用配置类:可以查看应用在运行期的静态信息:例如自动配置信息、加载的springbean信息、yml文件配置信息、环境信息等
    • 度量指标类:主要是运行期的动态信息,例如堆栈、请求连等
    • 操作控制类:主要是指shurdown,用户可以发送一个请求将应用的控制功能关闭
GET/auditevents显示应用暴露的审计事件 (比如认证进入、订单失败)
GET/beans展示了所有 bean 的别名、类型、是否单例、类的地址、依赖等信息
GET/conditions使用 conditions 可以在应用运行时查看代码了某个配置在什么条件下生效,或者某个自动配置为什么没有生效。
GET/configprops描述配置属性(包含默认值)如何注入Bean
GET/env获取全部环境属性
GET/env/{name}根据名称获取特定的环境属性值
GET/flyway提供一份 Flyway 数据库迁移信息
GET/liquidbase显示Liquibase 数据库迁移的纤细信息
GET/health报告应用程序的健康指标,这些值由 HealthIndicator 的实现类提供,用来检查应用的运行状态,“UP”表示健康,"down"不健康
GET/heapdump返回一个 GZip 压缩的 JVM 堆 dump
GET/httptrace显示HTTP足迹,最近100个HTTP request/repsponse
GET/info获取我们自己配置在配置文件中以 info 开头的配置信息
GET/logfile返回log file中的内容(如果 logging.file 或者 logging.path 被设置)
GET/loggers可以查看当前应用的日志级别等信息
GET/metrics是一个非常重要的监控端点,其监控内容覆盖了 JVM 内存、堆、类加载、处理器和 tomcat 容器等一些重要指标
GET/metrics/{name}报告指定名称的应用程序度量值
GET/scheduledtasks展示应用中的定时任务信息
GET/sessions如果我们使用了 Spring Session 展示应用中的 HTTP sessions 信息
POST/shutdown关闭应用程序,要求endpoints.shutdown.enabled设置为true
GET/mappings描述全部的 URI路径,以及它们和控制器(包含Actuator端点)的映射关系
GET/threaddump生成当前线程活动的快照,主要展示了线程名、线程ID、线程的状态、是否等待锁资源等信息。

SpringBoot监控-图形化界面使用

这个要针对两个模块springboot-admin-server和springboot-admin-clients进行

对于springboot-admin-server

  • 首先我们要导入依赖坐标admin-starter-server

  • <dependency><groupId>de.codecentric</groupId><artifactId>spring-boot-admin-starter-server</artifactId></dependency>
    
  • 然后在引导类上启用监控功能@EnableAdminServer即可

  • package com.lele.springbootadminserver;import de.codecentric.boot.admin.server.config.EnableAdminServer;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;@EnableAdminServer
    @SpringBootApplication
    public class SpringbootAdminServerApplication {public static void main(String[] args) {SpringApplication.run(SpringbootAdminServerApplication.class, args);}}
    

对于springboot-admin-clients

  • 导入依赖坐标admin-starter-client

  • <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>de.codecentric</groupId><artifactId>spring-boot-admin-starter-client</artifactId></dependency>
    
  • 配置相关信息:server地址、显示health详细信息、开启所有监控

  • # 执行admin.server地址
    spring.boot.admin.client.url=http://localhost:9000# 这一句是表示在health那里显示详细信息
    management.endpoint.health.show-details=always# 开启所有监控
    management.endpoints.web.exposure.include=*server.port=8080
    

SpringBoot项目部署

SpringBoot项目开发支持两种方式部署到服务器

  • jar包
  • war包

注意:为了防止打包出来的名字过于繁琐,我们可以先定义一个finalName标签进行改名

<build><finalName>springboot-package</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins>
</build>
  • jar包

    • 利用的是本地的Tomcat进行打包,使用命令行mvn clean package进行打包或者到右边Maven的生存期package

    • 然后就可以运行jar包java -jar finalName.jar

  • war包

    • 利用外部的Tomcat进行打包部署到其他Tomcat服务器上

    • 首先要修改配置类

    • @SpringBootApplication() 
      public class SpringbootstudyApplication extends SpringBootServletInitializer {//继承SpringBootServletInitializer 重写 configure@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder){return builder.sources(SpringbootstudyApplication.class);}public static void main(String[] args) {SpringApplication.run(SpringbootstudyApplication.class, args);}
      }
    • 然后还要在pom.xml里面修改打包方式<package>war</package>就可以了

相关文章:

  • 如何用ai设计测试
  • 多线程并发编程硬核指南:从互斥锁到生产者模型的全场景实战与原理揭秘
  • c语言学习_函数4
  • 如何在软件公司推行狼性文化?可能存在哪些困难?
  • 手机SIM卡通话中随时插入录音语音片段(Windows方案)
  • 自然语言处理NLP 学习笔记
  • 【狂飙AGI】第5课:前沿技术-文生图(系列1)
  • MIT线性代数第一讲笔记
  • 适合 Acrobat DC 文件类型解析
  • Windows平台轻量级图片处理工具实测:功能与体验分享
  • 嵌入式知识篇---三种坐标系
  • ZW3D 二次开发-制作插件
  • 动态规划之爬楼梯(二)
  • SQL Developer 表复制
  • Vue相关知识2
  • 【嵌入式ARM汇编基础】-快速了解ARM汇编语言
  • 耗时3小时,把这两天做好的爬虫程序,用Python封装成exe文件
  • Rust语言典型并发模式小结
  • Day32
  • verl multi-node train 教程
  • 网站接入服务单位名称/南宁优化网站网络服务
  • 手机什么网站可以设计楼房/色盲测试图片60张
  • iis 网站模板下载/网站关键词优化
  • wordpress 整站下载/发布
  • 日本网站制作/关键词优化排名第一
  • 网站模板制作与安装教程/网络推广有多少种方法