springBoot自动配置原理源码分析+自定义starter启动器+可视化监控+mybatisPlus使用

一、springBoot自动化配置原理

1. starter管理机制

通过依赖了解SpringBoot管理了哪些starter

  1. 通过依赖 spring-boot-dependencies 搜索 starter- 发现非常多的官方starter,并且已经帮助我们管理好了版本。
  2. 项目中使用直接引入对应的 starter 即可,这个场景下需要的依赖就会自动导入到项目中,简化了繁琐的依赖。

所有的场景启动器都依赖于spring-boot-starter

例如druid:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.24</version>
    <scope>compile</scope>
</dependency>

这个启动器本身没有代码,通过依赖,构建了springBoot的基础运行环境,

包括spring基础环境,自动化配置基本环境。

org\springframework\boot\spring-boot-starter\spring-boot-starter-2.5.0.pom 里面的默认依赖

自动化配置依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.5.0</version>
    <scope>compile</scope>
</dependency>

spring的IOC容器
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.7</version>
    <scope>compile</scope>
</dependency>

。。。。。等等

小结:

  • 引入的官方starter依赖默认都可以不写版本
  • 如果配置满足您当前开发需要,则默认配置即可
  • 自定义的starter,需要引入spring-boot-starter

2. springmvc的自动化配置原理

以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.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
image-20210612214843402 image-20210612220418462 image-20210612215231369

小结: 有了SpringBoot以后,让开发人员重点关注业务本身,而不是环境上,提升了开发效率。

3. 底层原理之@Configuration

理解@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启动速度。

4. 底层原理之@Import

【1】@Import的基础用法
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); 
   }
}
【2】@Import另外两种实现

为讲解源码做铺垫

  1. 导入 ImportSelector 实现类。会调用接口的selectImports()方法来加载资源。

    image-20210613000128805
  2. 导入 ImportBeanDefinitionRegistrar 实现类,会调用接口的registerBeanDefination()

    来向spring注册bean的信息。(将对象放到spring容器中)

    image-20210613000535321

5. 底层原理之@Conditional衍生条件装配

作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器

image-20210613000732168
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才有效,按需导入。

6. 底层原理之@ConfigurationProperties配置绑定

在springBoot基础中有演示,用于配置文件的自动依赖注入。

7. 自动化配置原理@SpringBootApplication入口分析

理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用

@SpringBootApplication组合注解

image-20210612234803630

【1】@SpringBootConfiguration注解作用
  • @SpringBootConfiguration是对@Configuration注解的包装,

    proxyBeanMethods 默认配置 true, full模式(单例模式创建Bean),反之,false为 多例模式

  • 标识是一个配置类所以 引导类也是配置类

image-20210612234815878
【2】@ComponentScan注解作用
  • 组件扫描,默认扫描的规则 ( 引导类所在的包及其子包所有带注解的类 )

问题:

  1. 在引导类中配置 @Bean 注解可以吗?
  2. 为什么Controller、service类添加完注解后,不需要添加扫描包?
【3】@EnableAutoConfiguration自动配置注解

理解@EnableAutoConfiguration自动化配置核心实现注解

@EnableAutoConfiguration也是一个组合注解

image-20210612234927175

【1】@AutoConfigurationPackage

作用:利用Registrar给容器中导入一系列组件 ,将引导类的所有包及其子包的组件导入进来

image-20210612234951082

点击 Registrar 进入到源码的 register 方法,添加 断点,测试

image-20210613084407124

通过 debug 程序发现,默认情况下 将引导类的所有包及其子包的组件导入进来

【2】@Import(AutoConfigurationImportSelector.class)注解作用

作用:利用selectImports方法中的 getAutoConfigurationEntry 方法给容器中批量导入工厂配置相关组件

  1. 调用AutoConfigurationImportSelector类中的selectImports方法
  2. 调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
image-20210613085434132
  1. 利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)得到所有的组件
  2. 从META-INF/spring.factories位置来加载一个文件。

默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

image-20210613094041615 image-20210613094400722

小结: 自动化配置默认加载的配置文件在哪?

META-INF/spring.factories

8. 自动化配置原理-按条件开启自动配置类和配置项

  • 理解所有的自动化配置虽然会全部加载,但由于底层有大量的@ConditionalOnXXX注解进行判断,所以有很多自动配置类并不能完全开启
  • 如果配置生效了,则会加载默认的属性配置类,实现默认的对应场景的自动化配置

以上通过 META-INF/spring.factories 配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。

以webmvc自动化配置为例

image-20210613095800261

问题: 这些不用的 starter 的依赖,能不能导入到我们工程里面? 为什么?

导入相关的starter 依赖,才会进行自动配置加载。不用的不必要导入,会降低springBoot启动速度。

9. 自动化配置原理-springBoot源码分析(理解)

理解整个SpringBoot启动的完成自动化配置及属性加载的全过程

