Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
创建Spring Boot项目,勾选Spring Web
①创建User类
package com.aiw.springbootcondition.pojo;
public class User {
}
②创建配置类
package com.aiw.springbootcondition.config;
import com.aiw.springbootcondition.condition.ClassCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.aiw.springbootcondition.pojo.User;
@Configuration // 标识为配置类
public class UserConfig {
@Bean
@Conditional(ClassCondition.class)
public User user() {
return new User();
}
}
③实现Condition接口类
package com.aiw.springbootcondition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 需求:导入Jedis坐标后创建Bean
// 思路:判断redis.clients.jedis.Jedis文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
e.printStackTrace();
flag = false;
}
return flag;
}
}
④主程序
package com.aiw.springbootcondition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.aiw.springbootcondition.pojo.User;
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
// 启动Spring Boot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
Object user = context.getBean("user", User.class);
System.out.println(user);
}
}
若在pom.xml文件中导入了如下jedis坐标,则能获取到User Bean,否则获取不到
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
目前到此,只实现了一半需求,继续完善如下,新建注解
package com.aiw.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();
}
修改ClassCondition.java
package com.aiw.springbootcondition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
public class ClassCondition implements Condition {
/**
* @param context 上下文对象。用于获取环境,IOC容器,CLassLoader对象
* @param metadata 注解元对象。用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// // 需求:导入Jedis坐标后创建Bean
// // 思路:判断redis.clients.jedis.Jedis文件是否存在
// boolean flag = true;
// try {
// Class> cls = Class.forName("redis.clients.jedis.Jedis");
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// flag = false;
// }
// return flag;
// 需求:导入通过注解属性值value指定坐标后创建Bean
// 获取注解属性值 value
MultiValueMap<String, Object> map = metadata.getAllAnnotationAttributes(ConditionOnClass.class.getName());
String[] value = (String[]) map.get("value").get(0);
boolean flag = true;
try {
for (String className : value) {
Class<?> cls = Class.forName(className);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
flag = false;
}
return flag;
}
}
再修改UserConfig.java
package com.aiw.springbootcondition.config;
import com.aiw.springbootcondition.condition.ConditionOnClass;
import com.aiw.springbootcondition.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 标识为配置类
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
@ConditionOnClass("redis.clients.jedis.Jedis")
public User user() {
return new User();
}
}
这样只有导入了Jedis坐标,才会创建Bean
Spring Boot已经提供了许多的ConditionalOnXxx注解,在Maven: org.springframework.boot:spring-boot-autoconfigure-2.7.1
中,如下:
使用Spring Boot提供Conditional注解,测试如下,修改UserConfig.java
package com.aiw.springbootcondition.config;
import com.aiw.springbootcondition.pojo.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 标识为配置类
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
// @ConditionOnClass("redis.clients.jedis.Jedis")
public User user() {
return new User();
}
@Bean
@ConditionalOnProperty(name = "Aiw", havingValue = "test")
public User user2() {
return new User();
}
}
修改主程序,如下:
package com.aiw.springbootcondition;
import com.aiw.springbootcondition.pojo.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) {
// 启动Spring Boot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
Object user = context.getBean("user2", User.class);
System.out.println(user);
}
}
修改配置文件application.yml
Aiw: test
启动项目,控制台输出如下:
若是在application.yml
中找不到Aiw属性和其对应的值,则无法创建Bean
1、自定义条件:
① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。 matches 方法两个参数:
• context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
• metadata:元数据对象,用于获取注解属性。
② 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解
2、SpringBoot 提供的常用条件注解:
• ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
• ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
• ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4中内置服务器(Tomcat、Jetty、Netty、Undertow)供我们选择,我们可以很方便的进行切换。
在Maven: org.springframework.boot:spring-boot-autoconfigure-2.7.1
中查看如下:
切换内置Web服务器,首先需要排除Tomcat
<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>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
刷新依赖,重启项目,控制台如下:
切换其它服务器,方法类似。
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注 解导入一些配置类,实现Bean的动态加载。
SpringBoot 工程是否可以直接获取jar包中定义的Bean?
答:不能。解决方法如下:
使用@ComponentScan扫描配置类所在的包
@ComponentScan("com.aiw.config")
使用@Import注解,加载类;这些类都会被Spring创建,并放入IOC容器
@Import(Config.class)
对@Import注解进行封装
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(Config.class)
public @interface EnableXxx{}
@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
① 导入Bean
修改主程序,如下:
package com.aiw.springbootimport;
import com.aiw.springbootimport.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(User.class)
public class SpringbootImportApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootImportApplication.class, args);
// User user = context.getBean(User.class); // 这种获取不到
User user = (User) context.getBean("user");
System.out.println(user);
}
}
使用这种方法,配置类上的@Configuration注解可以不需要了
② 导入配置类
创建配置类
package com.aiw.springbootimport.config;
import com.aiw.springbootimport.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
public User user(){
return new User();
}
}
修改主程序,如下:
package com.aiw.springbootimport;
import com.aiw.springbootimport.config.UserConfig;
import com.aiw.springbootimport.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(UserConfig.class)
public class SpringbootImportApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootImportApplication.class, args);
User user = context.getBean(User.class);
// User user = (User) context.getBean("user"); // 这种也可以获取到
System.out.println(user);
}
}
③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
创建实现类,如下:
package com.aiw.springbootimport.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.aiw.springbootimport.pojo.User"};
}
}
字符串不是写死的,以后可以写到配置文件中去
修改主程序,如下:
package com.aiw.springbootimport;
import com.aiw.springbootimport.config.MyImportSelector;
import com.aiw.springbootimport.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringbootImportApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootImportApplication.class, args);
// User user = context.getBean(User.class); // 这种获取不到
User user = (User) context.getBean("user");
System.out.println(user);
}
}
运行结果:
④ 导入 ImportBeanDefinitionRegistrar 实现类。
创建实现类
package com.aiw.springbootimport.config;
import com.aiw.springbootimport.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.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user",beanDefinition);
}
}
修改主程序,如下:
package com.aiw.springbootimport;
import com.aiw.springbootimport.config.MyImportBeanDefinitionRegistrar;
import com.aiw.springbootimport.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootImportApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootImportApplication.class, args);
// User user = context.getBean(User.class); // 这种也可以获取到
User user = (User) context.getBean("user");
System.out.println(user);
}
}
若报错,则在配置文件中添加如下:
spring.main.allow-bean-definition-overriding=true
- 1
需求:自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean。
步骤:
SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。
Java中的事件监听机制定义了以下几个角色:
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成 一些操作。
ApplicationContextInitializer、SpringApplicationRunListener、CommandLineRunner、ApplicationRunner
创建Spring Boot(2.7.2)工程,分别创建4个实现类:
MyApplicationContextInitializer.java
package com.aiw.springbootlistener.listener;
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");
}
}
MySpringApplicationRunListener.java
package com.aiw.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 java.time.Duration;
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(SpringApplication application, String[] args){}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("starting...项目启动中");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, 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, Duration timeTaken) {
System.out.println("started...上下文对象加载完成");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("ready...项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed...项目启动失败");
}
}
SpringApplicationRunListener的实现类必须提供构造方法,否则报错;并且不能使用@Component自动注入
MyCommandLineRunner.java
package com.aiw.springbootlistener.listener;
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.toString(args));
}
}
MyApplicationRunner.java
package com.aiw.springbootlistener.listener;
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.toString(args.getSourceArgs()));
}
}
启动项目,目前是没有任何参数。如下:
点击环境配置
->修改选项
->勾选程序实参
,输入name=Aiw
如下:
再次启动项目,如下:
可以发现,始终有两个未生效;在resources
目录下新建META-INF/spring.factories
文件,内容如下:
org.springframework.context.ApplicationContextInitializer=com.aiw.springbootlistener.listener.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=com.aiw.springbootlistener.listener.MySpringApplicationRunListener
键是对应接口的全路径名,值是自定义实现类的引用
再次启动项目,如下:
将以下内容保存为banner.txt
,放于src/main/resources/banner.txt
_ooOoo_
o8888888o
88" . "88
(| -_- |)
O\ = /O
____/`---'\____
. ' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . __
."" '< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
======`-.____`-.___\_____/___.-`____.-'======
`=---='
.............................................
佛祖保佑 永无BUG
启动项目,可以看到默认banner已被替换。
SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性 、日志信息等。
① 导入依赖坐标
创建Spring Boot(2.7.2)项目,并勾选如下依赖:
也可以手动导入
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
② 启动项目,访问http://localhost:8080/actuator
访问http://localhost:8080/actuator/health,默认不显示详细信息
在配置文件中添加如下:
# 开启健康检查的完整信息
management.endpoint.health.show-details=always
重新启动项目,如下:
若引入redis坐标:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
重新启动项目,如下:
再次修改配置文件
# 开启健康检查的完整信息
management.endpoint.health.show-details=always
# 将所有的监控endpoint暴露出来
management.endpoints.web.exposure.include=*
重新启动项目,如下:
说明:
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上的一些监控信息。
使用步骤
admin-server:
① 创建 admin-server 模块
② 导入依赖坐标 admin-starter-server
或者手动导入:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
dependency>
③ 在引导类上启用监控功能@EnableAdminServer
package com.aiw.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);
}
}
在配置文件中添加如下:
server.port=9000
admin-client:
① 创建 admin-client 模块
② 导入依赖坐标 admin-starter-client
或者手动导入:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-clientartifactId>
dependency>
③ 配置相关信息:server地址等
在配置文件中添加如下:
# 执行admin.server地址
spring.boot.admin.client.url=http://localhost:9000
# 开启健康检查的完整信息
management.endpoint.health.show-details=always
# 将所有的监控endpoint暴露出来
management.endpoints.web.exposure.include=*
④ 启动server和client服务,访问server
在浏览器输入http://localhost:9000/
点击在线项,页面如下:
IDEA也提供了快捷功能,如下:
SpringBoot 项目开发完毕后,支持两种方式部署到服务器:
① jar包(官方推荐)
② war包
新建SpringBoot(2.7.2)项目,勾选Web依赖坐标,并创建简单控制器,如下:
package com.aiw.springbootdeploy.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/all", method = RequestMethod.GET)
public String all() {
return "success";
}
}
在IDEA中打开Maven界面,双击package
出现以下界面,则打包成功:
打包位置位于target
目录下:
打开所在文件夹,如下:
按住shift+鼠标右键,点击在此处打开Powershell窗口
,输入如下命令:
回车,即可启动服务:
在浏览器输入http://localhost:8080/user/all
修改pom.xml
的打包方式为war
<packaging>warpackaging>
修改主程序,如下:
package com.aiw.springbootdeploy;
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 SpringbootDeployApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringbootDeployApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringbootDeployApplication.class);
}
}
排除内置的Tomcat
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
在pom.xml
声明使用外部Tomcat服务器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
若觉得打包的名称不好看,可以指定名称,在pom.xml
的build
标签下添加finalName
即可:
<build>
<finalName>springbootfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
这样打包的名称就是springboot了
重新打包,如下:
将生成的war包放于外部的Tomcat的webapps目录下,如下:
启动外部Tomcat,双击startup.bat,运行项目之后Tomcat会自动解包,如下:
再在浏览器上访问http://localhost:8080
会出现外部Tomcat的信息,如下:
此时再访问http://localhost:8080/user/all
,则找不到路径,如下:
需要再加一层目录,也就是项目的打包名称,即http://localhost:8080/springboot/user/all
实测,不知为何加上项目名后,还是报404错误,难道是新版本有改动?
注意,使用war包方式,若还是在配置文件中修改端口地址,则是不生效的,因为此时war包是使用外部服务器;需要到外部服务器配置文件中去改。