北京市住房城乡建设门户网站,公司网站域名申请,aspx网站开发,公司网站建设中心系列文章目录 文章目录系列文章目录前言一、Springboot 简介#xff1f;1.1 什么是启动器#xff1f;1.2 Springboot 优点1.3 Springboot 核心二、搭建方式2.1 搭建方式一2.2 搭建方式二2.3 搭建方式三三、启动原理3.1 初始化SrpingApplication对象3.2 执行run()方法1. 加载监…系列文章目录 文章目录系列文章目录前言一、Springboot 简介1.1 什么是启动器1.2 Springboot 优点1.3 Springboot 核心二、搭建方式2.1 搭建方式一2.2 搭建方式二2.3 搭建方式三三、启动原理3.1 初始化SrpingApplication对象3.2 执行run()方法1. 加载监听器2. 构造上下文环境3. 初始化应用上下文4. 刷新应用上下文准备阶段5. 刷新上下文6. 自动装配原理总结四、项目配置4.1 properties配置文件4.2 yml配置文件4.3 配置目录及优先级1. 配置文件存放位置2. 配置文件存放读取优先级3. 配置文件4. springboot 项目结构五、整合Mybatis5.1 导入依赖5.2 编写配置文件: appliction.yml中添加如下配置5.3 编写功能代码六、整合logback七、整合PageHelper7.1 PageHelper插件7.2 实现原理7.3 使用方法7.4 PageInfo对象和Page对象的区别八、整合Druid九、整合JSP十、整合FreeMarker10.1 介绍10.2 使用10.3 常用指令十一、整合Thymeleaf11.1 介绍11.2 基础语法11.3 内置对象十二、 模板引擎总结12.1 jsp12.2 freemarker12.3 Thymeleaf十三、项目打包部署13.1 打包 jar13.2 打包war十四、异常处理14.1 简介14.2 具体实现十五、单元测试类十六、bean管理十七、拦截器十八、 其他拓展18.1 注解拓展18.2 静态资源类18.3 文件上传18.4 MyBatis-plus18.5 JUnit5单元测试总结前言
Spring Boot是由Pivotal团队提供的全新框架其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置从而使开发人员不再需要定义样板化的配置。通过这种方式Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。 一、Springboot 简介
Spring Boot实际上是利用Spring Framework 4 自动配置特性完成。编写项目时不需要编写xml文件。发展到现在Spring Boot已经具有很很大的生态圈各种主流技术已经都提供了Spring Boot的启动器。
1.1 什么是启动器
Spring框架在项目中作用是Spring整合各种其他技术让其他技术使用更加方便。Spring Boot的启动器实际上就是一个依赖。这个依赖中包含了整个这个技术的相关jar包还包含了这个技术的自动配置以前绝大多数XML配置都不需要配置了。当然了启动器中自动配置无法实现所有内容的自动配置在使用Spring Boot时还需要进行少量的配置这个配置不是在xml中了而是在properties或yml中即可。如果是Spring自己封装的启动器的artifact id名字满足spring-boot-starter-xxxx如果是第三方公司提供的启动满足xxxx-spring-boot-starter。以后每次使用Spring Boot整合其他技术时首先需要考虑导入启动器。
1.2 Springboot 优点
使用Spring Boot可以创建独立的Spring应用程序在Spring Boot中直接嵌入了Tomcat、Jetty、Undertow等Web 容器在使用SpringBoot做Web开发时不需要部署WAR文件通过提供自己的启动器(Starter)依赖简化项目构建配置尽量的自动配置Spring和第三方库绝对没有代码生成也不需要XML配置文件
1.3 Springboot 核心
起步依赖- 起步依赖本质上是一个Maven项目对象模型Project Object ModelPOM定义了对其他库的传递依赖这些东西加在一起即支持某项功能。 简单的说起步依赖就是将具备某种功能的坐标打包到一起并提供一些默认的功能。 自动配置 -Spring Boot的自动配置是一个运行时更准确地说是应用程序启动时的过程考虑了众多因素才决定 Spring配置应该用哪个不该用哪个。该过程是Spring自动完成的。
二、搭建方式
2.1 搭建方式一 导入依赖1 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersion!--继承父项目方式--parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.4.5/version/parentgroupIdcom.msb/groupIdartifactIdspringboot01/artifactIdversion1.0-SNAPSHOT/versiondependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.4.5/version/dependency/dependencies
/project依赖传递 创建一个controller
package com.msb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/*** Author: bingwoo*/
Controller
public class FirstController {RequestMapping(/firstController)ResponseBodypublic String firstController(){return hello springboot;}
}创建启动类
Spring Boot的启动类的作用是启动Spring Boot项目是基于Main方法来运行的。注意启动类在启动时会做注解扫描(Controller、Service、Repository…)扫描位置为同包或者子包下的注解所以启动类的位置应放于包的根下。
启动类与启动器区别 启动类表示项目的启动入口 启动器表示jar包的坐标 必须在包中新建这个类不能直接放入到java文件夹。 在com.msb下新建自定义名称的类(规范XXXXApplication),可以是项目上下文路径Application package com.msb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//启动类
//可以自动扫描当前类所在包及子包的注解
//注意此类要放入到包中 在 和controller包同一个层次即可
SpringBootApplication
public class Springboot01Application {public static void main(String[] args) {SpringApplication.run(Springboot02Application.class, args);}
}启动日志
2.2 搭建方式二 在公司中可能会出现必须继承某个项目如果Spring Boot用了继承就不能继承别的项目了。所以Spring Boot还提供了依赖的方式。 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.msb/groupIdartifactIdspringboot01/artifactIdversion1.0-SNAPSHOT/versiondependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion2.4.5/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.4.5/version/dependency/dependencies
/project使用idea自带springBoot项目初始化插件
2.3 搭建方式三
在spingboot官网直接创建官网地址
三、启动原理 简单了解启动原理可以看这边文章文字地址想详细了解建议耐心看完本 SpringBoot的入口是从SpringApplication.run()传入我们的主启动类开始 SpringBootApplication
public class LeeSpringbootApplication {public static void main(String[] args) {SpringApplication.run(LeeSpringbootApplication.class, args);}
}run()方法 初始化SrpingApplication对象执行run() 方法(primarySources主启动类class)public static ConfigurableApplicationContext run(Class?[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
}3.1 初始化SrpingApplication对象 设置应用类型后面会根据类型初始化对应的环境常用的一般都是servlet环境 加载系统中引导器Bootstrapper从META-INF/spring.factories中加载 初始化classpath下 META-INF/spring.factories 中已配置的ApplicationContextInitalizer 初始化classpath下所以已配置的 ApplicationListener 根据调用栈设置 main 方法的类名 public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources) {//设置资源加载器为nullthis.resourceLoader resourceLoader;//断言加载资源不能为nullAssert.notNull(primarySources, PrimarySources must not be null);//将primarySources数组转换为list最后放到LinkedHashSet集合中this.primarySources new LinkedHashSet(Arrays.asList(primarySources));// 1.1 推断应用类型后面会根据类型初始化对应的环境常用的一般都是servlet环境this.webApplicationType WebApplicationType.deduceFromClasspath();// 1.2 加载系统中引导器Bootstrapperthis.bootstrapRegistryInitializers new ArrayList(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));// 1.3 初始化classpath下 META-INF/spring.factories 中已配置的ApplicationContextInitalizersetInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 1.4 初始化classpath下所以已配置的 ApplicationListenersetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 1.5 根据调用栈设置 main 方法的类名this.mainApplicationClass deduceMainApplicationClass();
}在执行 getSpringFactoriesInstances(BootstrapRegistryInitializer. class ) 中会调用 loadSpringFactories() 方法遍历所有jar包中classpath下 META-INF/spring.factories文件并保存在缓存中 private static MapString, ListString loadSpringFactories(ClassLoader classLoader) {MapString, ListString result cache.get(classLoader);if (result ! null) {return result;}result new HashMap();try {EnumerationURL urls classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url urls.nextElement();UrlResource resource new UrlResource(url);Properties properties PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry?, ? entry : properties.entrySet()) {String factoryTypeName ((String) entry.getKey()).trim();String[] factoryImplementationNames StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key - new ArrayList()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) - implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException(Unable to load factories from location [ FACTORIES_RESOURCE_LOCATION ], ex);}return result;
}3.2 执行run()方法 获取并启动监听器 构造上下文环境 初始化应用上下文 刷新应用上下文前的准备阶段 刷新上下文 刷新应用上下文后的扩展接口 public ConfigurableApplicationContext run(String... args) {//记录程序运行时间long startTime System.nanoTime();// 创建 DefaultBootstrapContext 的一项DefaultBootstrapContext bootstrapContext createBootstrapContext();// ConfigurableApplicationContext spring的上下文ConfigurableApplicationContext context null;configureHeadlessProperty();// 1、获取并启动监听器SpringApplicationRunListeners listeners getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments new DefaultApplicationArguments(args);// 2、构造上下文环境ConfigurableEnvironment environment prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 处理需要忽略的BeanconfigureIgnoreBeanInfo(environment);// 打印banner springboot图标Banner printedBanner printBanner(environment);// 3、初始化应用上下文context createApplicationContext();context.setApplicationStartup(this.applicationStartup);// 4、刷新应用上下文前的准备阶段prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 5、刷新上下文refreshContext(context);// 6、刷新应用上下文后的扩展接口afterRefresh(context, applicationArguments);// 记录执行时间Duration timeTakenToStartup Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}listeners.started(context, timeTakenToStartup);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {Duration timeTakenToReady Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;
}1. 加载监听器 加载META-INF/spring.factories 中的 SpringApplicationRunListenerSpringApplicationRunListeners负责在springBoot启动的不同阶段广播出不同的消息传递给ApplicationListener监听器实现类 private SpringApplicationRunListeners getRunListeners(String[] args) {Class?[] types new Class?[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}在 getSpringFactoriesInstances 中加载构建监听器对象并根据order进行排序 private T CollectionT getSpringFactoriesInstances(ClassT type, Class?[] parameterTypes, Object... args) {ClassLoader classLoader getClassLoader();// Use names and ensure unique to protect against duplicatesSetString names new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));ListT instances createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}2. 构造上下文环境 根据之前标记的应用类型(SERVLET)创建相应的环境并根据配置文件配置相应的系统环境 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environment// 创建并配置相应环境ConfigurableEnvironment environment getOrCreateEnvironment();// 根据用户配置配置系统环境configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);// 启动监听器其中一个重要的监听器 ConfigFileApplicationListener 加载项目配置文件的监听器listeners.environmentPrepared(bootstrapContext, environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty(spring.main.environment-prefix),Environment prefix cannot be set via properties.);bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment convertEnvironment(environment);}ConfigurationPropertySources.attach(environment);return environment;
}3. 初始化应用上下文 根据配置的应用类型( SERVLET )创建对应的context ( AnnotationConfigServletWebServerApplicationContext ) 并在父类 GenericApplicationContext 的构造方法中创建了DefaultListableBeanFactoryioc容器 protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);
}ApplicationContextFactory DEFAULT (webApplicationType) - {try {switch (webApplicationType) {case SERVLET:return new AnnotationConfigServletWebServerApplicationContext();case REACTIVE:return new AnnotationConfigReactiveWebServerApplicationContext();default:return new AnnotationConfigApplicationContext();}}catch (Exception ex) {throw new IllegalStateException(Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory, ex);}
};4. 刷新应用上下文准备阶段 主要完成应用上下文属性设置并且将启动类生成实例对象保存到容器中。 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 设置容器环境context.setEnvironment(environment);// 执行容器后置处理(主要设置转换器)postProcessApplicationContext(context);// 应用初始化器执行容器中的 ApplicationContextInitializer 包括spring.factoriesapplyInitializers(context);// 向各个容器中发送容器已经准备好的事件listeners.contextPrepared(context);bootstrapContext.close(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory context.getBeanFactory();// 将main函数中的args参数封装成单例Bean注册到容器beanFactory.registerSingleton(springApplicationArguments, applicationArguments);if (printedBanner ! null) {// 将printedBanner 封装成单例Bean 注册到容器beanFactory.registerSingleton(springBootBanner, printedBanner);}if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// Load the sources// 获取主启动类SetObject sources getAllSources();Assert.notEmpty(sources, Sources must not be empty);// 加载启动类将启动类注册到容器load(context, sources.toArray(new Object[0]));// 发布容器中已加载的事件listeners.contextLoaded(context);
}postProcessApplicationContext(context) 设置转换器 protected void postProcessApplicationContext(ConfigurableApplicationContext context) {if (this.beanNameGenerator ! null) {context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,this.beanNameGenerator);}if (this.resourceLoader ! null) {if (context instanceof GenericApplicationContext) {((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);}if (context instanceof DefaultResourceLoader) {((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());}}if (this.addConversionService) {context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());}
}应用ApplicationContextInitializer protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class? requiredType GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, Unable to call initializer.);initializer.initialize(context);}
}getAllSource() 获取主启动类 public SetObject getAllSources() {SetObject allSources new LinkedHashSet();if (!CollectionUtils.isEmpty(this.primarySources)) {allSources.addAll(this.primarySources);}if (!CollectionUtils.isEmpty(this.sources)) {allSources.addAll(this.sources);}return Collections.unmodifiableSet(allSources);
}load() 主要将主启动类生成实例对象保存在容器中spring容器在启动的时候会将类解析成spring内部的BeanDefinition结构并将BeanDefinition存储到DefaultListableBeanFactory的map中。 protected void load(ApplicationContext context, Object[] sources) {if (logger.isDebugEnabled()) {logger.debug(Loading source StringUtils.arrayToCommaDelimitedString(sources));}// 创建 BeanDefinitionLoaderBeanDefinitionLoader loader createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);if (this.beanNameGenerator ! null) {loader.setBeanNameGenerator(this.beanNameGenerator);}if (this.resourceLoader ! null) {loader.setResourceLoader(this.resourceLoader);}if (this.environment ! null) {loader.setEnvironment(this.environment);}// 将启动类生成实例对象保存到容器中loader.load();
}//getBeanDefinitionRegistry(context) 将上下文转换为 BeanDefinitionRegistry 类型
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {if (context instanceof BeanDefinitionRegistry) {return (BeanDefinitionRegistry) context;}if (context instanceof AbstractApplicationContext) {return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();}throw new IllegalStateException(Could not locate BeanDefinitionRegistry);
}createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources) 创建 BeanDefinitionLoader其中创建一些Bean定义读取器 。 protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {return new BeanDefinitionLoader(registry, sources);
}BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {Assert.notNull(registry, Registry must not be null);Assert.notEmpty(sources, Sources must not be empty);this.sources sources;// 创建注解形式的Bean定义读取器 egConfiguration Bean Component Controller等this.annotatedReader new AnnotatedBeanDefinitionReader(registry);// 创建xml形式的Bean定义读取器this.xmlReader (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);this.groovyReader (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);// 创建类路径扫描器this.scanner new ClassPathBeanDefinitionScanner(registry);// 扫描器添加排除过滤器this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}loader.load()将启动类生成实例对象保存在容器中 。 void load() {for (Object source : this.sources) {//source 为启动类load(source); }
}private void load(Object source) {Assert.notNull(source, Source must not be null);if (source instanceof Class?) {// 从class中加载load((Class?) source);return;}if (source instanceof Resource) {// 从 Resource 中加载load((Resource) source);return;}if (source instanceof Package) {// 从 Package 中加载load((Package) source);return;}if (source instanceof CharSequence) {// 从 CharSequence 中加载load((CharSequence) source);return;}throw new IllegalArgumentException(Invalid source type source.getClass());
}private void load(Class? source) {if (isGroovyPresent() GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {// Any GroovyLoaders added in beans{} DSL can contribute beans hereGroovyBeanDefinitionSource loader BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());}if (isEligible(source)) {// 将启动类的 BeanDefinition 注册到 BeanDefinitionMap 中this.annotatedReader.register(source);}
}5. 刷新上下文 主要逻辑为AbstractApplicationContext 对象的 refresh() 方法进行整个容器的刷新过程会调用spring中的refresh()方法其中有13个关键方法来完成整个SpringBoot应用程序的启动。 private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {shutdownHook.registerApplicationContext(context);}refresh(context);
}protected void refresh(ConfigurableApplicationContext applicationContext) {applicationContext.refresh();
}public final void refresh() throws BeansException, IllegalStateException {try {super.refresh();}catch (RuntimeException ex) {WebServer webServer this.webServer;if (webServer ! null) {webServer.stop();}throw ex;}
}public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh this.applicationStartup.start(spring.context.refresh);// Prepare this context for refreshing.//1:准备刷新上下文环境prepareRefresh();// Tell the subclass to refresh the internal bean factory.//2:获取告诉子类初始化Bean工厂 不同工厂不同实现ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// 对bean工厂进行填充属性prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 执行beanFactroy后置处理器postProcessBeanFactory(beanFactory);StartupStep beanPostProcess this.applicationStartup.start(spring.context.beans.post-process);// Invoke factory processors registered as beans in the context.// 调用我们的bean工厂的后置处理器. 1. 会在此将class扫描成beanDefinition 2.bean工厂的后置处理器调用invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.// 注册我们bean的后置处理器registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.// 初始化国际化资源处理器.initMessageSource();// Initialize event multicaster for this context.// 创建事件多播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.onRefresh();// Check for listener beans and register them.//把我们的事件监听器注册到多播器上registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 实例化我们剩余的单实例bean.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn(Exception encountered during context initialization - cancelling refresh attempt: ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset active flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Springs core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}
}invokeBeanFactoryPostProcessors(beanFactory) 改方法会解析核心启动类中 SpringBootApplication实现自动配置 Ioc容器的初始化包括三个步骤该三个步骤在 invokeBeanFactoryPostProcessors 中完成 Resource定位 在SpringBoot中包扫描是从主类所在包开始扫描prepareContext()方法中会将主类解析成BeanDefinition保存在容器中然后在refresh()方法的 invokeBeanFactoryPostProcessors ()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。SpringBoot的各种starter是通过SPI扩展机制实现的自动装配SpringBoot的自动装配同样也是在 invokeBeanFactoryPostProcessors() 方法中实现的。在SpringBoot中有很多的EnableXXX注解其底层是Import注解在 invokeBeanFactoryPostProcessors() 方法中也实现了对该注解指定的配置类的定位加载。常规在SpringBoot中有三种定位方法主类所在的包、SPI扩展机制实现的自动装配、Import注解指定的类SPI 全称为 Service Provider Interface是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件自动加载文件里所定义的类 也可以这样理解 SPI是“基于接口的编程策略模式配置文件”组成实现的动态加载机制。 BeanDefinition的载入 SpringBoot会将通过定位得到的basePackage的路径拼装成 classpath:com/***/.class 的形式然后 PathMatchingResourcePatternResolver类会将该路径下所有的 .class 文件加载进来然后进行遍历判断是否含有 Component 注解如果有就是要装载的 BeanDefinition。 注册Beanfinition 注册过程是将载入过程中解析得到的BeanDefinition向IOC容器进行注册。通过上下文分析在容器中将BeanDefinition注入到一个ConcurrenHashMap中IOC容器通过这个map来保存BeanDefinition数据。 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime// (e.g. through an Bean method registered by ConfigurationClassPostProcessor)if (!NativeDetector.inNativeImage() beanFactory.getTempClassLoader() null beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}
}// PostProcessorRegistrationDelegate
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, ListBeanFactoryPostProcessor beanFactoryPostProcessors) {// WARNING: Although it may appear that the body of this method can be easily// refactored to avoid the use of multiple loops and multiple lists, the use// of multiple lists and multiple passes over the names of processors is// intentional. We must ensure that we honor the contracts for PriorityOrdered// and Ordered processors. Specifically, we must NOT cause processors to be// instantiated (via getBean() invocations) or registered in the ApplicationContext// in the wrong order.//// Before submitting a pull request (PR) to change this method, please review the// list of all declined PRs involving changes to PostProcessorRegistrationDelegate// to ensure that your proposal does not result in a breaking change:// https://github.com/spring-projects/spring-framework/issues?qPostProcessorRegistrationDelegateis%3Aclosedlabel%3A%22status%3Adeclined%22// Invoke BeanDefinitionRegistryPostProcessors first, if any.SetString processedBeans new HashSet();if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry (BeanDefinitionRegistry) beanFactory;ListBeanFactoryPostProcessor regularPostProcessors new ArrayList();ListBeanDefinitionRegistryPostProcessor registryProcessors new ArrayList();for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {BeanDefinitionRegistryPostProcessor registryProcessor (BeanDefinitionRegistryPostProcessor) postProcessor;registryProcessor.postProcessBeanDefinitionRegistry(registry);registryProcessors.add(registryProcessor);}else {regularPostProcessors.add(postProcessor);}}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let the bean factory post-processors apply to them!// Separate between BeanDefinitionRegistryPostProcessors that implement// PriorityOrdered, Ordered, and the rest.ListBeanDefinitionRegistryPostProcessor currentRegistryProcessors new ArrayList();// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.String[] postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.boolean reiterate true;while (reiterate) {reiterate false;postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();}// Now, invoke the postProcessBeanFactory callback of all processors handled so far.invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}else {// Invoke factory processors registered with the context instance.invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let the bean factory post-processors apply to them!String[] postProcessorNames beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,// Ordered, and the rest.ListBeanFactoryPostProcessor priorityOrderedPostProcessors new ArrayList();ListString orderedPostProcessorNames new ArrayList();ListString nonOrderedPostProcessorNames new ArrayList();for (String ppName : postProcessorNames) {if (processedBeans.contains(ppName)) {// skip - already processed in first phase above}else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.sortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);// Next, invoke the BeanFactoryPostProcessors that implement Ordered.ListBeanFactoryPostProcessor orderedPostProcessors new ArrayList(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);// Finally, invoke all other BeanFactoryPostProcessors.ListBeanFactoryPostProcessor nonOrderedPostProcessors new ArrayList(nonOrderedPostProcessorNames.size());for (String postProcessorName : nonOrderedPostProcessorNames) {nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);// Clear cached merged bean definitions since the post-processors might have// modified the original metadata, e.g. replacing placeholders in values...beanFactory.clearMetadataCache();
}private static void invokeBeanDefinitionRegistryPostProcessors(Collection? extends BeanDefinitionRegistryPostProcessor postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {StartupStep postProcessBeanDefRegistry applicationStartup.start(spring.context.beandef-registry.post-process).tag(postProcessor, postProcessor::toString);// 解析注解postProcessor.postProcessBeanDefinitionRegistry(registry);postProcessBeanDefRegistry.end();}
}实现自动装配: invokeBeanFactoryPostProcessors ()方法主要是对 ConfigurationClassPostProcessor 类的处理这是BeanDefinitionRegistryPostProcessor的子类BeanDefinitionRegistryPostProcessor 是BeanDefinitionRegistryPostProcessor 的子类调用BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry()方法会解析 PropertySource ComponentScans ComponentScan Bean Import 等注解refresh() - AbstractApplicationContext.invokeBeanFactoryPostProcessors(beanFactory) - invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()) - postProcessor.postProcessBeanDefinitionRegistry(registry) (ConfigurationClassPostProcessor类下的方法) - processConfigBeanDefinitions(registry) - new ConfigurationClassParser() (解析Configuration 标注的类) - parser.parse(candidates) (解析启动类上的注解)- this.reader.loadBeanDefinitions(configClasses) (生效自动配置类)
// ConfigurationClassPostProcessor
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException(postProcessBeanDefinitionRegistry already called on this post-processor against registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException(postProcessBeanFactory already called on this post-processor against registry);}this.registriesPostProcessed.add(registryId);processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {ListBeanDefinitionHolder configCandidates new ArrayList();String[] candidateNames registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! null) {if (logger.isDebugEnabled()) {logger.debug(Bean definition has already been processed as a configuration class: beanDef);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no Configuration classes were foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined Order value, if applicableconfigCandidates.sort((bd1, bd2) - {int i1 ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application contextSingletonBeanRegistry sbr null;if (registry instanceof SingletonBeanRegistry) {sbr (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator ! null) {this.componentScanBeanNameGenerator generator;this.importBeanNameGenerator generator;}}}if (this.environment null) {this.environment new StandardEnvironment();}// Parse each Configuration classConfigurationClassParser parser new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);SetBeanDefinitionHolder candidates new LinkedHashSet(configCandidates);SetConfigurationClass alreadyParsed new HashSet(configCandidates.size());do {StartupStep processConfig this.applicationStartup.start(spring.context.config-classes.parse);//获取所有bean的全路径解析各类注解parser.parse(candidates);parser.validate();SetConfigurationClass configClasses new LinkedHashSet(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader null) {this.reader new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 使自动配置类生效this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);processConfig.tag(classCount, () - String.valueOf(configClasses.size())).end();candidates.clear();if (registry.getBeanDefinitionCount() candidateNames.length) {String[] newCandidateNames registry.getBeanDefinitionNames();SetString oldCandidateNames new HashSet(Arrays.asList(candidateNames));SetString alreadyParsedClasses new HashSet();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) !alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware Configuration classesif (sbr ! null !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since itll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}parser.parse(candidates) 从启动类开始解析各种注解( PropertySource ComponentScan Import ImportResource Bean)加载配置类在processImports(configClass , sourceClass , getImports(sourceClass) , filter , true)中对启动类进行解析加载其中的Import注解的类public void parse(SetBeanDefinitionHolder configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException(Failed to parse configuration class [ bd.getBeanClassName() ], ex);}}this.deferredImportSelectorHandler.process();
}protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected void processConfigurationClass(ConfigurationClass configClass, PredicateString filter) throws IOException {if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}ConfigurationClass existingClass this.configurationClasses.get(configClass);if (existingClass ! null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else {// Explicit bean definition found, probably replacing an import.// Lets remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass asSourceClass(configClass, filter);do {sourceClass doProcessConfigurationClass(configClass, sourceClass, filter);}while (sourceClass ! null);this.configurationClasses.put(configClass, configClass);
}protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, PredicateString filter)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes firstprocessMemberClasses(configClass, sourceClass, filter);}// Process any PropertySource annotationsfor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info(Ignoring PropertySource annotation on [ sourceClass.getMetadata().getClassName() ]. Reason: Environment must implement ConfigurableEnvironment);}}// Process any ComponentScan annotations// 对启动类下的所有 ComponentScan 进行解析加载包含(RestController Service等)SetAnnotationAttributes componentScans AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// The config class is annotated with ComponentScan - perform the scan immediatelySetBeanDefinitionHolder scannedBeanDefinitions this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand null) {bdCand holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// Process any Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true);// Process any ImportResource annotationsAnnotationAttributes importResource AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource ! null) {String[] resources importResource.getStringArray(locations);Class? extends BeanDefinitionReader readerClass importResource.getClass(reader);for (String resource : resources) {String resolvedResource this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual Bean methodsSetMethodMetadata beanMethods retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass sourceClass.getMetadata().getSuperClassName();if (superclass ! null !superclass.startsWith(java) !this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass - processing is completereturn null;
}通过getImport(sourceClass) 解析启动类上的注解获取到其中被Import注解的类即AutoConfigurationPackages、AutoConfigurationImportSelector 启动类上SpringBootApplication注解为组合注解 SpringBootConfiguration其实质是一个 Configuration 注解表明该类是一个配置类EnableAutoConfiguration开启了自动配置功能AutoConfigurationPackage被该注解标注的类即主配置类将主配置类所在的包当作base-packageComponentScan直接向容器中注入指定的组件 在解析Import注解时会有一个getImports()方法从启动类开始递归解析注解把所有包含Import的注解都解析到然后再processImport()方法中对Import注解的类进行分类此处主要识别的是AutoConfigurationImportSelector 归属于ImportSelector的子类在后续的过程中会调用 DeferredImprotSelectorHandler中的process()方法来完成EnableAutoConfiguration的加载。 private SetSourceClass getImports(SourceClass sourceClass) throws IOException {SetSourceClass imports new LinkedHashSet();SetSourceClass visited new LinkedHashSet();collectImports(sourceClass, imports, visited);return imports;
}private void collectImports(SourceClass sourceClass, SetSourceClass imports, SetSourceClass visited)throws IOException {if (visited.add(sourceClass)) {for (SourceClass annotation : sourceClass.getAnnotations()) {String annName annotation.getMetadata().getClassName();if (!annName.equals(Import.class.getName())) {collectImports(annotation, imports, visited);}}imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), value));}
}执行 this. deferredImportSelectorHandler.process() 方法进行实现自动装配 public void process() {ListDeferredImportSelectorHolder deferredImports this.deferredImportSelectors;this.deferredImportSelectors null;try {if (deferredImports ! null) {DeferredImportSelectorGroupingHandler handler new DeferredImportSelectorGroupingHandler();deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);deferredImports.forEach(handler::register);handler.processGroupImports();}}finally {this.deferredImportSelectors new ArrayList();}
}// ConfigurationClassParser
public void processGroupImports() {for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {PredicateString exclusionFilter grouping.getCandidateFilter();grouping.getImports().forEach(entry - {ConfigurationClass configurationClass this.configurationClasses.get(entry.getMetadata());try {// 处理配置类上的注解processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),exclusionFilter, false);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException(Failed to process import candidates for configuration class [ configurationClass.getMetadata().getClassName() ], ex);}});}
}// DeferredImportSelectorGrouping
public IterableGroup.Entry getImports() {for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {// 遍历DeferredImportSelectorHolder对象集合deferredImportsdeferrdImports集合装了各种ImportSelectorAutoConfigurationImportSelectthis.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector());}// 经过上面处理然后再进行选择导入哪写配置类return this.group.selectImports();
}
//AutoConfigurationImportSelector
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() - String.format(Only %s implementations are supported, got %s,AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));// 获取自动配置类放入 AutoConfigurationEntry 对象中AutoConfigurationEntry autoConfigurationEntry ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);// 将封装了自动配置类的 AutoConfigurationEntry 对象装进 autoConfigurationEntries 集合this.autoConfigurationEntries.add(autoConfigurationEntry);// 遍历刚获取的自动配置类for (String importClassName : autoConfigurationEntry.getConfigurations()) {// 将符合条件的自动配置类作为 keyannotationMetadata作为值放进 entries 集合中this.entries.putIfAbsent(importClassName, annotationMetadata);}
}
// AutoConfigurationImportSelector
public IterableEntry selectImports() {if (this.autoConfigurationEntries.isEmpty()) {return Collections.emptyList();}.getAutoConfigurationEntry(annotationMetadata);// 得到所有要排除的自动配置类集合SetString allExclusions this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());.getAutoConfigurationEntry(annotationMetadata);// 得到经过过滤后所有符合条件的自动配置类集合SetString processedConfigurations this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));.getAutoConfigurationEntry(annotationMetadata);// 移除需要排除的自动配置类processedConfigurations.removeAll(allExclusions);.getAutoConfigurationEntry(annotationMetadata);// 对标注有 Order注解的自动配置类进行排序return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream().map((importClassName) - new Entry(this.entries.get(importClassName), importClassName)).collect(Collectors.toList());
}private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,CollectionSourceClass importCandidates, PredicateString exclusionFilter,boolean checkForCircularImports) {if (importCandidates.isEmpty()) {return;}if (checkForCircularImports isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));}else {this.importStack.push(configClass);try {for (SourceClass candidate : importCandidates) {if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector - delegate to it to determine importsClass? candidateClass candidate.loadClass();ImportSelector selector ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);PredicateString selectorFilter selector.getExclusionFilter();if (selectorFilter ! null) {exclusionFilter exclusionFilter.or(selectorFilter);}if (selector instanceof DeferredImportSelector) {this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);}else {String[] importClassNames selector.selectImports(currentSourceClass.getMetadata());CollectionSourceClass importSourceClasses asSourceClasses(importClassNames, exclusionFilter);processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);}}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// Candidate class is an ImportBeanDefinitionRegistrar -// delegate to it to register additional bean definitionsClass? candidateClass candidate.loadClass();ImportBeanDefinitionRegistrar registrar ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else {// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -// process it as an Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);}}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException(Failed to process import candidates for configuration class [ configClass.getMetadata().getClassName() ], ex);}finally {this.importStack.pop();}}
}执行 this. reader.loadBeanDefinitions(configClasses) 对自动配置类进行生效生成Bean对象。
6. 自动装配原理总结
当启动SpringBoot应用程序时会创建 SpringApplication 对象在对象的构造方法中进行某些参数的初始化工作最主要的是判断当前应用程序的类型及初始化器和监听器在这个过程中会加载整个应用程序的 META-INF/spring.factories 文件将文件的内容保存到缓存中(MapClassLoader , MapString , List cache )方便后续获取。SpringApplication对象创建完成后开始执行run() 方法来完成整个启动。启动过程中最主要的是有两个方法prepareContext()、refreshContext()在这两个方法中完成了自动装配的核心功能。在其之前的处理逻辑中包含了上下文对象的创建banner的打印等各个准备工作。在prepareContext()方法中主要完成的是对上下文对象的初始化操作包含了属性值的设置比如环境对象。在整个过程中load()方法完成将当前启动类作为一个BeanDefinition注册到registry中方便后续在进行BeanFactory调用执行时找到对应的主类来完成对 SpringBootApplication EnableAutoConfiguration等注解的解析工作。在 refreshContext()方法中会进行整个容器的刷新过程会调用spring中的refresh()方法。refresh()中有13个关键方法,在自动装配过程中会调用 invokeBeanFactoryPostProcessors()方法主要是对 ConfigurationClassPostProcessor 类的处理这是BeanDefinitionRegistryPostProcessor的子类BeanDefinitionRegistryPostProcessor 是BeanDefinitionRegistryPostProcessor 的子类调用BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry()方法会解析PropertySource ComponentScans ComponentScan Bean Import等注解。在解析Import注解时会有一个getImports()方法从启动类开始递归解析注解把所有包含Import的注解都解析到然后再processImport()方法中对Import注解的类进行分类此处主要识别的是AutoConfigurationImportSelector 归属于ImportSelector的子类在后续的过程中会调用 DeferredImprotSelectorHandler中的process()方法来完成EnableAutoConfiguration的加载。
四、项目配置
4.1 properties配置文件
SpringBoot默认读取项目下名字为application开头的 yml yaml properties配置文件resourcedirectory${basedir}/src/main/resources/directoryfilteringtrue/filteringincludesinclude**/application*.yml/includeinclude**/application*.yaml/includeinclude**/application*.properties/include/includes/resourceresourcedirectory${basedir}/src/main/resources/directoryexcludesexclude**/application*.yml/excludeexclude**/application*.yaml/excludeexclude**/application*.properties/exclude/excludes/resource
/resources在项目下的application.properties里修改端口号和项目上下文路径 注意这里的每一个. 都代表一个层级。SpringBoot常见配置查看官网文档 常见配置如下
4.2 yml配置文件
properties转换成yml之后,使用缩进代表层级关系基本格式要求 大小写敏感使用缩进代表层级关系相同的部分只出现一次注意空格 普通数据类型 server:port: 8888配置对象类型数据 person:name: zsage: 12sex: 男
#或者写成json格式
person2: {name: zss,age: 18 }配置数组类型 city:- beijing- tianjin- shanghai- chongqing
#或者
city2: [beijing,tianjin,shanghai,chongqing]4.3 配置目录及优先级
如果同一个目录下有application.yml也有application.properties默认先读取application.properties。如果同一个配置属性在多个配置文件都配置了默认使用第1个读取到的后面读取的不覆盖前面读取到的。
1. 配置文件存放位置
1 . 当前项目根目录中 2. 当前项目根目录下的一个/config子目录中 3. 项目的resources即classpath根路径中 4. 项目的resources即classpath根路径下的/config目录中
2. 配置文件存放读取优先级 当前项目根目录下的一个/config子目录中(最高) config/application.propertiesconfig/application.yml 当前项目根目录中(其次) application.propertiesapplication.yml 项目的resources即classpath根路径下的/config目录中(一般) resources/config/application.propertiesresources/config/application.yml 项目的resources即classpath根路径中(最后) resources/application.propertiesresources/application.yml
3. 配置文件 Spring Boot 中有两种上下文对象一种是 bootstrap, 另外一种是application(ServletContext), bootstrap 是应用程序的父上下文也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载它们默认也不能被本地相同配置覆盖。 bootstrap配置文件特征 boostrap 由父 ApplicationContext 加载比 applicaton 优先加载。boostrap 里面的属性不能被覆盖。 bootstrap与 application 的应用场景 application 配置文件主要用于 Spring Boot 项目的自动化配置。bootstrap 配置文件有以下几个应用场景。 使用 SpringCloudConfig 配置中心时这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息。一些固定的不能被覆盖的属性。一些加密/解密的场景。
4. springboot 项目结构 -- 项目名--src--main--javajava代码--resources--public 公共资源。所有共享的内容。对外公开的内容。--static静态资源。图片、js、css。不会被服务器解析。--js-- jquery.js 访问http://ip:port/js/jquery.js注意:该目录是SpringBoot可以直接识别的目录会将其中的静态资源编译到web项目中并放到tomcat中使用。静态资源的访问路径中无需声明static 例如:localhost:8080/a.png--templates FreeMarker thymeleaf 页面所在目录。--webapp 只有当页面使用jsp时才有。--WEB-INF设置WEB-INF
五、整合Mybatis
5.1 导入依赖
依赖dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.3/version
/dependency
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.21/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.12/versionscopeprovided/scope
/dependency5.2 编写配置文件: appliction.yml中添加如下配置
mapper-locations: classpath:mybatis/*.xml mapper映射文件包扫描 type-aliases-package 实体类别名包扫描spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mydb?useSSLfalseuseUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: root
mybatis:mapper-locations: classpath:mybatis/*.xmltype-aliases-package: com.msb.pojo5.3 编写功能代码
在启动类上添加注解表示mapper接口所在位置SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class,args);}
}定义mapper接口 如果不在MyApplication启动类上添加MapperScan必须在UserMapper接口上添加Mapper注解。 //Mapper
public interface UserMapper {ListUser selectAll();
}定义mapper.xml映射文件
在resource下新建mybatis文件夹mapper.xml文件名没有要求了不需要和接口名完全对应了是根据namespace去找接口。但是最好还是和接口名字保持一致 controller层代码 package com.msb.controller;
import com.msb.pojo.User;
import com.msb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/*** Author: bingwoo*/
Controller
RequestMapping(/user)
public class UserController {Autowiredprivate UserService userService;RequestMapping(/findAll)ResponseBodypublic ListUser findAll(){return userService.findAll();}
}service层代码
package com.msb.service.impl;
import com.msb.pojo.User;
import com.msb.mapper.UserMapper;
import com.msb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/*** Author: bingwoo*/
Service
public class UserServiceImpl implements UserService {Autowiredprivate UserMapper userMapper;Overridepublic ListUser findAll() {return userMapper.findAll();}
}idea中往往会误报代码错误,如果我们确定代码无问题,可以通过降低idea检查代码的严格程度来消除报错:快捷键: ctrlaltshifth
六、整合logback Spring Boot默认使用Logback组件作为日志管理。Logback是由log4j创始人设计的一个开源日志组件。 在Spring Boot项目中我们不需要额外的添加Logback的依赖因为在spring-boot-starter或者spring-boot-starter-web中已经包含了Logback的依赖。 Logback读取配置文件的步骤 1在classpath下查找文件logback-test.xml 2如果文件不存在则查找logback.xml ?xml version1.0 encodingUTF-8 ?configuration
!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-- property nameLOG_HOME value${catalina.base}/logs/ / !-- 控制台输出 -- appender nameStdout classch.qos.logback.core.ConsoleAppender!-- 日志输出格式 -- layout classch.qos.logback.classic.PatternLayout !--格式化输出%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-- pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n /pattern /layout /appender !-- 按照每天生成日志文件 -- appender nameRollingFile classch.qos.logback.core.rolling.RollingFileAppender rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy!--日志文件输出的文件名--FileNamePattern${LOG_HOME}/server.%d{yyyy-MM-dd}.log/FileNamePattern MaxHistory30/MaxHistory/rollingPolicy layout classch.qos.logback.classic.PatternLayout !--格式化输出%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-- pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n /pattern /layout !--日志文件最大的大小--triggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicyMaxFileSize10MB/MaxFileSize/triggeringPolicy/appender !-- 日志输出级别 --root levelinfo appender-ref refStdout / appender-ref refRollingFile / /rootlogger namecom.msb.mapper levelDEBUG/logger
!--日志异步到数据库 --
!--appender nameDB classch.qos.logback.classic.db.DBAppender日志异步到数据库 connectionSource classch.qos.logback.core.db.DriverManagerConnectionSource连接池 dataSource classcom.mchange.v2.c3p0.ComboPooledDataSourcedriverClasscom.mysql.jdbc.Driver/driverClassurljdbc:mysql://127.0.0.1:3306/databaseName/urluserroot/userpasswordroot/password/dataSource/connectionSource/appender --
/configuration七、整合PageHelper
7.1 PageHelper插件
我们在正常的查询业务之中,只需要加上一行代码就可以实现分页的数据的封装处理
7.2 实现原理
PageHelper方法使用了静态的ThreadLocal参数分页参数和线程是绑定的。内部流程是ThreadLocal中设置了分页参数pageIndexpageSize之后在查询执行的时候获取当线程中的分页参数执行查询的时候通过拦截器在sql语句中添加分页参数之后实现分页查询查询结束后在 finally 语句中清除ThreadLocal中的查询参数
7.3 使用方法
调用PageHelper方法PageHelper.startPage(pageNum, pageSize)MyBatis 查询方法 注意只要你可以保证在PageHelper方法调用后紧跟 MyBatis 查询方法这就是安全的。因为PageHelper在finally代码段中自动清除了ThreadLocal存储的对象。 添加PageHelper启动器依赖 dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper-spring-boot-starter/artifactIdversion1.2.12/version
/dependency控制器 package com.msb.controller;
import com.msb.pojo.Emp;
import com.msb.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/*** Author: bingwoo*/
Controller
RequestMapping(/emp)
public class EmpController {Autowiredprivate EmpService empService;RequestMapping(/findByPage/{pageNum}/{pageSize})ResponseBodypublic ListEmp findByPage(PathVariable(pageNum) Integer pageNum,PathVariable(pageSize) Integer pageSize){return empService.findByPage(pageNum,pageSize);}
}Service层代码编写 package com.msb.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.msb.mapper.EmpMapper;
import com.msb.pojo.Emp;
import com.msb.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/*** Author: bingwoo*/
Service
public class EmpServiceImpl implements EmpService {Autowiredprivate EmpMapper empMapper;Overridepublic ListEmp findByPage(Integer pageNum, Integer pageSize) {PageEmp page PageHelper.startPage(pageNum, pageSize);ListEmp list empMapper.findAll();// 方式1System.out.println(当前页:page.getPageNum());System.out.println(总页数page.getPages());System.out.println(页大小:page.getPageSize());System.out.println(总记录数:page.getTotal());System.out.println(当前页数据page.getResult());// 方式2PageInfoEmp pi new PageInfo(list);System.out.println(当前页pi.getPageNum());System.out.println(总页数pi.getPages());System.out.println(页大小pi.getSize());System.out.println(总记录数pi.getTotal());System.out.println(当前页数据pi.getList());return list;}
}7.4 PageInfo对象和Page对象的区别 Page对象解析参数 private int pageNum; //当前页码
private int pageSize; //每页数据的数量
private int startRow; //始页首行行号
private int endRow; //尾页尾行行号
private long total; //总记录数
private int pages; //总页数
private Boolean reasonable; //分页合理化
private Boolean pageSizeZero; //当设置为true的时候如果pagesize设置为0或RowBounds的limit0就不执行分页返回全部结果PageInfo对象解析参数 private int pageNum; //当前页
private int pageSize; //每页显示数据条数
private int size; //当前页的数量
private int startRow; //始页首行行号
private int endRow; //尾页尾行行号
private long total; //总记录数
private int pages; //总页数
private ListT list; //查询结果的数据
private int firstPage; //首页
private int prePage; //上一页
private int nextPage; // 下一页
private int lastPage; //最后一页
private boolean isFirstPage; //是不是第一页
private boolean isLastPage; //是不是最后一页
private boolean hasPreviousPage;//有没有上一页
private boolean hasNextPage; //有没有下一页
private int navigatePages; //所有导航页号
private int[] navigatepageNums; //导航页码数八、整合Druid 介绍Druid是由阿里巴巴推出的数据库连接池。它结合了C3P0、DBCP、PROXOOL等数据库连接池的优点。之所以从众多数据库连接池中脱颖而出还有一个重要的原因就是它包含控制台,很方便的帮助我们实现对于sql执行的监控。 添加依赖 dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.1.10/version
/dependency修改配置文件application.yml spring:datasource:# 使用阿里的Druid连接池type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver# 填写你数据库的url、登录名、密码和数据库名url: jdbc:mysql://127.0.0.1:3306/mydb?useSSLfalseuseUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghaiusername: rootpassword: rootdruid:# 连接池的配置信息# 初始化大小最小最大initial-size: 5min-idle: 5maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测检测需要关闭的空闲连接单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间单位是毫秒minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1testWhileIdle: truetestOnBorrow: falsetestOnReturn: false# 打开PSCache并且指定每个连接上PSCache的大小poolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20# 配置监控统计拦截的filters去掉后监控界面sql无法统计wall用于防火墙filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能慢SQL记录connectionProperties: druid.stat.mergeSql\true;druid.stat.slowSqlMillis\5000# 配置DruidStatFilterweb-stat-filter:enabled: trueurl-pattern: /*exclusions: *.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*# 配置DruidStatViewServletstat-view-servlet:url-pattern: /druid/*# IP白名单(没有配置或者为空则允许所有访问)allow: 127.0.0.1,192.168.8.109# IP黑名单 (存在共同时deny优先于allow)deny: 192.168.1.188# 禁用HTML页面上的“Reset All”功能reset-enable: false# 登录名login-username: admin# 登录密码login-password: 123456九、整合JSP
添加依赖
!--JSP依赖--
dependencygroupIdorg.apache.tomcat.embed/groupIdartifactIdtomcat-embed-jasper/artifactIdscopeprovided/scope
/dependency添加webapps目录并设置目录 设置工作目录如果在IDEA中项目结构为聚合工程。那么在运行jsp是需要指定路径。如果项目结构为独立项目则不需要。 在 yml配置文件中配置视图解析器参数 在控制类中声明单元方法请求转发jsp资源package com.msb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/*** Author: bingwoo*/
Controller
public class PageController {RequestMapping(/{uri})public String getPage(PathVariable(uri) String uri){return uri;}
}十、整合FreeMarker
10.1 介绍
FreeMarker是一款模板引擎 即一种基于模板和要改变的数据 并用来生成输出文本HTML网页、电子邮件、配置文件、源代码等的通用工具。 它不是面向最终用户的而是一个Java类库是一款程序员可以嵌入他们所开发产品的组件。FreeMarker是免费的基于Apache许可证2.0版本发布。其模板编写为FreeMarker Template LanguageFTL属于简单、专用的语言。需要准备数据在真实编程语言中来显示比如数据库查询和业务运算 之后模板显示已经准备好的数据。在模板中主要用于如何展现数据 而在模板之外注意于要展示什么数据。 常用的java模板引擎还有哪些 Jsp、Freemarker、Thymeleaf 、Velocity 等。模板数据模型输出freemarker并不关心数据的来源只是根据模板的内容将数据模型在模板中显示并输出文件通常为html也可以生成其它格式的文本文件freemarker作为springmvc一种视图格式默认情况下SpringMVC支持freemarker视图格式。 需要创建Spring BootFreemarker工程用于测试模板。
10.2 使用 导入依赖 !--freeMaker依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-freemarker/artifactId
/dependency创建controller RequestMapping(/freemarker)
Controller
public class FreemarkerController {RequestMapping(/show)public String freemarker(MapString, Object map){map.put(name,msb);//返回模板文件名称return show;}
}通过查阅配置信息发现,默认前缀为 ‘’ ,后缀为.ftlh,默认路径为templates templates目录下创建模板文件 !DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/title
/head
body
this is showftlh
br/
${name}
img srcimg/a.jpg/img
/body
/html10.3 常用指令
A遍历List集合 注释即#‐‐和‐‐介于其之间的内容会被freemarker忽略插值Interpolation即..部分,freemarker会用真实的值代替{..}部分,freemarker会用真实的值代替..部分,freemarker会用真实的值代替{…}FTL指令和HTML标记类似名字前加#予以区分Freemarker会解析标签中的表达式或逻辑。文本仅文本信息这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析直接输出内容。Controller
public class FreemarkerController {Autowiredprivate EmpService empService;RequestMapping(/showEmp)public ModelAndView testList(){ModelAndView mv new ModelAndView();ListEmp list empService.findAll();mv.addObject(empList, list);mv.setViewName(showEmp);return mv;}
}页面代码!DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/titlestyle typetext/css#empTable{width: 80%;border: 1px solid blue;margin: 0px auto;}#empTable th,td{border: 1px solid green;text-align: center;}/style
/head
body
table idempTable cellpadding0px cellspacing0pxtrth索引/thth工号/thth姓名/thth岗位/thth薪资/thth部门号/th/tr#list empList as emptrtd${emp_index}/tdtd${emp.empno}/tdtd${emp.ename}/tdtd${emp.job}/tdtd${emp.sal}/tdtd${emp.deptno}/td/tr/#list
/table
/body
/html说明 _index得到循环的下标使用方法是在stu后边加_index它的值是从0开始 遍历Map数据 遍历输出指定内容controllerController
public class FreemarkerController {Autowiredprivate EmpService empService;RequestMapping(/showEmpMap)public ModelAndView testMap(){ModelAndView mv new ModelAndView();ListEmp list empService.findAll();MapString,Emp empMap new HashMap();for (Emp emp : list) {empMap.put(emp.getEmpno().toString(), emp);}mv.addObject(empMap, empMap);mv.setViewName(showEmpMap);return mv;}
}页面FreeMarker在遍历map集合是,key必须是String!DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/titlestyle typetext/css#empTable{width: 80%;border: 1px solid blue;margin: 0px auto;}#empTable th,td{border: 1px solid green;text-align: center;}/style
/head
body
输出7521员工信息br/
工号:${empMap[7521].empno}br/
姓名:${empMap[7521].ename}br/
岗位:${empMap[7521].job}br/
薪资:${empMap[7521].sal}br/
部门号:${empMap[7521].deptno}br/
br/
遍历EmpMap
table idempTable cellpadding0px cellspacing0pxtrth索引/thth工号/thth姓名/thth岗位/thth薪资/thth部门号/th/tr#list empMap?keys as ktrtd${k_index}/tdtd${k}/tdtd${empMap[k].ename}/tdtd${empMap[k].job}/tdtd${empMap[k].sal}/tdtd${empMap[k].deptno}/td/tr/#list
/table
/body
/htmlif指令 if 指令即判断指令是常用的FTL指令freemarker在解析时遇到if会进行判断条件为真则输出if中间的内容否 则跳过内容不再输出。 if中支持的运算符 算数运算符 FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:, - , * , / , % 逻辑运算符有如下几个: 逻辑与: 逻辑或:|| 逻辑非:! 逻辑运算符只能作用于布尔值,否则将产生错误 c比较运算符有如下几个: ① 或者:判断两个值是否相等. ② !:判断两个值是否不等. ③ 或者gt:判断左边值是否大于右边值 ④ 或者gte:判断左边值是否大于等于右边值 ⑤ 或者lt:判断左边值是否小于右边值 ⑥ 或者lte:判断左边值是否小于等于右边值 注意: 和!可以用于字符串,数值和日期来比较是否相等,但和!两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,“x”,x ,X是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替会有更好的效果,因为 FreeMarker会把解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:#if (xy) 如何判断空值 判断某变量是否存在使用 “??” 用法为:variable??,如果该变量存在,返回true,否则返回false 例为防止stus为空报错可以加上判断如下缺失变量默认值使用 “!” 使用!要以指定一个默认值当变量为空时显示默认值。例 ${name!‘’}表示如果name为空显示空字符串。如果是嵌套对象则建议使用括起来。#if empList??#list empList as emptr #if emp_index%2 0 stylebackground-color: gray /#iftd${emp_index}/tdtd${emp.empno}/tdtd #if emp.ename KING stylecolor: aqua /#if${emp.ename}/tdtd${emp.job}/tdtd${emp.mgr!无}/tdtd #if emp.sal gte 2000.0 stylecolor: red /#if${emp.sal}/tdtd${emp.comm!0}/tdtd${emp.deptno}/td/tr/#list/#if内置函数 内建函数语法格式 变量?函数名称 内建函数获取某个集合的大小: ${集合名?size}内建函数日期格式化 显示年月日: ${today?date} 显示时分秒 today?time显示日期时间{today?time} 显示日期时间today?time显示日期时间{today?datetime} 自定义格式化 ${today?string(“yyyy年MM月”)}内建函数将json字符串转成对象#assign text{bank:工商银行,account:10101920201920212} /
#assign datatext?eval /
开户行${data.bank} 账号${data.account}其中用到了 assign标签assign的作用是定义一个变量。页面员工人数:${empList?size}
table idempTable cellpadding0px cellspacing0pxtrth索引/thth工号/thth姓名/thth岗位/thth上级/thth入职日期a/thth入职日期b/thth入职日期c/thth入职日期d/thth薪资/thth补助/thth部门号/th/tr#if empList??#list empList as emptr #if emp_index%2 0 stylebackground-color: gray /#iftd${emp_index}/tdtd${emp.empno}/tdtd #if emp.ename KING stylecolor: aqua /#if${emp.ename}/tdtd${emp.job}/tdtd${emp.mgr!无}/tdtd${emp.hiredate?date}/tdtd${emp.hiredate?time}/tdtd${emp.hiredate?datetime}/tdtd${emp.hiredate?string(yyyy年MM月dd日)}/tdtd #if emp.sal gte 2000.0 stylecolor: red /#if${emp.sal}/tdtd${emp.comm!0}/tdtd${emp.deptno}/td/tr/#list/#if
/table十一、整合Thymeleaf
11.1 介绍 Thymeleaf的主要目标是将优雅的自然模板带到开发工作流程中并将HTML在浏览器中正确显示并且可以作为静态原型让开发团队能更容易地协作。Thymeleaf能够处理HTMLXMLJavaScriptCSS甚至纯文本。 长期以来,jsp在视图领域有非常重要的地位,随着时间的变迁,出现了一位新的挑战者:Thymeleaf,Thymeleaf是原生的,不依赖于标签库.它能够在接受原始HTML的地方进行编辑和渲染.因为它没有与Servelet规范耦合,因此Thymeleaf模板能进入jsp所无法涉足的领域。 Thymeleaf在Spring Boot项目中放入到resources/templates中。这个文件夹中的内容是无法通过浏览器URL直接访问的和WEB-INF效果一样所有Thymeleaf页面必须先走控制器。 创建项目,准备配置文件及各层级代码项目中添加依赖 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.3/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.21/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.12/versionscopeprovided/scope/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.1.10/version/dependencydependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper-spring-boot-starter/artifactIdversion1.2.12/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactIdversion2.4.5/version/dependency
/dependencies在resources下新建templates文件夹。新建index.html package com.msb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/*** Author: bingwoo*/
Controller
public class ThymeleafController {RequestMapping(showIndex)public String showIndex(){return index;}
}11.2 基础语法
Thymeleaf通过标准变量表达式完成数据的展示和处理 标准变量表达式必须依赖标签,不能独立使用标准变量表达式一般在开始标签中,以 th开头语法为: tag th:***“${key}” 表达式中可以通过${}取出域中的值并放入标签的指定位置${}在这里不能单独使用,必须在 th:后面的双引号里使用 th:text属性 !--向span双标签内部添加文本--span th:textpageMessage/span br/!--从域中根据参数名取出参数值放在双标签中--span th:text${msg}/span br/th:value 获取值 !--向input标签中的value属性赋值--
input typetext th:valuepageMessage/
!--从域中根据参数名取出参数值 向input标签中的value属性赋值--
input typetext th:value${msg}/th:if 判断 span th:if${name}!张三会显示/span循环遍历.th:each - th:eachu,i :${list} 其中i表示迭代状态。index:当前迭代器的索引 从0开始count:当前迭代对象的计数 从1开始size:被迭代对象的长度even/odd:布尔值当前循环是否是偶数/奇数 从0开始first:布尔值当前循环的是否是第一条如果是返回true否则返回falselast:布尔值当前循环的是否是最后一条如果是则返回true否则返回false !DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlestyle typetext/css#empTable{width: 80%;border: 1px solid blue;margin: 0px auto;}#empTable th,td{border: 1px solid green;text-align: center;}/style
/head
body
//展示单个员工信息:
span th:if${emp}!null工号:span th:text${emp.empno}/spanbr/姓名:span th:text${emp.ename}/spanbr/职务:span th:text${emp.job}/spanbr/上级:span th:text${emp.mgr}/spanbr/入职日期:span th:text${emp.hiredate}/spanbr/工资:span th:text${emp.sal}/spanbr/补助:span th:text${emp.comm}/spanbr/部门号:span th:text${emp.deptno}/spanbr/
/span
hr/
span th:if${empList}!nullspan th:if${empList.size()} ne 0工号:span th:text${empList[0].empno}/spanbr/姓名:span th:text${empList[0].ename}/spanbr/职务:span th:text${empList[0].job}/spanbr/上级:span th:text${empList[0].mgr}/spanbr/入职日期:span th:text${empList[0].hiredate}/spanbr/工资:span th:text${empList[0].sal}/spanbr/补助:span th:text${empList[0].comm}/spanbr/部门号:span th:text${empList[0].deptno}/spanbr//span
/span
table idempTable cellpadding0px cellspacing0pxtrth索引/thth序号/thth总人数/thth偶数索引?/thth奇数索引?/thth第一?/thth最后?/thth工号/thth姓名/thth职务/thth上级/thth入职日期/thth工资/thth补助/thth部门号/th/trtr th:eachemp,i:${empList}td th:text${i.index}/tdtd th:text${i.count}/tdtd th:text${i.size}/tdtd th:text${i.odd}/tdtd th:text${i.even}/tdtd th:text${i.first}/tdtd th:text${i.last}/tdtd th:text${emp.empno}/tdtd th:text${emp.ename}/tdtd th:text${emp.job}/tdtd th:text${emp.mgr}/tdtd th:text${emp.hiredate}/tdtd th:text${emp.sal}/tdtd th:text${emp.comm}/tdtd th:text${emp.deptno}/td/tr
/table
/body
/html算数运算符 , - , * , / , % span th:text11/span
span th:text11/span
span th:text${emp.empno}1/span
span th:text${emp.empno1}/span关系运算符 1 gt: great than大于
2 ge great equal大于等于
3 eq equal等于
4 lt less than小于
5 le less equal小于等于
6 ne not equal不等于! 逻辑运算符: 或 and: 表示并且 || 或 or : 表示或者 div th:text10 and 23/div
div th:text10 and 23/div
div th:text10 or 23/div
div th:text10 or 23/div
hr/
div th:text${emp.sal ge 800}/div
div th:text${emp.sal } ge 800/div
div th:text${emp.sal ge 800} and ${emp.deptno eq 20}/div
div th:text(${emp.sal }ge 800) or (${emp.deptno } ne 20)/div
div th:text${emp.sal ge 800 or emp.deptno ne 20 }/div三目运算符 tr th:eachemp,i:${empList} th:class${i.odd}?a:b对空值作出处理
tr th:eachemp,i:${empList} th:class${i.odd}?a:btd th:text${i.index}/tdtd th:text${i.count}/tdtd th:text${emp.mgr} eq null ?老板:${emp.mgr}/tdtd th:text${emp.hiredate}/tdtd th:text${emp.sal}/tdtd th:text${emp.comm} eq null ?0:${emp.comm}/tdtd th:text${emp.deptno}/td
/trth:href a th:href{/getParam(id1,namemsb)} 跳转/a
!-- 获取作用域值--
a th:href{/getParam(name${stu.name},age${stu.age})}跳转二/ath:onclick :给元素绑定事件,单击事件并传递参数
//写法1:仅仅支持数字和布尔类型参数的传递,字符串不支持
a hrefjavascript:viod(0) th:onclickdel(${emp.empno})删除/a
//写法2:支持数字和文本类型的参数传递
a hrefjavascript:void(0) th:onclickdelEmp([[${emp.empno}]],[[${emp.ename}]])删除/a11.3 内置对象
Thymeleaf提供了一些内置对象内置对象可直接在模板中使用。这些对象是以#引用的。使用内置对象的语法 引用内置对象需要使用# 大部分内置对象的名称都以s结尾。如strings、numbers、dates 常见内置对象如下 #arrays数组操作的工具
#aggregates操作数组或集合的工具
#bools判断boolean类型的工具
#calendars类似于#dates但是是java.util.Calendar类的方法
#ctx上下文对象可以从中获取所有的thymeleaf内置对象
#dates日期格式化内置对象具体方法可以参照java.util.Date
#numbers 数字格式化#strings字符串格式化具体方法可以参照String如startsWith、contains等
#objects参照java.lang.Object
#lists列表操作的工具参照java.util.List
#setsSet操作工具参照java.util.Set#mapsMap操作工具参照java.util.Map
#messages操作消息的工具。strings对象 dates对象 #numbers #numbers.formatDecimal(numbwe,整数位,整数位千分位标识符,小数位,小数位表示符)${#numbers.formatDecimal(num,1,COMMA,2,POINT)}显示99,999,999.991表示整数位至少一位不足以0补齐如num 0.00${#numbers.formatDecimal(num,0,COMMA,2,POINT)}则显示 .00${#numbers.formatDecimal(num,1,COMMA,2,POINT)}则显示 0.00COMMA,POINT‘.’域对象 代码示例 RequestMapping(showIndex)
public String showIndex(MapString,Object map, HttpServletRequest req, HttpSession session){// 向request域放数据req.setAttribute(msg, requestMessage);// 向session域放数据session.setAttribute(msg, sessionMessage);// 向application域放数据req.getServletContext().setAttribute(msg, applicationMessage);// 对象List集合数据ListEmp empList empService.findAll();map.put(empList, empList);return index;
}!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlestyle typetext/css#empTable{width: 80%;border: 1px solid blue;margin: 0px auto;}#empTable th,td{border: 1px solid green;text-align: center;}.a{background-color: antiquewhite;}.b{background-color: gray;}/style
/head
body
table idempTable cellpadding0px cellspacing0pxtrth索引/thth序号/thth总人数/thth偶数索引?/thth奇数索引?/thth第一?/thth最后?/thth工号/thth姓名/thth职务/thth上级/thth入职日期/thth入职年/thth入职月/thth入职日/thth工资/thth补助/thth部门号/thth操作/th/trtr th:eachemp,i:${empList} th:class${i.odd}?a:btd th:text${i.index}/tdtd th:text${i.count}/tdtd th:text${i.size}/tdtd th:text${i.odd}/tdtd th:text${i.even}/tdtd th:text${i.first}/tdtd th:text${i.last}/tdtd th:text${emp.empno}/tdtd th:text${emp.ename}/tdtd th:text${emp.job}/tdtd th:text${#strings.isEmpty(emp.mgr)}?老板:${emp.mgr}/tdtd th:text${#dates.format(emp.hiredate,yyyy-MM-dd HH:mm:ss)}/tdtd th:text${#dates.year(emp.hiredate)}/tdtd th:text${#dates.month(emp.hiredate)}/tdtd th:text${#dates.day(emp.hiredate)}/tdtd th:text${#numbers.formatDecimal(emp.sal,7,COMMA,2,POINT)}/tdtd th:text${#strings.isEmpty(emp.comm)}?0:${#numbers.formatDecimal(emp.sal,7,COMMA,2,POINT)}/tdtd th:text${emp.deptno}/tdtda hrefjavascript:void(0) th:onclickremoveEmp([[${emp.empno}]],[[${emp.ename}]])删除/a/td/tr/table
scriptfunction removeEmp(empno,ename){var resulet confirm(确定要删除编号为empno的ename);if(resulet){window.location.hrefremoveEmp?empnoempnoenameename;}}
/script
hr/
request:br/
span th:text${#httpServletRequest.getAttribute(msg)}/spanbr/
span th:text${#request.getAttribute(msg)}/spanbr/
span th:text${msg}/spanbr/
session:br/
span th:text${#httpSession.getAttribute(msg)}/spanbr/
span th:text${#session.getAttribute(msg)}/spanbr/
span th:text${session.msg}/spanbr/
application:br/
span th:text${#servletContext.getAttribute(msg)}/spanbr/
span th:text${application.msg}/spanbr/
/body
/html十二、 模板引擎总结
12.1 jsp
优点 功能强大可以写java代码支持jsp标签jsp tag支持表达式语言el官方标准用户群广丰富的第三方jsp标签库 缺点 性能问题。不支持前后端分离
12.2 freemarker
FreeMarker是一个用Java语言编写的模板引擎它基于模板来生成文本输出。FreeMarker与Web容器无关即在Web运行时它并不知道Servlet或HTTP。它不仅可以用作表现层的实现技术而且还可以用于生成XMLJSP或Java 等。 目前企业中:主要用Freemarker做静态页面或是页面展示
优点 不能编写java代码可以实现严格的mvc分离性能非常不错对jsp标签支持良好内置大量常用功能使用非常方便宏定义类似jsp标签非常方便使用表达式语言 缺点 不是官方标准用户群体和第三方标签库没有jsp多
12.3 Thymeleaf
hymeleaf是个XML/XHTML/HTML5模板引擎可以用于Web与非Web应用。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码开发者只需将标签属性添加到模板中即可。接下来这些标签属性就会在DOM文档对象模型上执行预先制定好的逻辑。Thymeleaf的可扩展性也非常棒。你可以使用它定义自己的模板属性集合这样就可以计算自定义表达式并使用自定义逻辑。这意味着Thymeleaf还可以作为模板引擎框架。
优点静态html嵌入标签属性浏览器可以直接打开模板文件便于前后端联调。springboot官方推荐方案。缺点模板必须符合xml规范
十三、项目打包部署
13.1 打包 jar
SpringBoot项目可以是jar类型的maven项目也可以是一个war类型的maven项目取决于我们要不要整合jsp使用。但是不管是哪种项目类型已经不是我们传统意义上的项目结构了在本地使用SpringBoot的启动器即可访问我们开发的项目。如果我们将项目功能开发完成后需要使用SpringBoot的打包功能来将项目进行打包。SpringBoot项目打包在linux服务器中运行: jar类型项目会打成jar包jar类型项目使用SpringBoot打包插件打包时会在打成的jar中内置一个tomcat的jar。所以我们可以使用jdk直接运行该jar项目可jar项目中有一个功能将功能代码放到其内置的tomcat中运行。我们直接使用浏览器访问即可。war类型项目会打成war包在打包时需要将内置的tomcat插件排除配置servlet的依赖。将war正常的放到tomcat服务器中运行即可。
导入springboot打包插件buildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationforktrue/fork/configuration/plugin/plugins
/build将项目导出成jar包并运行 使用maven package指令打包即可 打成包后,可以通过dos java -jar指令直接启动运行
13.2 打包war
将项目导出war包并运行项目打包成war之后,要放在一个Tomcat上运行如果我们当前的maven项目本身就是war类型的项目直接打包即可,但是如果我们当前的maven项目是jar类型的项目我们需要将项目修改为war类型修改项目的pom文件使用packaging标签设置值为war.并且需要在项目中创建webapp文件夹并设置为资源文件夹。 更改打包类型 webapp文件夹可加可不加 排除项目中自带的所有的Tomcat插件和jsp servlet 依赖,因为这里要将项目放到一个Tomcat上运行 !--配置SpringBoot的web启动器--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId!--排除web启动中自动依赖的tomcat插件--exclusionsexclusiongroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactId/exclusion/exclusions
/dependency
!--手动依赖tomcat插件但是表明项目打包时该依赖不会被打进去目的主要是保证开发阶段本地SpringBoot项目可以正常运行
--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactId!--打包的时候可以不用包进去别的设施会提供。事实上该依赖理论上可以参与编译测试运行等周期。相当于compile但是打包阶段做了exclude操作--scopeprovided/scope
/dependencySpringBoot的启动类继承SpringBootServletInitializer并重写configure SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {//重写配置方法Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(MyApplication.class);}public static void main(String[] args) {//启动SpringBootSpringApplication.run(MyApplication.class,args);}
}使用install命令打包项目并将war包放到tomcat下的webapps下启动tomcat即可。 如果我们使用的是tomcat7则需要将javax.el-api-3.0.0.jar包放到tomcat下 的lib目录中。
十四、异常处理
14.1 简介 SpringMVC异常简介系统中异常包括两类预期异常(检查型异常)和运行时异常 RuntimeException前者通过捕获异常从而获取异常信息 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。系统的 dao、service、controller 出现都通过 throws Exception 向上抛出最后由 springmvc 前端控制器交由异常处理器进行异常处理如下图 默认情况Spring Boot项目错误页面如下。当项目实际上线如果给用户显示这个页面就不是很友好。当系统出现异常时应该给用户显示更加友好的错误页面。 设置具体的状态码页面在templates/下新建error文件夹在error中新建状态码.html的页面。例如当出现500时显示的页面为500.html 使用x进行模糊匹配当出现5开头状态码的错误时显示页面可以命名为5xx.html 当出现50开头状态码的错误时显示页面可以命名为50x.html 统一错误显示页面在templates下新建error.html。如果项目中不存在具体状态码的页面或没有使用x成功匹配的页面时显示error.html作为错误显示页面。
14.2 具体实现 使用ExceptionHandler注解处理异常 缺点只能处理当前Controller中的异常。 Controller
public class ControllerDemo1 {RequestMapping(test1.action)public String test1(){int i 1/0;return success;}RequestMapping(test2.action)public String test2(){String s null;System.out.println(s.length());return success;}ExceptionHandler(value {ArithmeticException.class,NullPointerException.class} )public ModelAndView handelException(){ModelAndView mv new ModelAndView();mv.setViewName(error1);return mv;}
}使用ControllerAdviceExceptionHandler(此处优先级低于局部异常处理器) package com.msb.exceptionhandler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/*** Author: bingwoo*/
ControllerAdvice
public class GloableExceptionHandler1 {ExceptionHandler(value {ArithmeticException.class,NullPointerException.class} )public ModelAndView handelException(){ModelAndView mv new ModelAndView();mv.setViewName(error1);return mv;}
}使用SimpleMappingExceptionResolver(xml配置,配置类配置) /*** 全局异常*/
Configuration
public class GloableException2 {Beanpublic SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){SimpleMappingExceptionResolver resolver new SimpleMappingExceptionResolver();Properties prop new Properties();prop.put(java.lang.NullPointerException,error1);prop.put(java.lang.ArithmeticException,error2);resolver.setExceptionMappings(prop);return resolver;}
}自定义的HandlerExceptionResolver /*** 全局异常* HandlerExceptionResolve*/
Configuration
public class GloableException3 implements HandlerExceptionResolver {Overridepublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {ModelAndView mv new ModelAndView();if(e instanceof NullPointerException){mv.setViewName(error1);}if(e instanceof ArithmeticException){mv.setViewName(error2);}mv.addObject(msg,e);return mv;}
}十五、单元测试类
在src/main/test里面新建com.msb.项目上下文注意 测试类不能叫做Test,会和注解同名测试方法必须是public测试方法返回值必须是void测试方法必须没有参数 package com.msb;
import com.msb.pojo.Emp;
import com.msb.service.EmpService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
SpringBootTest(classes Springboot03Application.class)
class Springboot03AppliactionTests {Autowiredprivate EmpService empService;Testpublic void testFindAll() {ListEmp list empService.findAll();list.forEach(System.out::println);}
}十六、bean管理
Spring Boot中Bean管理 Spring Boot 由于没有XML文件所以所有的Bean管理都放入在一个配置类中实现。配置类就是类上具有Configuration的类。这个类就相当于之前的applicationContext.xml 新建配置类 com.msb.config.MyConfig 规范都是放入到config文件夹中。 注意配置类要有Configuration,方法要有Bean Configuration
public class MyConfig {//访问权限修饰符没有强制要求一般是protected//返回值就是注入到Spring容器中实例类型。// 方法名没有强制要求,相当于bean 中id属性。Beanprotected User getUser(){User user new User();user.setId(1L);user.setName(张三);return user;}//自定义bean名称Bean(user2)protected User getUser2(){User user new User();user.setId(2L);user.setName(李四);return user;}
}如果Spring容器中存在同类型的Bean通过Bean的名称获取到Bean对象。或结合Qualifier使用 SpringBootTest
public class TestGetBean {AutowiredQualifier(user2)private User user;Testpublic void testGetUser(){System.out.println(user);}
}在配置类的方法中通过方法参数让Spring容器把对象注入。 //自定义bean名称
Bean(user1)
public User getUser(){User user new User();user.setId(2L);user.setName(李四);return user;
}
Bean
//可以直接从方法参数中取到。
public People getPeople(User user1){People p new People();p.setUser(user1);return p;
}十七、拦截器 新建拦截器类。(注意不要忘记类上注解Component) Component
public class DemoInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println(执行拦截器);return true;}
}配置拦截器(注意类上有注解Configuration。此类相当于SpringMVC配置文件。 addPathPattern(): 拦截哪些URL。 /** 拦截全部excludePathPatterns(): 不拦截哪些URL。当和addPathPattern()冲突时,excludePathPatterns()生效。) Configuration
public class MyConfig implements WebMvcConfigurer {Autowiredprivate DemoInterceptor demoInterceptor;//配置拦截器的映射Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(demoInterceptor).addPathPatterns(/**).excludePathPatterns(/login);}
}十八、 其他拓展
18.1 注解拓展
springboot默认已经帮助我们整合好了SpringMVC,同时也给我们默认配置了DispathcerServlet 和编码过滤器,同时也给我们配置好了WEB项目开发的常见组件 查看容器中的所有组件 SpringBootApplication 注解 /*
* 默认扫描启动类所在包下的所有层级的子包
* 可以通过scanBasePackages属性指定扫描路径
* SpringBootApplication是一个合成注解,可以拆分为以下三个注解
* SpringBootConfiguration
* EnableAutoConfiguration
* ComponentScan(basePackages com.msb)
* */
SpringBootApplication
public class Springboot04Application {public static void main(String[] args) {//返回一个spring容器ConfigurableApplicationContext context SpringApplication.run(Springboot04Application.class, args);// 查看所有组件的名String[] names context.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}Configuration 注解 package com.msb.config;
import com.msb.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*** Author: bingwoo* MyConfig配置类本身也是一个spring容器中的bean* proxyBeanMethodstrue 属性,给MyConfig对象产生一个代理对象* 通过代理对象控制反复调用MyConfig里面的方法返回的是容器中的一个单实例* 如果proxyBeanMethodsfalse 那么我们拿到的MyConfig对象就不是一个代理对象* 那么这个时候反复调用MyConfig中的方法返回的就是多实例** proxyBeanMethodsfalse 称之为Lite模式 特点启动快* proxyBeanMethodstrue 称之为Full模式 特点依赖spring容器控制bean单例**/
Configuration(proxyBeanMethods true)
public class MyConfig {/*bean id user1 class com.msb.pojo.User... .../bean*/Bean // 向容器中添加一个Bean,以方法名作为Bean的id,返回值类型作为组件的类型public User user1(){return new User(zhangsan, 10);}/*bean id user2 class com.msb.pojo.User... .../bean*/Bean(user2) // 向容器中添加一个Bean,手动指定Bean的name属性,返回值类型作为组件的类型public User getUser(){return new User(lisi, 11);}
}Import 注解 package com.msb.config;
import com.msb.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/*
* Import({User.class}) 在容器中自动创建Bean的注解
* 通过传入字节码,默认调用bean的无参构造器,向容器中存放一个Bean
* 默认组件的名字就是类的全路径名
* Import只要放到可以被扫描到的类之上就可以,不必非得是配置类或者Controller
* */
Import({User.class})
Configuration(proxyBeanMethods true)
public class MyConfig {
}SpringBootApplication(scanBasePackages com.msb)
public class Springboot04Application {public static void main(String[] args) {//启动SpringBoot, 返回一个spring容器ConfigurableApplicationContext context SpringApplication.run(Springboot04Application.class, args);// 根据类型获取BeanUser bean context.getBean(User.class);System.out.println(bean);// 获取属性User类的所有bean的nameString[] beanNamesForType context.getBeanNamesForType(User.class);for (String s : beanNamesForType) {System.out.println(s);}}
}Conditional 条件装配 注解
18.2 静态资源类 默认无前缀,如果想指定静态资源前缀,可以 通过spring.mvc.static-path-pattern配置 springboot还支持静态资源webjars 的处理方式,就是将静态资源打成jar导入 dependencygroupIdorg.webjars/groupIdartifactIdjquery/artifactIdversion3.6.0/version
/dependency拦截器静态资源放行 package com.msb.config;
import com.msb.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/*** Author: bingwoo*/
Configuration
public class MyInterceptorRegist implements WebMvcConfigurer {Autowiredprivate LoginInterceptor loginInterceptor;//配置拦截器的映射Overridepublic void addInterceptors(InterceptorRegistry registry) {//放行地址registry.addInterceptor(loginInterceptor).addPathPatterns(/**).excludePathPatterns(/login,/login.html,/css/**,/js/**,/img/**,/font/**);}
}18.3 文件上传 导入依赖 dependencygroupIdcom.sun.jersey/groupIdartifactIdjersey-client/artifactIdversion1.19/version
/dependency页面代码 html
headmeta charsetUTF-8titleTitle/titlestyle.progress {width: 200px;height: 10px;border: 1px solid #ccc;border-radius: 10px;margin: 10px 0px;overflow: hidden;}/* 初始状态设置进度条宽度为0px */.progress div {width: 0px;height: 100%;background-color: yellowgreen;transition: all .3s ease;}/stylescript typetext/javascript srcjs/jquery.min.js/scriptscript typetext/javascript$(function(){$(#uploadFile).click(function(){// 获取要上传的文件var photoFile $(#photo)[0].files[0]if(photoFileundefined){alert(您还未选中文件)return;}// 将文件装入FormData对象var formData new FormData();formData.append(headPhoto,photoFile)// ajax向后台发送文件$.ajax({type:post,data:formData,url:file/upload,processData:false,contentType:false,success:function(result){// 接收后台响应的信息alert(result.message)// 图片回显$(#headImg).attr(src,result.newFileName);},xhr: function() {var xhr new XMLHttpRequest();//使用XMLHttpRequest.upload监听上传过程注册progress事件打印回调函数中的event事件xhr.upload.addEventListener(progress, function (e) {//loaded代表上传了多少//total代表总数为多少var progressRate (e.loaded / e.total) * 100 %;//通过设置进度条的宽度达到效果$(.progress div).css(width, progressRate);})return xhr;}})})})/script
/head
body
form actionaddPlayer methodgetp账号input typetext namename/pp密码input typetext namepassword/pp昵称input typetext namenickname/pp头像:br/input idphoto typefilebr/img idheadImg stylewidth: 200px;height: 200px alt你还未上传图片br/div classprogressdiv/div/diva iduploadFile hrefjavascript:void(0)立即上传/a/ppinput typesubmit value注册/p
/form
/body
/htmlController代码 package com.msb.controller;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/*** Author: bingwoo*/
Controller
RequestMapping(/file)
public class FileController {// 文件存储位置private final static String FILESERVERhttp://127.0.0.1:8090/upload/;RequestMapping(/upload)ResponseBodypublic MapString,String upload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {MapString,String mapnew HashMap();// 指定文件存储目录为我们项目部署环境下的upload目录String realPath req.getServletContext().getRealPath(/upload);File dir new File(realPath);// 如果不存在则创建目录if(!dir.exists()){dir.mkdirs();}// 获取文件名String originalFilename headPhoto.getOriginalFilename();// 避免文件名冲突,使用UUID替换文件名String uuid UUID.randomUUID().toString();// 获取拓展名String extendsName originalFilename.substring(originalFilename.lastIndexOf(.));// 新的文件名String newFileNameuuid.concat(extendsName);// 创建 sun公司提供的jersey包中的client对象Client clientClient.create();WebResource resource client.resource(FILESERVER newFileName);// 文件保存到另一个服务器上去了resource.put(String.class, headPhoto.getBytes());// 上传成功之后,把文件的名字和文件的类型返回给浏览器map.put(message, 上传成功);map.put(newFileName, FILESERVERnewFileName);map.put(filetype, headPhoto.getContentType());return map;}
} yml中配置文件大小限制 spring:servlet:multipart:max-file-size: 10MBmax-request-size: 100MB多文件同步上传处理方式 form actionfile/upload methodpost enctypemultipart/form-datap账号input typetext namename/pp密码input typetext namepassword/pp昵称input typetext namenickname/pp头像:br/input idphoto namephoto typefileinput idphotos namephotos typefile multiplebr/img idheadImg stylewidth: 200px;height: 200px alt你还未上传图片br/div classprogressdiv/div/diva iduploadFile hrefjavascript:void(0)立即上传/a/ppinput typesubmit value注册/p
/form后台接收的处理单元参数处理 public MapString,String upload(String name,String password,String nickname,RequestPart(photo) MultipartFile photo,RequestPart(photos) MultipartFile[] photos, HttpServletRequest req)18.4 MyBatis-plus
MyBatis-plus是mybatis的增强工具,在MyBatis 上只做增强,不做改变,为简化开发,提高效率而生。 安装 导入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope
/dependency
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.21/version
/dependency
dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.1.10/version
/dependency
dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.2/version
/dependency自动配置的内容MyBatis PlusAutoConfiguration配置类,MyBatisPlusProperties配置项前缀 mybatis-plus: 就是对mybatis-plus的参数的设置SQLSessionFactory已经配置好 mapperlocations 自动配置好的,默认值是classpath:/mapper//*.xml 意为任意包路径下所有的mapper包下的xml文件 Mapper建议替换成MapperScan:配置mybatisplus spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/mydb?useSSLfalseuseUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueusername: rootpassword: rootdruid:initial-size: 5min-idle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1testWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20filters: stat,wall,slf4jconnectionProperties: druid.stat.mergeSql\true;druid.stat.slowSqlMillis\5000web-stat-filter:enabled: trueurl-pattern: /*exclusions: *.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*stat-view-servlet:url-pattern: /druid/*allow: 127.0.0.1,192.168.8.109deny: 192.168.1.188reset-enable: falselogin-username: adminlogin-password: 123456
mybatis-plus:type-aliases-package: com.msb.pojo分页插件的使用 package com.msb.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*** Author: bingwoo*/
Configuration
public class MyBatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor();// 设置请求的页面大于最大页后操作 true调回到首页false 继续请求 默认false//paginationInnerInterceptor.setOverflow(false);// 设置最大单页限制数量默认 500 条-1 不受限制//paginationInnerInterceptor.setMaxLimit(500L);// 设置数据库类型paginationInnerInterceptor.setDbType(DbType.MYSQL);mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);return mybatisPlusInterceptor;}
}测试代码 Test
public void testPage(){// 当前页 页大小QueryWrapperDept queryWrappernew QueryWrapper();//queryWrapper.likeRight(dname, A);PageDept page deptService.page(new Page(1, 2), queryWrapper);// 当前页数据 总页数 总记录数 当前页 页大小 ... ..ListDept list page.getRecords();list.forEach(System.out::println);System.out.println(总页数:page.getPages());System.out.println(总记录数:page.getTotal());System.out.println(当前页:page.getCurrent());System.out.println(页大小:page.getSize());
}18.5 JUnit5单元测试
springboot 2.2.0开始引入Junit5作为单元测试的默认库。JUnit5和之前的版本有很大的不同,由单个子项目的几个不同模块组成。JUnit Platform ,是在JVM上启动测试框架的技术,不仅支持Junit自己的测试引擎,其他的测试引擎也可以JUnit Jupiter,提供了Junit5的最新的编程模型,是Junit5 的核心,内部包含了一个测试引擎,用于在Junit Platform上运行JUnit Vintager: 提供了兼容Junit4/3 的测试引擎Junit5 JUnit Platform JUnit JupiterJUnit Vintager
Junit支持Spring中的注解,测试起来比较方便, Autowired Transactional 等 测试代码 package com.msb;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.msb.mapper.DeptMapper;
import com.msb.pojo.Dept;
import com.msb.service.DeptService;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.commons.annotation.Testable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import java.util.concurrent.TimeUnit;
SpringBootTest // 使用springboot的容器功能
/*BootstrapWith(SpringBootTestContextBootstrapper.class)
ExtendWith({SpringExtension.class})*/
DisplayName(Junit5测试类)// 测试类描述
class SpringbootMybatisplusApplicationTests2 {Autowiredprivate DeptMapper deptMapper;BeforeEachpublic void testForeach(){System.out.println(beforeach);}AfterEachpublic void testAftereach(){System.out.println(aferEach);}BeforeAllpublic static void beforeAll(){System.out.println(beforall);}AfterAllpublic static void aferAll(){System.out.println(afterAll);}RepeatedTest(3)// 重复测试3次Timeout(value 10000,unit TimeUnit.MILLISECONDS)// 超时时间设置DisplayName(Junit测试方法1)Testpublic void test1(){System.out.println(a);System.out.println(deptMapper);}Disabled// 设置不可用DisplayName(Junit测试方法2) // 方法描述Testpublic void test2(){System.out.println(b);}
}断言机制断定某件事情,一定会发生,如果没有发生,那就是出现了问题,所欲的测试运行结束后,会有一个详细的断言报告用来对测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions中的静态方法,简单断言
测试代码package com.msb;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
SpringBootTest
DisplayName(Junit5断言测试类)
class SpringbootMybatisplusApplicationTests3 {DisplayName(简单断言1)Testpublic void testAssertions1(){int add add(1, 2);Assertions.assertEquals(6,add,add结果计算错误);}public int add(int a,int b){return ab;}DisplayName(简单断言2)Testpublic void testAssertions2(){String s new String(xxx);String s2new String(abc);Assertions.assertEquals(s,s2,String对象不一样);}// 组合断言DisplayName(组合断言)Testpublic void testAssertAll(){Assertions.assertAll(AssertAll,()- Assertions.assertTrue(true false),()- Assertions.assertEquals(1,2));}// 异常断言 认为应该会出现异常DisplayName(异常断言)Testpublic void testAssertException(){Assertions.assertThrows(ArithmeticException.class, ()-{ int i1/0;}, 没有抛出异常);}// 超时断言 判断有没有超时DisplayName(超时断言)Testpublic void testAssertTimeOut(){Assertions.assertTimeout(Duration.ofMillis(1000),()- Thread.sleep(5000));}// 快速失败DisplayName(快速失败)Testpublic void testFail(){if(true){Assertions.fail(测试 失败);}}
}前置条件(assumptions假设)类似于断言,不同在于,不满足断言回事方法测试失败,而不满足的前置条件会使得的是方法的执行中止,前置条件可以看成是测试方法执行的前提,当条件不满足时,就没有继续执行的必要 package com.msb;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Duration;
SpringBootTest
DisplayName(Junit5测试前置条件)
class SpringbootMybatisplusApplicationTests4 {DisplayName(测试前提条件)Testpublic void testAssumptions(){// 假设为true,才会执行Assumptions.assumeTrue(false,结果不是true);System.out.println(后面的测试代码前提条件);}DisplayName(简单断言1)Testpublic void testAssertions1(){int add 10;Assertions.assertEquals(6,add,add结果计算错误);System.out.println(后面的测试代码简单断言);}
}嵌套测试 package com.msb;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.EmptyStackException;
import java.util.Stack;
DisplayName(嵌套测试)
class SpringbootMybatisplusApplicationTests5 {StackObject stack;TestDisplayName(is instantiated with new Stack())void isInstantiatedWithNew() {new Stack();// 外层的测试不能驱动内层的测试方法assertNull(stack);}NestedDisplayName(when new)class WhenNew {BeforeEachvoid createNewStack() {stack new Stack();}TestDisplayName(is empty)void isEmpty() {assertTrue(stack.isEmpty());}TestDisplayName(throws EmptyStackException when popped)void throwsExceptionWhenPopped() {assertThrows(EmptyStackException.class, stack::pop);}TestDisplayName(throws EmptyStackException when peeked)void throwsExceptionWhenPeeked() {assertThrows(EmptyStackException.class, stack::peek);}NestedDisplayName(after pushing an element)class AfterPushing {String anElement an element;BeforeEach // 内层Test可以驱动外层的BeforeEachvoid pushAnElement() {stack.push(anElement);}TestDisplayName(it is no longer empty)void isNotEmpty() {assertFalse(stack.isEmpty());}TestDisplayName(returns the element when popped and is empty)void returnElementWhenPopped() {assertEquals(anElement, stack.pop());assertTrue(stack.isEmpty());}TestDisplayName(returns the element when peeked but remains not empty)void returnElementWhenPeeked() {assertEquals(anElement, stack.peek());assertFalse(stack.isEmpty());}}}
}参数化测试 package com.msb;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.EmptyStackException;
import java.util.Stack;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
DisplayName(参数化测试)
class SpringbootMybatisplusApplicationTests6 {ParameterizedTestValueSource(ints { 1, 2, 3 })void testWithValueSource(int argument) {System.out.println(argument);assertTrue(argument 0 argument 4);}ParameterizedTestMethodSource(stringProvider)void testWithExplicitLocalMethodSource(String argument) {assertNotNull(argument);}static StreamString stringProvider() {return Stream.of(apple, banana);}
}总结
以上是总结的Spring boot 原理、搭建过程、整合Mybatis、整合logbacks、整合PageHelper、拓展了打包、异常处理、拦截器等共自己及大家学习。