【1】 启动类分析
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

分别跟踪两部分内容。

【2】 springBoot启动过程

main函数中的SpringApplication.run(BankApplication.class, args);就是项目的入口,

也是Spring加载的完整过程,我们从这里开始。

首先跟入run方法,流程如图:

image-20210613111015475

因此,接下来要看的是两部分:

  • new SpringApplication(primarySources):构造函数初始化
  • run(args):成员的run方法
[1] SpringApplication构造函数

我们把跟构造函数有关的几个变量和方法提取出来,方便查看:

// 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

1)deduceFromClasspath()方法

判断项目类型:

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种:

  • REACTIVE:要求classpath中包含org.springframework.web.reactive.DispatcherHandler,这个是WebFlux中的核心处理器,我们并没有。
  • SERVLET:要求classpath中包含org.springframework.web.servlet.DispatcherServlet,这是SpringMVC的核心控制器,在classpath中肯定可以找到
  • NONE:以上都不满足,就是NONE
2)getSpringFactoriesInstances()方法

在构造函数中被调用了两次,分别加载ApplicationContextInitializerApplicationListener

1612285452094

getSpringFactoriesInstances(Class<T> type) 方法的作用是获得指定接口的实现类的实例集合。

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下面的实现类的名称字符串集合。

那么这里是如何根据接口找到对应的实现类名称呢?

3)loadFactoryNames加载类名称

那么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包中,就有这样的文件

1562132554776

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方法,创建实例

4)createSpringFactoriesInstances创建实例

然后看看#createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) 方法,创建对象的代码:

/**
 * 根据类的全名称路径数组,创建对应的对象的数组
 *
 * @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;
}

基本上就是利用反射根据类名称,获取类的字节码,然后创建对象。

[2] run方法的启动流程

在完成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服务】

image-20210613140436318

依次跟踪到 onRefresh() 方法

image-20210613115203537

点击createWevbServer()方法

会创建ServletContext上下文域对象 、 WebServer的 Tomcat服务器

image-20210613135620193

tomcat.start() 启动服务器以触发初始化侦听器

image-20210613141524759

小结:springBoot启动流程图
image-20210613141121958

10. 自动化配置原理总结

  • 程序启动找到自动化配置包下 META-INF/spring.factoriesEnableAutoConfiguration
  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,从xxxxProperties里面拿(xxxProperties和配置文件进行了绑定)
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就可以使用了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。
graph LR;
1[xxxxAutoConfiguration] --> 2[ Bean组件]
2 --> 3[xxxxProperties里面取值]
3 --> 4[application.properties]

开发使用步骤总结:

  • 引入场景依赖
  • 查看自动配置了哪些
    • 自己分析,引入场景对应的自动配置一般都生效了
  • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
  • 自己分析是否需要修改
    • 参照文档修改配置项,xxxxProperties绑定了配置文件的哪些。
    • 自定义加入或者替换组件,@Bean、@Component等

二、springBoot自定义starter

1. 自定义redisson启动器(举例)

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.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.3.11.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.ahcfl</groupId>
<artifactId>ahcfl-reids-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>

<description>自定义redisson启动器</description>

<properties>
   <java.version>1.8</java.version>
   <redisson.version>3.12.0</redisson.version>
</properties>

<dependencies>
   <!-- spring工程的基本启动器-->
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
   </dependency>
   <!-- 解决配置类爆红 -->
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
   </dependency>
   <!-- redisson 依赖-->
   <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>${redisson.version}</version>
   </dependency>
   <!-- lombok插件-->
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
   </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.安装到本地仓库

2. 自定义redisson启动器引入到项目

1.新开web工程:引入依赖

<dependency>
	<groupId>com.ahcfl</groupId>
    <artifactId>ahcfl-reids-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</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";
    }
}
  1. 浏览器访问 127.0.0.1:8080/demo/resisson
  2. 访问成功,查看redis客户端 有 test:111的键值对

三、springBoot健康监控

1. 健康监控服务

理解健康监控actuator的作用

每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。

SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

步骤:
1.引入依赖
2.启动项目,访问 http://localhost:8080/actuator
3.修改配置,添加配置,再次访问

1、引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.启动项目,访问 http://localhost:8080/actuator 显示:

image-20210613152509604

3.修改配置,添加配置项,再次访问 http://localhost:8080/actuator

management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露

  endpoint:
    health:
      enabled: true   # 开启健康检查详细信息
      show-details: always

image-20210613153935888

2. admin可视化

搭建可视化监控平台

SpringBoot Admin 有两个角色,客户端(Client)和服务端(Server)。

Spring Boot Admin为注册的应用程序提供以下功能:

  • 显示健康状况
  • 显示详细信息,例如
  • JVM和内存指标
  • micrometer.io指标
  • 数据源指标
  • 缓存指标
  • 显示内部信息
  • 关注并下载日志文件
  • 查看JVM系统和环境属性
  • 查看Spring Boot配置属性
  • 支持Spring Cloud的可发布/ env-和// refresh-endpoint
  • 轻松的日志级别管理
  • 与JMX-beans交互
  • 查看线程转储
  • 查看http-traces
  • 查看审核事件
  • 查看http端点
  • 查看预定的任务
  • 查看和删除活动会话(使用spring-session)
  • 查看Flyway / Liquibase数据库迁移
  • 下载heapdump
  • 状态更改通知(通过电子邮件,Slack,Hipchat等)
  • 状态更改的事件日志(非持久性)

快速入门:https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started

搭建Server端:

1.创建admin_server工程,引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.10.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2.开启注解支持

image-20210613154047946

3.修改服务端口号未9999

image-20210613154102524

搭建Client端:

1.在任意工程中引入依赖:

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.3.1</version>
</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/

image-20210613154130854

四、springBoot项目部署

1. jar包部署
步骤
1.添加打包插件,maven命令打jar包
2.将jar包上传到linux任意目录
3.通过命令java -jar app.jar来执行

1.添加打包插件,maven命令打jar包

<plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
image-20210613154402551

2.将jar包上传到linux任意目录

rz命令可以上传 / 或者 其他 客户端 支持 拖拽上传

1622905178518

3.通过命令java -jar app.jar来执行

1622905223023

  1. 浏览器访问测试

注意:linux防火墙需要开放8080端口号

2. war包部署
步骤
1.修改工程的打包方式为war包
2.引入打包插件
3. 修改启动类,继承 SpringBootServletInitializer
4.配置tomcat,将war包丢到webapps目录下
5.启动tomcat,测试访问

1.修改工程的打包方式为war包

2.引入打包插件

<plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>
</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目录下

1622905821682

5.启动tomcat,测试访问 虚拟机的ip:8080/springBoot/demo

注意:war包的名字就是项目发布时的虚拟路径

五、整合Mybatis-Plus(扩展)

Mybatis-Plus文档

Mybatis-Plus github

Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。这是官方给的定义,关于mybatis-plus的更多介绍及特性,可以参考mybatis-plus官网。那么它是怎么增强的呢?其实就是它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行

1. 数据准备

准备数据和实体

/*
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;
}

2. 整合mybaits-Plush

步骤
1.引入springBoot整合mybatis-plus的启动器,该启动器是mybatis官方提供的
2.配置yml文件
3.编写mapper接口,继承BaseMapper
4.在启动类上添加mapper包扫描
5.在单元测试中测试mapper对象

1.引入springBoot整合mybatis-plus的启动器,该启动器是mybatis官方提供的

<!--mybatis-plus启动器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>
<!--mybatis-pluse-xtension依赖包-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-extension</artifactId>
    <version>3.3.2</version>
</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成功

image-20210613161249099

3. BaseMapper数据操作

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);
        });
    }
}

测试结果:

image-20210613162307785

4. IService通用业务

对于一些常见的增删该查业务操作,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> {

image-20210613164231210

5. 联调springmvc

需求:在页面上显示所有的用户信息,页面使用vue的ajax请求获取数据,展示数据

1.基于上面的代码修改
2.编写controller
3.浏览器直接访问
  1. user.html 页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</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>
        <!--使用指令  v-for 遍历数据模型 users数据-->
        <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>
  1. 编写controller
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 显示:

image-20210613164746294

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用 Redisson 实现分布式锁,具体实现如下: 1. 引入 Redisson 依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.1</version> </dependency> ``` 2. 定义自定义注解 `DistributedLock`: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributedLock { String value() default ""; long leaseTime() default 30L; TimeUnit timeUnit() default TimeUnit.SECONDS; } ``` 3. 在需要加锁的方法上加上 `@DistributedLock` 注解: ```java @Service public class UserService { @DistributedLock("user:#{#userId}") public User getUserById(String userId) { // ... } } ``` 4. 实现 `DistributedLockAspect` 切面: ```java @Aspect @Component public class DistributedLockAspect { private final RedissonClient redissonClient; public DistributedLockAspect(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockKey = distributedLock.value(); if (StringUtils.isEmpty(lockKey)) { lockKey = joinPoint.getSignature().toLongString(); } RLock lock = redissonClient.getLock(lockKey); boolean locked = lock.tryLock(distributedLock.leaseTime(), distributedLock.timeUnit()); if (!locked) { throw new RuntimeException("获取分布式锁失败"); } try { return joinPoint.proceed(); } finally { lock.unlock(); } } } ``` 5. 在 `application.yml` 中配置 Redisson: ```yaml spring: redis: host: localhost port: 6379 password: database: 0 redisson: single-server-config: address: redis://${spring.redis.host}:${spring.redis.port} ``` 这样就实现了一个基于 Redis 的分布式锁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值