SpringBoot的基本使用2
1、IOC容器功能
1.1、添加组件
在 springboot 的默认配置文件 properties 中已经包含了很多的默认配置,这些默认配置能够帮我们完成大部分的配置,但是不能通过 properties 配置 bean,我们可以通过 Springboot 中的 @Configuration 和 @Bean 来创建 bean。
@Configuration 用于定义配置类,可替换 spring 的 bean xml 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法将会被扫描,并用于构建 bean 定义,初始化Spring容器。@Configuration 注解可以达到在 Spring 中使用 xml 配置文件的作用。
- @Configuration 可理解为用 spring 的时候的 xml 文件。
- @Bean 可理解为用 spring 的时候 xml 里面的 bean 标签。
平常在使用 spring 时,我们通常会通过一个类似于 bean.xml 的配置文件来配置 bean,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置创建的对象--> <bean id="user" class="test.User"></bean> </beans>
下面通过 springboot 的 @Configuration 来实现替代 spring 中配置文件的写法:
先创建一个 User 类:
package test01.entity; public class User { public void say() { System.out.println("hello"); } }
通过配置类来注册 bean:
//实际上配置类也会被注册为一个组件 @Configuration public class MyConifg { // 通过 @Bean 注解来注册bean。 // 以方法名作为组件的id,返回值就是组件在容器中的实例。默认是单例模式,即scope="singleton" @Bean public User testUser() { return new User(); } }
@Configuration 标注在类上,相当于把该类作为 spring 的 xml 配置文件中的<beans>,作用是配置 spring 容器(应用上下文)。
然后就可以在启动类中获取 bean 了:
@SpringBootApplication public class SpringbootStartApplication { public static void main(String[] args) { //这里返回的是IOC容器 ConfigurableApplicationContext run = SpringApplication.run(SpringbootStartApplication.class, args); User user =(User) run.getBean("testUser"); user.say(); User user2 =(User) run.getBean("testUser"); System.out.println(user == user2); //将输出true,因为通过bean获取到的对象都是单例对象 } }
2、自动配置原理之Condition
在 springboot 中,如果我们引入了一些依赖后,springboot 会自动帮我们创建这些依赖的 bean,那么 springboot 是如何知道我们是否引入了这些依赖,如何判断是否该帮我们创建 bean 的呢?其实这些都可以通过 Condition 实现。
Condition 是在 Spring 4.0 增加的条件判断功能,通过这个功能可以实现选择性的创建 Bean 操作。
通过 @Configuration 和 @Bean 我们可以创建 bean,通过 @Conditional 注解我们可以选择性地创建 bean。如下:
@Configuration public class MyConifg {
@Bean @Conditional() public User testUser() { return new User(); } }
点击 @Conditional() 注解进去可以看到这个注解需要一个 Class(因为是一个数组,所以可以导很多 Class ),这些 Class 必须都是 Condition 或者 Condition 的子类。而 Condition 就是核心的条件接口,点击进入 Condition 可以看到接口中只有一个 matches() 方法,返回值为 boolean 类型。
所以我们要使用 @Conditional() 注解就需要在注解中传入一个 Condition 条件接口的实现类,并且实现类要复写matches() ,返回 true 或者 false。如果返回的是 true,那么 User 对应的 Bean 将会被 Spring 容器创建,如果返回的是 false,那么容器则不会创建 User 对应的 Bean 。
下面创建 condition.ClassCondition 类,通过该类实现 Condition 接口并复写 matches() 方法。如下:
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 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //下面假设需求为判断是否引入了Jedis依赖,如果引入则创建Bean,否则不创建,以此来模拟springboot自动配置原理 //实现思路:判断redis.clients.jedis.Jedis.class文件是否存在 boolean flag = true; try { Class<?> cls = Class.forName("redis.clients.jedis.Jedis"); } catch (ClassNotFoundException e) { flag = false; } return flag; } }
然后在之前的 @Conditional 中放入创建好的 ClassCondition.class,如下:
@Configuration public class MyConifg { @Bean @Conditional(ClassCondition.class) public User testUser() { return new User(); } }
此时如果没有引入 Jedis 依赖,则使用 testUser bean 会报错,只有引入了 Jedis 依赖,testUser bean 才会被创建。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
由此我们便可以通过 @Conditional 实现通过条件来选择性地创建 bean,也就是在实现了 Condition 接口的类的 matches() 方法中,通过返回 false 或者 true 来控制在什么条件下需要创建 bean。
可参考:https://blog.csdn.net/m0_46114643/article/details/121780236
上面操作中我们是指定了某个依赖,当引入了该依赖时才对应地创建 bean,实际上也可以给通过参数的方式传递依赖名,即依赖不固定,判断当作为参数的依赖有引入时,即创建bean,同样可以参考:https://blog.csdn.net/m0_46114643/article/details/121780236
3、自动配置原理之@Import
SpringBoot 工程是不能直接获取 jar 包中定义的 Bean 的,也就是无法直接获取依赖所定义的 Bean,但我们使用 springboot 时,很明显只需引入各种依赖即可直接获通过 IOC 容器取到各个依赖的 Bean,那 springboot 是如何在引入依赖时就自动加载了各个依赖的 Bean 的呢?其实是通过 @Enable* 注解来动态加载的。
SpringBoot 中提供了很多 @Enable 开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用 @Import 注解导入一些配置类,实现Bean的动态加载。
@Enable* 底层依赖于 @Import 注解导入一些类,使用 @Import 导入的类会被 Spring 加载到 IOC 容器中。
@Import 提供四种用法:
- 导入Bean
- 导入配置类
- 导入 ImportSelector 的实现类。一般用于加载配置文件中的类
- 导入 ImportBeanDefinitionRegistrar 实现类。
可参考:https://blog.csdn.net/weixin_50390438/article/details/116866179
4、自动配置原理之@EnableAutoConfiguration
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载 这些配置类,初始化Bean。并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean。
可查看:https://blog.csdn.net/m0_51167384/article/details/115023479
5、切换内置web服务器
SpringBoot 的 Web 环境中默认使用 tomcat 作为内置服务器,其实 SpringBoot 提供了4种内置服务器供我们选择,包括:tomcat、Jetty、Netty、Undertow,我们可以很方便地通过依赖来切换使用不同的 web 服务器。
参考:https://blog.csdn.net/u012887259/article/details/123343627
6、springboot的监听机制
SpringBoot 的监听机制,其实是对 Java 提供的时间监听机制的封装。Java 中的时间监听机制定义了以下几个角色:
- 事件:Event,继承 java.util.EventObject 类的对象。
- 事件源:Source,任意对象 Object。
- 监听器:Listener,实现 java.util.EventListener 接口的对象。
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
监听器接口有以下:
- ApplicationContextInitializer
- SpringApplicationRunListener
- CommandLineRunner
- ApplicationRunner
其中,CommandLineRunner 和 ApplicationRunner 会在 springboot 项目启动后自动执行,而 ApplicationContextInitializer 和 SpringApplicationRunListener 想要被执行,需要手动在 META-INF/spring.factories 文件中进行配置。
下面示例,分别实现四个接口类,如下:
- ApplicationContextInitializer 接口实现
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; @Component public class MyApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("ApplicationContextInitializer....initialize"); } }
- SpringApplicationRunListener 接口实现
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationRunListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; public class MySpringApplicationRunListener implements SpringApplicationRunListener { public MySpringApplicationRunListener(SpringApplication application, String[] args) { } @Override public void starting() { System.out.println("starting...项目启动中"); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { System.out.println("environmentPrepared...环境对象开始准备"); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("contextPrepared...上下文对象开始准备"); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("contextLoaded...上下文对象开始加载"); } @Override public void started(ConfigurableApplicationContext context) { System.out.println("started...上下文对象加载完成"); } @Override public void running(ConfigurableApplicationContext context) { System.out.println("running...项目启动完成,开始运行"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("failed...项目启动失败"); } }
- CommandLineRunner 接口实现
import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.Arrays; @Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"); System.out.println(Arrays.asList(args)); } }
- ApplicationRunner 接口实现
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 当项目启动后执行run方法。 */ @Component public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run"); System.out.println(Arrays.asList(args.getSourceArgs())); } }
实现四个接口类后,我们启动项目可以看到,只有 CommandLineRunner 和 ApplicationRunner 的输出执行了,而 ApplicationContextInitializer 和 SpringApplicationRunListener 没有被执行。执行结果如下:
CommandLineRunner 和 ApplicationRunner 在项目启动时被自动调用,执行 run 方法,那么我们就可以在这里做一些事情,比如为了防止前期用户访问时没有数据,我们期望 Redis 在项目启动时能够把数据库的一些信息提前加载进来作为缓存,就可以把代码放在这里执行,也就是缓存预热。CommandLineRunner 和 ApplicationRunner 的 run 方法的参数实际上就是执行程序的参数,我们可以在 idea 的 program arguments 中进行配置添加。
而 ApplicationContextInitializer 和 SpringApplicationRunListener 想要被执行,需要我们进行配置。在 resources 目录下创建 META-INF/spring.factories,这个文件在 SpringBoot 启动时会自动被扫描到,它是一种键值对的方式。
在 META-INF/spring.factories 文件中添加以下配置:
# key为接口的全路径名,value为该接口的实现类的完整类名。请根据实际类名进行修改 org.springframework.context.ApplicationContextInitializer=test01.listener.MyApplicationContextInitializer org.springframework.boot.SpringApplicationRunListener=test01.listener.MySpringApplicationRunListener
重新启动项目,执行结果如下:
....
可以看到 ApplicationContextInitializer 的输出,它输出的位置在图标之后,项目准备 IOC 容器之前,我们可以在后期使用中去检测项目的一些资源是否存在。
SpringApplicationRunListener 的不同方法在不同生命周期阶段输出,在后期开发过程中我们就可以根据具体需求在不同的时机完成不同的需求。
7、springboot的启动流程
8、springboot监控
8.1、通过 actuator 监控
Spring Boot包含许多附加功能,可帮助您在将应用程序投入生产时监视和管理应用程序。 可以选择使用HTTP端点或JMX来管理和监控您的应用程序,自动应用于审计,健康和指标收集。一句话:springboot 提供用于监控和管理生产环境的模块
可通过 actuator 监控系统,只需引入依赖即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
启动项目后,访问 http://localhost:8080/actuator 即可看到一些监控信息,如下:
在spring boot 2.0以后,actuator默认只开启了info和health两个端点,要想使用其他的端点可以在配置文件中进行配置,比如在 application.properties 配置如下:
# 开启健康检查的完整信息 management.endpoint.health.show-details=always # 将所有的监控endpoint暴露出来 management.endpoints.web.exposure.include=*
8.2、通过Spring Boot Admin监控
上面通过 actuator 来查看监控信息都是一些 json 信息,不直观,我们可以通过 Spring Boot Admin 来直观地管理和监控 springboot 项目。Spring Boot Admin是一个社区项目,用于管理和监控您的Spring Boot 应用程序。
- Spring Boot Admin是一个开源社区项目,用于管理和监控SpringBoot应用程序。
- Spring Boot Admin 有两个角色,客户端(Client)和服务端(Server)。
- 应用程序作为Spring Boot Admin Client向为Spring Boot Admin Server注册
- Spring Boot Admin Server 的UI界面将Spring Boot Admin Client的Actuator Endpoint上的一些监控信息。
我们可以搭建一个 spring boot admin 的 server 端,用来管理和监控 springboot 的项目,搭建 spring boot admin 的 server 可参考:https://baijiahao.baidu.com/s?id=1722255512333993705&wfr=spider&for=pc
搭建 client 端其实只要是引入一些依赖和配置即可,也可通过以下模板快速搭建,如下:
依赖如下:
<dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-dependencies</artifactId> <version>${spring-boot-admin.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
最后在 client 的配置文件 application.properties 添加以下信息即可:
# 执行admin.server地址,以下端口和地址根据admin.server实际配置不同而不同 spring.boot.admin.client.url=http://localhost:9001 management.endpoint.health.show-details=always management.endpoints.web.exposure.include=*
client 和 server 都启动后,可以通过直接访问 server 端查看到监控的 springboot 项目的情况,比如上面我们可以访问:localhost:9001
9、springboot项目部署
SpringBoot 项目开发完毕后,支持两种方式部署到服务器:
- jar 包(官方推荐) :jar包方式启动,也就是使用spring boot内置的tomcat运行。
- war 包:需要先部署 tomcat 等服务器,然后再部署在这些服务器内
9.1、通过jar包方式部署(默认方式)
springboot 项目中默认打包是会打成 jar 包的,打成 jar 包后我们可以直接在命令行中执行该 jar 包,无需再额外安装 tomcat 等 servlet 容器。
只需要在项目的 pom.xml 文件中添加插件:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
然后就可以通过 idea 打包了:
在执行 package 之前可以先执行一个 clean,清空一下旧的包。
打包完成后,可以看到在 target 目录中生成了一个 jar 包:
然后就可以在命令行中直接执行该 jar 包了,例如:java -jar springbootTest01-1.0-SNAPSHOT.jar
执行后 tomcat 就被启动成功了,在浏览器中访问 http://localhost:8888/hello 可以看到接口访问正常。
我们也可以通过编写一些脚本来启动和停止应用,参考:https://blog.csdn.net/qq_34491508/article/details/91490434
9.2、通过war包方式部署
springboot 项目中默认是会打成 jar 包的,所以要想达成 war 包,首先需要改一下 pom.xml 文件,设置打包方式为 war,如下:
然后改造一下启动类即可,让启动类继承 SpringBootServletInitializer 并重写方法,如下:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication public class SpringbootStartApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SpringbootStartApplication.class); } public static void main(String[] args) { SpringApplication.run(SpringbootStartApplication.class, args); } }
最后就可以直接点击 package 按钮进行打包了,如下:
将打出来的 war 包直接放在 tomcat 安装目录的 webapps 目录下,启动 tomcat 即可访问到 springboot 项目了。
但是需要注意,此时在项目中 application.properties 配置文件中的一些配置就不会生效了,比如项目的端口,这些就应该在 tomcat 中配置才会生效。