通过依赖了解SpringBoot管理了哪些starter
spring-boot-dependencies
搜索 starter-
发现非常多的官方starter,并且已经帮助我们管理好了版本。starter
即可,这个场景下需要的依赖就会自动导入到项目中,简化了繁琐的依赖。所有的场景启动器都依赖于spring-boot-starter
例如druid:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.24version>
<scope>compilescope>
dependency>
这个启动器本身没有代码,通过依赖,构建了springBoot的基础运行环境,
包括spring基础环境,自动化配置基本环境。
org\springframework\boot\spring-boot-starter\spring-boot-starter-2.5.0.pom 里面的默认依赖
自动化配置依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigureartifactId>
<version>2.5.0version>
<scope>compilescope>
dependency>
spring的IOC容器
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.3.7version>
<scope>compilescope>
dependency>
。。。。。等等
小结:
以web MVC自动化配置原理为例,理解web MVC自动化配置加入了哪些依赖,做了哪些默认配置。
回忆一下:SpringMVC学习时候,我们在 SSM整合时,添加spring及spring web mvc相关依赖
springmvc.xml 配置文件配置了:
1. 扫描controller 所在包
2. 配置annotation-driven支持mvc功能(HandlerMapping, HandlerAdapter)
3. 视图解析器
4. 静态资源
5. 拦截器
6. ……
web.xml 配置:
1. 初始化spring容器
2. 初始化springmvc DispatcherServlet
3. post请求乱码过滤器
部署还需要单独的tomcat
-----------------------------------------------------
也就是说:我们现在需要在开发业务代码前,就必须要准备好这些环境,否则无法完成业务代码,
这就是我们现在的问题。
让这些问题成为过去,现在我们就探索一下SpringBoot是如何帮助我们完成强大而又简单自动化配置的。
以引入web启动器为列:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
小结: 有了SpringBoot以后,让开发人员重点关注业务本身,而不是环境上,提升了开发效率。
理解@Configuration的作用和新特性
@Configuration : 标注当前类是一个配置类,spring会加载改配置类
属性 proxyBeanMethods:
true: @Bean标注的方式创建 对象会使用代理方式创建,并且放到spring容器中,单例。
false: @Bean标注的方法执行调用来创建对象,不会进spring容器(多例)。
默认为true
步骤:
1.创建MyConfig配置类,提供方法创建对象,使用@Bean标注
2.创建User实体类,
3.通过spring容器获取配置类对象,调用方法获取对象。
代码演示:
package com.ahcfl.demo2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false) // 标注当前类是一个配置类,spring会加载改配置类,且不会进spring容器(多例模式)。
public class MyConfig {
@Bean
public User user(){
return new User();
}
}
package com.ahcfl.demo2.pojo;
public class User {
public User() {
System.out.println("对象被创建了");
}
}
package com.ahcfl.demo2;
import com.ahcfl.demo2.config.MyConfig;
import com.ahcfl.demo2.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Demo2Application {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(Demo2Application.class, args);
MyConfig myConfig = ac.getBean(MyConfig.class);
User user = myConfig.user();
User user2 = myConfig.user();
System.out.println(user);
System.out.println(user2);
}
}
小结:不常用的bean设置为false,不加入IOC容器中,可以提升springBoot启动速度。
1.导入Bean,会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径
2.导入配置类,并且类中有 带有@Bean注解方法,创建对象存到IOC容器,bean名称为:默认方法名称
代码演示
package com.ahcfl.demo2;
import com.ahcfl.demo2.config.MyConfig;
import com.ahcfl.demo2.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)
@Import(MyConfig.class)
public class Demo2Application {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(Demo2Application.class, args);
User user = ac.getBean(User.class);
System.out.println(user);
}
}
为讲解源码做铺垫
导入 ImportSelector 实现类。会调用接口的selectImports()方法来加载资源。
导入 ImportBeanDefinitionRegistrar 实现类,会调用接口的registerBeanDefination()
来向spring注册bean的信息。(将对象放到spring容器中)
作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器
package com.ahcfl.demo2.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@ConditionalOnMissingBean(Dog.class) //在没有dog类对象的情况下创建bean
//@ConditionalOnClass(Dog.class) //存在当前类的情况下创建bean
@Bean
public User user(){
return new User();
}
}
小结:@ConditionalOnXXX 注解存在的意义是:满足条件当前类或者Bean才有效,按需导入。
在springBoot基础中有演示,用于配置文件的自动依赖注入。
理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用
@SpringBootConfiguration是对@Configuration注解的包装,
proxyBeanMethods 默认配置 true, full模式(单例模式创建Bean),反之,false为 多例模式
标识是一个配置类,所以 引导类也是配置类
问题:
理解@EnableAutoConfiguration自动化配置核心实现注解
@EnableAutoConfiguration也是一个组合注解
作用:利用Registrar给容器中导入一系列组件 ,将引导类的所有包及其子包的组件导入进来
点击 Registrar
进入到源码的 register
方法,添加 断点,测试
通过 debug 程序发现,默认情况下 将引导类的所有包及其子包的组件导入进来
作用:利用selectImports
方法中的 getAutoConfigurationEntry
方法给容器中批量导入工厂配置相关组件
AutoConfigurationImportSelector
类中的selectImports
方法List configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类Map> loadSpringFactories(@Nullable ClassLoader classLoader)
得到所有的组件默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
小结: 自动化配置默认加载的配置文件在哪?
META-INF/spring.factories
以上通过 META-INF/spring.factories
配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。
以webmvc自动化配置为例
问题: 这些不用的 starter 的依赖,能不能导入到我们工程里面? 为什么?
导入相关的starter 依赖,才会进行自动配置加载。不用的不必要导入,会降低springBoot启动速度。
理解整个SpringBoot启动的完成自动化配置及属性加载的全过程
package com.ahcfl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.ahcfl.mapper")
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class,args);
}
}
这里跟SpringBoot有关联的部分有两个,
一个是SpringApplication.run(WebApplication.class, args);
,
一个就是启动类上的注解:@SpringBootApplication
。
分别跟踪两部分内容。
main函数中的SpringApplication.run(BankApplication.class, args);
就是项目的入口,
也是Spring加载的完整过程,我们从这里开始。
首先跟入run方法,流程如图:
因此,接下来要看的是两部分:
new SpringApplication(primarySources)
:构造函数初始化run(args)
:成员的run方法我们把跟构造函数有关的几个变量和方法提取出来,方便查看:
// SpringApplication.java
/**
* 资源加载器,读取classpath下的文件
*/
private ResourceLoader resourceLoader;
/**
* SpringBoot核心配置类的集合,这里只有一个元素,是我们传入的主函数
*/
private Set<Class<?>> primarySources;
/**
* 当前项目的应用类型
*/
private WebApplicationType webApplicationType;
/**
* ApplicationContextInitializer 数组
*/
private List<ApplicationContextInitializer<?>> initializers;
/**
* ApplicationListener 数组
*/
private List<ApplicationListener<?>> listeners;
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// 核心构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1.记录资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2.将传入的启动类装入集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3.判断当前项目的类型,可以是SERVLET、REACTIVE、NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4.初始化 initializers 数组
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 5.初始化 listeners 数组
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
分析说明:
ResourceLoader resourceLoader
:Spring中用来加载资源的加载器
Class>... primarySources
:这里是启动类,本例中就是WebApplication.class
WebApplicationType.deduceFromClasspath()
:判断当前项目的类型,
可以是SERVLET、REACTIVE、NONE,根据当前classpath中包含的class来判断,
影响后续创建的ApplicationContext的类型 【3】
getSpringFactoriesInstances(ApplicationContextInitializer.class)
:获取ApplicationContextInitializer类型的实现类对象数组 【4】
getSpringFactoriesInstances(ApplicationListener.class)
:获取ApplicationListener类型的实现类对象数组 【5】
deduceMainApplicationClass()
:没有实际用途,打印日志,输出当前启动类名称
我们只看难点部分,也就是步骤3、4、5
判断项目类型:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
可以看到判断结果包含3种:
org.springframework.web.reactive.DispatcherHandler
,这个是WebFlux中的核心处理器,我们并没有。org.springframework.web.servlet.DispatcherServlet
,这是SpringMVC的核心控制器,在classpath中肯定可以找到在构造函数中被调用了两次,分别加载ApplicationContextInitializer
和ApplicationListener
:
getSpringFactoriesInstances(Class
方法的作用是获得指定接口的实现类的实例集合。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
// 调用下面的一个重载方法,参数type就是接口的类型
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 真正的处理逻辑
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 1.先加载指定接口的实现类的名称集合
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 2.根据类的名称,创建实例对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 3.排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里关键是第1步中,调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法,
是用来获取指定接口的实现类的名称字符串,而后就可以根据名称创建实例了。
例如我们传递的参数是:ApplicationContextInitializer.class
,那么获取的就是ApplicationContextInitializer
下面的实现类的名称字符串集合。
那么这里是如何根据接口找到对应的实现类名称呢?
那么loadFactoryNames是如何根据接口找到对应的实现类名称呢,继续跟入:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法:
// SpringFactoriesLoader
/**
* 使用指定的类加载器,加载{@value #FACTORIES_RESOURCE_LOCATION}中记录的,指定factoryClass
* 类型的实现类的全路径名。
* @param factoryClass 需要加载的接口或抽象类
* @param classLoader 用来加载资源的类加载器
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取接口名称
String factoryClassName = factoryClass.getName();
// 从loadSpringFactories(classLoader)方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合
// 然后就可以调用map的get方法,根据factoryClass名称获取对应的实现类名称数组
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
注意到这里是先调用loadSpringFactories(classLoader)
方法,
此方法方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合。
那么,loadSpringFactories
方法是如何读取到这样的map呢?上面有截图分析这一部分,代码如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 尝试从缓存中获取结果
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 从默认路径加载资源文件,地址是:"META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 创建空map
result = new LinkedMultiValueMap<>();
// 遍历资源路径
while (urls.hasMoreElements()) {
// 获取某个路径
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载文件内容,文件中是properties格式,key是接口名,value是实现类的名称以,隔开
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取key的 名称
String factoryClassName = ((String) entry.getKey()).trim();
// 将实现类字符串变成数组并遍历,然后添加到结果result中
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 缓存中放一份,下次再加载可以从缓存中读取
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这个方法是利用ClassLoader
加载classpath下的所有的/META-INF/spring.factories
文件。
注意:所有jar包都会被扫描和查找。
例如,在spring-boot的jar包中,就有这样的文件
spring.factories内容类似这样:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
根据传入的接口名称,例如org.springframework.boot.ApplicationListener
,
就可以寻找到对应的实现类,例如:
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener
得到一个字符串集合并返回。
结束后,把得到的名字集合传递给createSpringFactoriesInstance方法,创建实例
然后看看#createSpringFactoriesInstances(Class
方法,创建对象的代码:
/**
* 根据类的全名称路径数组,创建对应的对象的数组
*
* @param type 父类类型
* @param parameterTypes 构造方法的参数类型
* @param classLoader 类加载器
* @param args 构造方法参数
* @param names 类全名称的数组
*/
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
// 定义空实例集合
List<T> instances = new ArrayList<>(names.size());
// 遍历 names 数组
for (String name : names) {
try {
// 获得类名称 name
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 判断类是否实现自 type 类
Assert.isAssignable(type, instanceClass);
// 获得构造方法
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 创建对象
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
} catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
基本上就是利用反射根据类名称,获取类的字节码,然后创建对象。
在完成SpringApplication对象初始化后,会调用其中的run方法,
public ConfigurableApplicationContext run(String... args) {
// 1.计时器,记录springBoot启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 2.配置headLess属性,这个跟AWT有关,忽略即可
configureHeadlessProperty();
// 3.获取SpringApplicationRunListener实例数组,默认获取的是EventPublishRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听
listeners.starting();
try {
// 4.创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//5.加载属性配置。所有的environment的属性都会加载进来,包括 application.properties 和外部的属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 6.打印Banner
Banner printedBanner = printBanner(environment);
// 7.根据WebApplicationType,创建不同的ApplicationContext
context = createApplicationContext();
// 8.获取异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 9.调用各种初始化器的initialize方法,初始化容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 10.准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcat
refreshContext(context);
// 11.执行初始化的后置逻辑,默认为空
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 12.打印 Spring Boot 启动的时长日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 13.通知监听器,SpringBoot启动完成
listeners.started(context);
// 14.调用 ApplicationRunner的运行方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 通知监听器,SpringBoot正在运行
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
其中注意的是刷新容器 refreshContext(context)【createWevbServer() 得到tomcat服务】
依次跟踪到 onRefresh() 方法
点击createWevbServer()
方法
会创建ServletContext上下文域对象 、 WebServer的 Tomcat服务器
tomcat.start() 启动服务器以触发初始化侦听器
META-INF/spring.factories
的EnableAutoConfiguration
graph LR;
1[xxxxAutoConfiguration] --> 2[ Bean组件]
2 --> 3[xxxxProperties里面取值]
3 --> 4[application.properties]
开发使用步骤总结:
redisson手册
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。
Redisson 基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
自定义自定义redisson启动器: ahcfl-redisson-spring-boot-starter.jar
步骤:
1.创建工程ahcfl-redisson-spring-boot-starter
2.引入springBoot父工程,引入springBoot基本启动器,redisson依赖,
3.创建RedissonProperties配置类,用来提供RedissonAutoConfig的配置
4.创建RedissonAutoConfig类,并且在META-INF/spring.factories中配置自动化配置启动类
5.在RedissonAutoConfig类中创建redissonClient对象
6.安装到仓库
**注意:不需要引入之前打jar包的 maven插件 **
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.11.RELEASEversion>
<relativePath/>
parent>
<groupId>com.ahcflgroupId>
<artifactId>ahcfl-reids-boot-starterartifactId>
<version>0.0.1-SNAPSHOTversion>
<description>自定义redisson启动器description>
<properties>
<java.version>1.8java.version>
<redisson.version>3.12.0redisson.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>${redisson.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
3.创建RedissonProperties配置类,用来提供配置
package com.ahcfl.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* redis连接地址
*/
private String nodes="redis://127.0.0.1:6379";
/**
* 获取连接超时时间
*/
private int connectTimeout=5000;
/**
* 最小空闲连接数
*/
private int connectPoolSize=64;
/**
* 最小连接数
*/
private int connectionMinimumidleSize=64;
/**
* 等待数据返回超时时间
*/
private int timeout=4000;
/**
* 刷新时间
*/
private int retryInterval=1500;
}
4.创建RedissonAutoConfig类,并且在META-INF/spring.factories中配置自动化配置启动类
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ahcfl.config.RedissonAutoConfig
5.在RedissonAutoConfig类中创建redissonClient对象
package com.ahcfl.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(RedissonClient.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfig {
@Autowired
private RedissonProperties redissonProperties;
@ConditionalOnMissingBean(RedissonClient.class)
@Bean(value = "redissonClient",destroyMethod="shutdown")
public RedissonClient config() {
String[] nodeList = redissonProperties.getNodes().split(",");
Config config = new Config();
//单节点
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(redissonProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(redissonProperties.getConnectPoolSize())
.setTimeout(redissonProperties.getTimeout());
//集群节点
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(redissonProperties.getConnectTimeout())
.setRetryInterval(redissonProperties.getRetryInterval())
.setMasterConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(redissonProperties.getConnectPoolSize())
.setSlaveConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumidleSize())
.setSlaveConnectionPoolSize(redissonProperties.getConnectPoolSize())
.setTimeout(3000);
}
System.out.println("redisson自动化配置完成");
return Redisson.create(config);
}
}
6.安装到本地仓库
1.新开web工程:引入依赖
<dependency>
<groupId>com.ahcflgroupId>
<artifactId>ahcfl-reids-boot-starterartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
.........
.........
2.直接依赖注入测试功能
package com.ahcfl.springBoot_demo4.controller;
import com.ahcfl.springBoot_demo4.pojo.JdbcConfig;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.JdbcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/demo")
@RestController
public class DemoController {
@Autowired
private RedissonClient redissonClient;
@RequestMapping("/redisson")
public String demo(){
System.out.println(redissonClient);
redissonClient.getBucket("test").set("111");
return "hello springBoot";
}
}
test:111
的键值对理解健康监控actuator
的作用
每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。
SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
步骤:
1.引入依赖
2.启动项目,访问 http://localhost:8080/actuator
3.修改配置,添加配置,再次访问
1、引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
2.启动项目,访问 http://localhost:8080/actuator 显示:
3.修改配置,添加配置项,再次访问 http://localhost:8080/actuator
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
enabled: true # 开启健康检查详细信息
show-details: always
搭建可视化监控平台
SpringBoot Admin 有两个角色,客户端(Client)和服务端(Server)。
Spring Boot Admin为注册的应用程序提供以下功能:
快速入门:https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started
搭建Server端:
1.创建admin_server工程,引入依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.10.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
2.开启注解支持
3.修改服务端口号未9999
搭建Client端:
1.在任意工程中引入依赖:
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-clientartifactId>
<version>2.3.1version>
dependency>
2.配置文件
spring:
boot:
admin:
client:
url: http://localhost:9999 # admin 服务地址
instance:
prefer-ip: true # 显示IP
application:
name: boot_data # 项目名称
3.启动服务,访问admin Server http://localhost:9999/
步骤
1.添加打包插件,maven命令打jar包
2.将jar包上传到linux任意目录
3.通过命令java -jar app.jar来执行
1.添加打包插件,maven命令打jar包
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
2.将jar包上传到linux任意目录
rz命令可以上传 / 或者 其他 客户端 支持 拖拽上传
3.通过命令java -jar app.jar来执行
注意:linux防火墙需要开放8080端口号
步骤
1.修改工程的打包方式为war包
2.引入打包插件
3. 修改启动类,继承 SpringBootServletInitializer
4.配置tomcat,将war包丢到webapps目录下
5.启动tomcat,测试访问
1.修改工程的打包方式为war包
2.引入打包插件
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
3.修改启动类,继承 SpringBootServletInitializer
package com.ahcfl.springBoot_demo4;
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 SpringBootDemo4Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemo4Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringBootDemo4Application.class);
}
}
4.配置tomcat,将war包丢到webapps目录下
5.启动tomcat,测试访问 虚拟机的ip:8080/springBoot/demo
注意:war包的名字就是项目发布时的虚拟路径
Mybatis-Plus文档
Mybatis-Plus github
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。这是官方给的定义,关于mybatis-plus的更多介绍及特性,可以参考mybatis-plus官网。那么它是怎么增强的呢?其实就是它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行
准备数据和实体
/*
Navicat MySQL Data Transfer
Source Server : mysql
Source Server Version : 50527
Source Host : localhost:3306
Source Database : vuedemo
Target Server Type : MYSQL
Target Server Version : 50527
File Encoding : 65001
Date: 2021-3-13 15:38:43
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`sex` tinyint(1) DEFAULT NULL COMMENT '性别,1男性,2女性',
`birthday` date DEFAULT NULL COMMENT '出生日期',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangshisan', '123456', '张三', '30', '1', '1984-08-08');
INSERT INTO `tb_user` VALUES ('2', 'admin20', '123456', '用户20', '18', '1', '2018-10-27');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1', '1988-09-01');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '1', '1985-01-01');
INSERT INTO `tb_user` VALUES ('6', 'lilei', '123456', '李磊', '23', '1', '1988-08-08');
INSERT INTO `tb_user` VALUES ('8', 'admin1', '123456', '用户1', '18', '1', '2018-10-27');
INSERT INTO `tb_user` VALUES ('11', 'admin4', '123456', '用户4', '18', '1', '2018-10-27');
实体类和表建立映射关系 User
package com.ahcfl.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
@Data
@ToString
//实体类和表建立映射关系
public class User implements Serializable {
private Long id;
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private String birthday;
}
步骤
1.引入springBoot整合mybatis-plus的启动器,该启动器是mybatis官方提供的
2.配置yml文件
3.编写mapper接口,继承BaseMapper
4.在启动类上添加mapper包扫描
5.在单元测试中测试mapper对象
1.引入springBoot整合mybatis-plus的启动器,该启动器是mybatis官方提供的
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.2version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-extensionartifactId>
<version>3.3.2version>
dependency>
2.配置yml文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_boot?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 1234
#mybatis-plus
mybatis-plus:
type-aliases-package: com.ahcfl.pojo
configuration:
# sql日志显示,这里使用标准显示
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 数据库中如果有类似 如 user_name 等命名,会将 _后的字母大写,这里是为了和实体类对应
map-underscore-to-camel-case: true
3.编写mapper接口,继承BaseMapper
package com.ahcfl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
4.在启动类上添加mapper包扫描
@SpringBootApplication
@MapperScan("com.ahcfl.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.在单元测试中测试mapper对象
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void demo(){
System.out.println(userMapper);
}
}
测试启动 mybatisplus注入userMapper成功
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> wrapper);
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
T selectOne(@Param("ew") Wrapper<T> queryWrapper);
.......................
.......................
详见mybatis-plus手册 使用mybaits-plus查询
步骤:
1.建立实体和表的映射
2.创建UserService接口UserServiceImpl实现类
a.并且声明mapper属性,依赖注入mapper属性
b.声明方法,并且实现方法,操作数据库
3.测试
1.建立实体和表的映射 在1的基础上 修改User类
package com.ahcfl.pojo;
@Data
@ToString
//实体类和表建立映射关系
@TableName(value = "tb_user")
public class User implements Serializable {
// 主键字段名称 id 主键自动增长
@TableId(value = "id",type = IdType.AUTO)
private Long id;
// 用户名 如果 属性名和数据库字段名一致 该注解可以省略
@TableField(value = "username")
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private String birthday;
}
2.创建UserService接口UserServiceImpl实现类
a.并且声明mapper属性,依赖注入mapper属性
b.声明方法,并且实现方法,操作数据库
package com.ahcfl.service;
import com.ahcfl.pojo.User;
import java.util.List;
public interface UserService {
public List<User> list();
}
==============================================
package com.ahcfl.service.impl;
import com.ahcfl.mapper.UserMapper;
import com.ahcfl.pojo.User;
import com.ahcfl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> list() {
return userMapper.selectList(null); // 调用BaseMapper<>已经提供好的方法
}
}
3.测试
package com.ahcfl.service;
import com.ahcfl.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void list() {
List<User> list = userService.list();
list.forEach(user -> {
System.out.println(user);
});
}
}
测试结果:
对于一些常见的增删该查业务操作,mybatis-plus已经做好了实现,我们直接继承即可
步骤:
1.修改我们的service接口,不需要声明任何方法,实现IService接口
2.修改我们的service实现类,继承ServiceImpl实现类
3.测试时,直接调用service的list方法
1.修改我们的service接口,不需要声明任何方法,实现IService接口
package com.ahcfl.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ahcfl.pojo.User;
public interface UserService extends IService<User>{
}
2.修改上面的service实现类,继承ServiceImpl实现类
package com.ahcfl.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ahcfl.mapper.UserMapper;
import com.ahcfl.pojo.User;
import com.ahcfl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
3.测试时,直接调用service的list方法
package com.ahcfl.service;
import com.ahcfl.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void list() {
List<User> list = userService.list();
list.forEach(user -> {
System.out.println(user);
});
}
}
查询结果和 3 中相同
执行原理:
通用service使用时:
1.我们的业务接口继承IService接口
2.我们的业务实现类继承ServiceImpl实现类
public interface UserService extends IService<User> {
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
需求:在页面上显示所有的用户信息,页面使用vue的ajax请求获取数据,展示数据
1.基于上面的代码修改
2.编写controller
3.浏览器直接访问
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<script src="js/vue.js">script>
<script src="js/axios-0.18.0.js">script>
<script src="js/index.js">script>
<body>
<div id="app">
<table class="altrowstable" id="alternatecolor">
<tr>
<td>用户编号td>
<td>用户姓名td>
<td>用户密码td>
<td>用户性别td>
<td>用户年纪td>
<td>用户生日td>
tr>
<tr v-for="user in users">
<td>{{user.id}}td>
<td>{{user.name}}td>
<td>{{user.password}}td>
<td>{{user.sex==1?'帅':'美'}}td>
<td>{{user.age}}td>
<td>{{user.birthday}}td>
tr>
table>
div>
body>
<script>
var vue = new Vue({
el:"#app",
// 数据模型
data:{
users:[] // 定义一个 数组 接收后台 json 数据 [{},{},{}]
},
created(){
// vue初始化 函数会自动调用
// 页面加载时 立刻发送ajax 访问后台 获取用户数据
axios.get("findAll").then((res)=>{
this.users = res.data // 后台 数据 赋值 数据模型 users
})
}
})
script>
html>
package com.ahcfl.controller;
import com.ahcfl.pojo.User;
import com.ahcfl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/findAll")
public List<User> findAll(){
return userService.list();
}
}
3.浏览器直接访问 127.0.0.1:8080/user.html 显示: