导入依赖1
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--继承父项目方式-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<groupId>com.msb</groupId>
<artifactId>springboot01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.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")
@ResponseBody
public String firstController(){
return "hello springboot";
}
}
启动类表示项目的启动入口
启动器表示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);
}
}
在公司中可能会出现必须继承某个项目,如果Spring Boot用了继承就不能继承别的项目了。所以Spring Boot还提供了依赖的方式。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.msb</groupId>
<artifactId>springboot01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
</dependencies>
</project>
简单了解启动原理可以看这边文章:文字地址,想详细了解:建议耐心看完本
SpringBoot的入口是从SpringApplication.run()传入我们的主启动类开始
@SpringBootApplication
public class LeeSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(LeeSpringbootApplication.class, args);
}
}
run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
设置应用类型,后面会根据类型初始化对应的环境,常用的一般都是servlet环境
加载系统中引导器Bootstrapper(从META-INF/spring.factories中加载)
初始化classpath下 META-INF/spring.factories 中已配置的ApplicationContextInitalizer
初始化classpath下所以已配置的 ApplicationListener
根据调用栈,设置 main 方法的类名
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//设置资源加载器为null
this.resourceLoader = resourceLoader;
//断言加载资源不能为null
Assert.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 加载系统中引导器Bootstrapper
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 1.3 初始化classpath下 META-INF/spring.factories 中已配置的ApplicationContextInitalizer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 1.4 初始化classpath下所以已配置的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 1.5 根据调用栈,设置 main 方法的类名
this.mainApplicationClass = deduceMainApplicationClass();
}
在执行 getSpringFactoriesInstances(BootstrapRegistryInitializer. class ) 中会调用 loadSpringFactories() 方法遍历所有jar包中classpath下 META-INF/spring.factories文件,并保存在缓存中
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> 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 elements
result.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;
}
获取并启动监听器
构造上下文环境
初始化应用上下文
刷新应用上下文前的准备阶段
刷新上下文
刷新应用上下文后的扩展接口
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);
// 处理需要忽略的Bean
configureIgnoreBeanInfo(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;
}
加载META-INF/spring.factories 中的 SpringApplicationRunListener,SpringApplicationRunListeners负责在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> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
根据之前标记的应用类型(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;
}
根据配置的应用类型( SERVLET )创建对应的context ( AnnotationConfigServletWebServerApplicationContext ) 并在父类 GenericApplicationContext 的构造方法中创建了DefaultListableBeanFactory(ioc容器)
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);
}
};
主要完成应用上下文属性设置,并且将启动类生成实例对象保存到容器中。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境
context.setEnvironment(environment);
// 执行容器后置处理(主要设置转换器)
postProcessApplicationContext(context);
// 应用初始化器,执行容器中的 ApplicationContextInitializer 包括spring.factories
applyInitializers(context);
// 向各个容器中发送容器已经准备好的事件
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory 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
// 获取主启动类
Set<Object> 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 Set<Object> getAllSources() {
Set<Object> 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));
}
// 创建 BeanDefinitionLoader
BeanDefinitionLoader 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定义读取器, eg:@Configuration @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 here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
}
if (isEligible(source)) {
// 将启动类的 BeanDefinition 注册到 BeanDefinitionMap 中
this.annotatedReader.register(source);
}
}
主要逻辑为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 Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
invokeBeanFactoryPostProcessors(beanFactory) 改方法会解析核心启动类中 @SpringBootApplication实现自动配置
Ioc容器的初始化包括三个步骤,该三个步骤在 invokeBeanFactoryPostProcessors 中完成
Resource定位
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, List<BeanFactoryPostProcessor> 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?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> 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.
List<BeanDefinitionRegistryPostProcessor> 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.
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> 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.
List<BeanFactoryPostProcessor> 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.
List<BeanFactoryPostProcessor> 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();
}
}
实现自动装配:
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) {
List<BeanDefinitionHolder> 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 found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.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 context
SingletonBeanRegistry 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 class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
//获取所有bean的全路径(解析各类注解)
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (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();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> 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 classes
if (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 it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
public void parse(Set<BeanDefinitionHolder> 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, Predicate<String> 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.
// Let's 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, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
for (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等)
Set<AnnotationAttributes> 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 immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (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 annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
AnnotationAttributes 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 methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (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 recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
通过getImport(sourceClass) 解析启动类上的注解,获取到其中被@Import注解的类,即AutoConfigurationPackages、AutoConfigurationImportSelector
启动类上@SpringBootApplication注解为组合注解
在解析@Import注解时,会有一个getImports()方法,从启动类开始递归解析注解,把所有包含@Import的注解都解析到,然后再processImport()方法中对@Import注解的类进行分类,此处主要识别的是AutoConfigurationImportSelector 归属于ImportSelector的子类,在后续的过程中会调用 DeferredImprotSelectorHandler中的process()方法,来完成EnableAutoConfiguration的加载。
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> 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() {
List<DeferredImportSelectorHolder> 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()) {
Predicate<String> 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 Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// 遍历DeferredImportSelectorHolder对象集合deferredImports,deferrdImports集合装了各种ImportSelector(AutoConfigurationImportSelect)
this.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()) {
// 将符合条件的自动配置类作为 key,annotationMetadata作为值放进 entries 集合中
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
// AutoConfigurationImportSelector
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
.getAutoConfigurationEntry(annotationMetadata);
// 得到所有要排除的自动配置类集合
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
.getAutoConfigurationEntry(annotationMetadata);
// 得到经过过滤后所有符合条件的自动配置类集合
Set<String> 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,
Collection<SourceClass> importCandidates, Predicate<String> 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 imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> 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());
Collection<SourceClass> 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 definitions
Class<?> 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 class
this.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对象。
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
普通数据类型
server:
port: 8888
配置对象类型数据
person:
name: zs
age: 12
sex: 男
#或者写成json格式
person2: {name: zss,age: 18 }
配置数组类型
city:
- beijing
- tianjin
- shanghai
- chongqing
#或者
city2: [beijing,tianjin,shanghai,chongqing]
1 . 当前项目根目录中
2. 当前项目根目录下的一个/config子目录中
3. 项目的resources即classpath根路径中
4. 项目的resources即classpath根路径下的/config目录中
当前项目根目录下的一个/config子目录中(最高)
当前项目根目录中(其次)
项目的resources即classpath根路径下的/config目录中(一般)
项目的resources即classpath根路径中(最后)
Spring Boot 中有两种上下文对象,一种是 bootstrap, 另外一种是application(ServletContext), bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
bootstrap配置文件特征
bootstrap与 application 的应用场景
-- 项目名
--src
--main
--java
java代码
--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
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
mapper-locations: classpath:mybatis/*.xml
type-aliases-package: com.msb.pojo
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
如果不在MyApplication启动类上添加@MapperScan必须在UserMapper接口上添加@Mapper注解。
//@Mapper
public interface UserMapper {
List<User> selectAll();
}
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 {
@Autowired
private UserService userService;
@RequestMapping("/findAll")
@ResponseBody
public List<User> 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 {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.findAll();
}
}
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 version="1.0" encoding="UTF-8" ?>
<configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="${catalina.base}/logs/" />
<!-- 控制台输出 -->
<appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出格式 -->
<layout class="ch.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 name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<layout class="ch.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 class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="Stdout" />
<appender-ref ref="RollingFile" />
</root>
<logger name="com.msb.mapper" level="DEBUG"></logger>
<!--日志异步到数据库 -->
<!--<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
日志异步到数据库
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
连接池
<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
<user>root</user>
<password>root</password>
</dataSource>
</connectionSource>
</appender> -->
</configuration>
添加PageHelper启动器依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.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 {
@Autowired
private EmpService empService;
@RequestMapping("/findByPage/{pageNum}/{pageSize}")
@ResponseBody
public List<Emp> 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 {
@Autowired
private EmpMapper empMapper;
@Override
public List<Emp> findByPage(Integer pageNum, Integer pageSize) {
Page<Emp> page = PageHelper.startPage(pageNum, pageSize);
List<Emp> list =empMapper.findAll();
// 方式1
System.out.println("当前页:"+page.getPageNum());
System.out.println("总页数"+page.getPages());
System.out.println("页大小:"+page.getPageSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("当前页数据"+page.getResult());
// 方式2
PageInfo<Emp> 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;
}
}
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的limit=0),就不执行分页,返回全部结果
PageInfo对象解析参数
private int pageNum; //当前页
private int pageSize; //每页显示数据条数
private int size; //当前页的数量
private int startRow; //始页首行行号
private int endRow; //尾页尾行行号
private long total; //总记录数
private int pages; //总页数
private List<T> 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是由阿里巴巴推出的数据库连接池。它结合了C3P0、DBCP、PROXOOL等数据库连接池的优点。之所以从众多数据库连接池中脱颖而出,还有一个重要的原因就是它包含控制台,很方便的帮助我们实现对于sql执行的监控。
添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
修改配置文件application.yml
spring:
datasource:
# 使用阿里的Druid连接池
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名
url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-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依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
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;
}
}
导入依赖
<!--freeMaker依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
创建controller
@RequestMapping("/freemarker")
@Controller
public class FreemarkerController {
@RequestMapping("/show")
public String freemarker(Map<String, Object> map){
map.put("name","msb");
//返回模板文件名称
return "show";
}
}
templates目录下创建模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is showftlh
<br/>
${name}
<img src="img/a.jpg"></img>
</body>
</html>
@Controller
public class FreemarkerController {
@Autowired
private EmpService empService;
@RequestMapping("/showEmp")
public ModelAndView testList(){
ModelAndView mv =new ModelAndView();
List<Emp> list =empService.findAll();
mv.addObject("empList", list);
mv.setViewName("showEmp");
return mv;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/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 id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>部门号</th>
</tr>
<#list empList as emp>
<tr>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.sal}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</table>
</body>
</html>
遍历Map数据
@Controller
public class FreemarkerController {
@Autowired
private EmpService empService;
@RequestMapping("/showEmpMap")
public ModelAndView testMap(){
ModelAndView mv =new ModelAndView();
List<Emp> list =empService.findAll();
Map<String,Emp> empMap =new HashMap<>();
for (Emp emp : list) {
empMap.put(emp.getEmpno().toString(), emp);
}
mv.addObject("empMap", empMap);
mv.setViewName("showEmpMap");
return mv;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/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 id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>部门号</th>
</tr>
<#list empMap?keys as k>
<tr>
<td>${k_index}</td>
<td>${k}</td>
<td>${empMap[k].ename}</td>
<td>${empMap[k].job}</td>
<td>${empMap[k].sal}</td>
<td>${empMap[k].deptno}</td>
</tr>
</#list>
</table>
</body>
</html>
if指令:
if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否 则跳过内容不再输出。 if中支持的运算符
算数运算符 FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , %
逻辑运算符有如下几个: 逻辑与:&& 逻辑或:|| 逻辑非:! 逻辑运算符只能作用于布尔值,否则将产生错误
c比较运算符有如下几个:
① =或者==:判断两个值是否相等.
② !=:判断两个值是否不等.
③ > 或者gt:判断左边值是否大于右边值
④ >=或者gte:判断左边值是否大于等于右边值
⑤ <或者lt:判断左边值是否小于右边值
⑥ <=或者lte:判断左边值是否小于等于右边值
注意: =和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,“x”,"x ","X"是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>
如何判断空值
<#if empList??>
<#list empList as emp>
<tr <#if emp_index%2 ==0 > style="background-color: gray" </#if>>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td <#if emp.ename == 'KING'> style="color: aqua" </#if>>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.mgr!'无'}</td>
<td <#if emp.sal gte 2000.0> style="color: red" </#if>>${emp.sal}</td>
<td>${emp.comm!'0'}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</#if>
内置函数
<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank} 账号:${data.account}
员工人数:${empList?size}
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>上级</th>
<th>入职日期a</th>
<th>入职日期b</th>
<th>入职日期c</th>
<th>入职日期d</th>
<th>薪资</th>
<th>补助</th>
<th>部门号</th>
</tr>
<#if empList??>
<#list empList as emp>
<tr <#if emp_index%2 ==0 > style="background-color: gray" </#if>>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td <#if emp.ename == 'KING'> style="color: aqua" </#if>>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.mgr!'无'}</td>
<td>${emp.hiredate?date}</td>
<td>${emp.hiredate?time}</td>
<td>${emp.hiredate?datetime}</td>
<td>${emp.hiredate?string("yyyy年MM月dd日")}</td>
<td <#if emp.sal gte 2000.0> style="color: red" </#if>>${emp.sal}</td>
<td>${emp.comm!'0'}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</#if>
</table>
Thymeleaf的主要目标是将优雅的自然模板带到开发工作流程中,并将HTML在浏览器中正确显示,并且可以作为静态原型,让开发团队能更容易地协作。Thymeleaf能够处理HTML,XML,JavaScript,CSS甚至纯文本。
长期以来,jsp在视图领域有非常重要的地位,随着时间的变迁,出现了一位新的挑战者:Thymeleaf,Thymeleaf是原生的,不依赖于标签库.它能够在接受原始HTML的地方进行编辑和渲染.因为它没有与Servelet规范耦合,因此Thymeleaf模板能进入jsp所无法涉足的领域。
Thymeleaf在Spring Boot项目中放入到resources/templates中。这个文件夹中的内容是无法通过浏览器URL直接访问的(和WEB-INF效果一样),所有Thymeleaf页面必须先走控制器。
创建项目,准备配置文件及各层级代码,项目中添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.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";
}
}
th:text属性
<!--向span双标签内部添加文本-->
<span th:text="pageMessage"></span> <br/>
<!--从域中根据参数名取出参数值放在双标签中-->
<span th:text="${msg}"></span> <br/>
th:value 获取值
<!--向input标签中的value属性赋值-->
<input type="text" th:value="pageMessage"/>
<!--从域中根据参数名取出参数值 向input标签中的value属性赋值-->
<input type="text" th:value="${msg}"/>
th:if 判断
<span th:if="${name}!='张三'">会显示</span>
循环遍历.th:each
- th:each="u,i :${list}" 其中i表示迭代状态。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/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}"></span><br/>
姓名:<span th:text="${emp.ename}"></span><br/>
职务:<span th:text="${emp.job}"></span><br/>
上级:<span th:text="${emp.mgr}"></span><br/>
入职日期:<span th:text="${emp.hiredate}"></span><br/>
工资:<span th:text="${emp.sal}"></span><br/>
补助:<span th:text="${emp.comm}"></span><br/>
部门号:<span th:text="${emp.deptno}"></span><br/>
</span>
<hr/>
<span th:if="${empList}!=null">
<span th:if="${empList.size()} ne 0">
工号:<span th:text="${empList[0].empno}"></span><br/>
姓名:<span th:text="${empList[0].ename}"></span><br/>
职务:<span th:text="${empList[0].job}"></span><br/>
上级:<span th:text="${empList[0].mgr}"></span><br/>
入职日期:<span th:text="${empList[0].hiredate}"></span><br/>
工资:<span th:text="${empList[0].sal}"></span><br/>
补助:<span th:text="${empList[0].comm}"></span><br/>
部门号:<span th:text="${empList[0].deptno}"></span><br/>
</span>
</span>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>序号</th>
<th>总人数</th>
<th>偶数索引?</th>
<th>奇数索引?</th>
<th>第一?</th>
<th>最后?</th>
<th>工号</th>
<th>姓名</th>
<th>职务</th>
<th>上级</th>
<th>入职日期</th>
<th>工资</th>
<th>补助</th>
<th>部门号</th>
</tr>
<tr th:each="emp,i:${empList}">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${emp.mgr}"></td>
<td th:text="${emp.hiredate}"></td>
<td th:text="${emp.sal}"></td>
<td th:text="${emp.comm}"></td>
<td th:text="${emp.deptno}"></td>
</tr>
</table>
</body>
</html>
算数运算符:+ , - , * , / , %
<span th:text="1+1"></span>
<span th:text="'1'+1"></span>
<span th:text="${emp.empno}+1"></span>
<span th:text="${emp.empno+1}"></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:text="1>0 and 2<3"></div>
<div th:text="1>0 and 2>3"></div>
<div th:text="1>0 or 2<3"></div>
<div th:text="1>0 or 2>3"></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:each="emp,i:${empList}" th:class="${i.odd}?a:b">
对空值作出处理
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${emp.mgr} eq null ?老板:${emp.mgr}"></td>
<td th:text="${emp.hiredate}"></td>
<td th:text="${emp.sal}"></td>
<td th:text="${emp.comm} eq null ?0:${emp.comm}"></td>
<td th:text="${emp.deptno}"></td>
</tr>
th:href
<a th:href="@{/getParam(id=1,name='msb')}" >跳转</a>
<!-- 获取作用域值-->
<a th:href="@{/getParam(name=${stu.name},age=${stu.age})}">跳转二</a>
th:onclick :给元素绑定事件,单击事件并传递参数
//写法1:仅仅支持数字和布尔类型参数的传递,字符串不支持
<a href="javascript:viod(0)" th:onclick="'del('+${emp.empno}+')'">删除</a>
//写法2:支持数字和文本类型的参数传递
<a href="javascript:void(0)" th:onclick="delEmp([[${emp.empno}]],[[${emp.ename}]])">删除</a>
引用内置对象需要使用#
大部分内置对象的名称都以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;
#sets:Set操作工具,参照java.util.Set;#maps:Map操作工具,参照java.util.Map;
#messages:操作消息的工具。
#numbers
#numbers.formatDecimal(numbwe,整数位,整数位千分位标识符,小数位,小数位表示符)
${#numbers.formatDecimal(num,1,'COMMA',2,'POINT')}
显示:99,999,999.99
1:表示整数位至少一位,不足以0补齐,如:num = 0.00,
${#numbers.formatDecimal(num,0,'COMMA',2,'POINT')}则显示 .00
${#numbers.formatDecimal(num,1,'COMMA',2,'POINT')}则显示 0.00
COMMA:','
POINT:‘.’
代码示例
@RequestMapping("showIndex")
public String showIndex(Map<String,Object> map, HttpServletRequest req, HttpSession session){
// 向request域放数据
req.setAttribute("msg", "requestMessage");
// 向session域放数据
session.setAttribute("msg", "sessionMessage");
// 向application域放数据
req.getServletContext().setAttribute("msg", "applicationMessage");
// 对象List集合数据
List<Emp> empList = empService.findAll();
map.put("empList", empList);
return "index";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/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 id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>序号</th>
<th>总人数</th>
<th>偶数索引?</th>
<th>奇数索引?</th>
<th>第一?</th>
<th>最后?</th>
<th>工号</th>
<th>姓名</th>
<th>职务</th>
<th>上级</th>
<th>入职日期</th>
<th>入职年</th>
<th>入职月</th>
<th>入职日</th>
<th>工资</th>
<th>补助</th>
<th>部门号</th>
<th>操作</th>
</tr>
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${#strings.isEmpty(emp.mgr)}?老板:${emp.mgr}"></td>
<td th:text="${#dates.format(emp.hiredate,'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${#dates.year(emp.hiredate)}"></td>
<td th:text="${#dates.month(emp.hiredate)}"></td>
<td th:text="${#dates.day(emp.hiredate)}"></td>
<td th:text="${#numbers.formatDecimal(emp.sal,7,'COMMA',2,'POINT')}"></td>
<td th:text="${#strings.isEmpty(emp.comm)}?0:${#numbers.formatDecimal(emp.sal,7,'COMMA',2,'POINT')}"></td>
<td th:text="${emp.deptno}"></td>
<td>
<a href="javascript:void(0)" th:onclick="removeEmp([[${emp.empno}]],[[${emp.ename}]])">删除</a>
</td>
</tr>
</table>
<script>
function removeEmp(empno,ename){
var resulet =confirm("确定要删除编号为"+empno+"的"+ename);
if(resulet){
window.location.href="removeEmp?empno="+empno+"&ename="+ename;
}
}
</script>
<hr/>
request:<br/>
<span th:text="${#httpServletRequest.getAttribute('msg')}"></span><br/>
<span th:text="${#request.getAttribute('msg')}"></span><br/>
<span th:text="${msg}"></span><br/>
session:<br/>
<span th:text="${#httpSession.getAttribute('msg')}"></span><br/>
<span th:text="${#session.getAttribute('msg')}"></span><br/>
<span th:text="${session.msg}"></span><br/>
application:<br/>
<span th:text="${#servletContext.getAttribute('msg')}"></span><br/>
<span th:text="${application.msg}"></span><br/>
</body>
</html>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
排除项目中自带的所有的Tomcat插件和jsp servlet 依赖,因为这里要将项目放到一个Tomcat上运行
<!--配置SpringBoot的web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除web启动中自动依赖的tomcat插件-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
手动依赖tomcat插件,但是表明项目打包时该依赖不会被打进去,目的主要是保证开发阶段本地SpringBoot
项目可以正常运行
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。
相当于compile,但是打包阶段做了exclude操作-->
<scope>provided</scope>
</dependency>
SpringBoot的启动类继承SpringBootServletInitializer,并重写configure
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
//重写配置方法
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
//启动SpringBoot
SpringApplication.run(MyApplication.class,args);
}
}
使用install命令打包项目,并将war包放到tomcat下的webapps下,启动tomcat即可。
如果我们使用的是tomcat7则需要将javax.el-api-3.0.0.jar包放到tomcat下 的lib目录中。
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作为错误显示页面。
使用@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;
}
}
使用:@ControllerAdvice+@ExceptionHandler(此处优先级低于局部异常处理器)
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 {
@Bean
public 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 {
@Override
public 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;
}
}
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 {
@Autowired
private EmpService empService;
@Test
public void testFindAll() {
List<Emp> list = empService.findAll();
list.forEach(System.out::println);
}
}
新建配置类
com.msb.config.MyConfig , 规范都是放入到config文件夹中。
注意:配置类要有@Configuration,方法要有@Bean
@Configuration
public class MyConfig {
//访问权限修饰符没有强制要求,一般是protected
//返回值就是注入到Spring容器中实例类型。
// 方法名没有强制要求,相当于中id属性。
@Bean
protected 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 {
@Autowired
@Qualifier("user2")
private User user;
@Test
public 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 {
@Override
public 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 {
@Autowired
private DemoInterceptor demoInterceptor;
//配置拦截器的映射
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
@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
* proxyBeanMethods=true 属性,给MyConfig对象产生一个代理对象
* 通过代理对象控制反复调用MyConfig里面的方法返回的是容器中的一个单实例
* 如果proxyBeanMethods=false 那么我们拿到的MyConfig对象就不是一个代理对象
* 那么这个时候反复调用MyConfig中的方法返回的就是多实例
*
* proxyBeanMethods=false 称之为Lite模式 特点启动快
* proxyBeanMethods=true 称之为Full模式 特点依赖spring容器控制bean单例
*
*/
@Configuration(proxyBeanMethods = true)
public class MyConfig {
/*... ... */
@Bean // 向容器中添加一个Bean,以方法名作为Bean的id,返回值类型作为组件的类型
public User user1(){
return new User("zhangsan", 10);
}
/*... ... */
@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);
// 根据类型获取Bean
User bean = context.getBean(User.class);
System.out.println(bean);
// 获取属性User类的所有bean的name
String[] beanNamesForType = context.getBeanNamesForType(User.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
}
}
@Conditional 条件装配 注解
springboot还支持静态资源webjars 的处理方式,就是将静态资源打成jar导入
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.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 {
@Autowired
private LoginInterceptor loginInterceptor;
//配置拦截器的映射
@Override
public void addInterceptors(InterceptorRegistry registry) {
//放行地址
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login","/login.html","/css/**","/js/**","/img/**","/font/**");
}
}
导入依赖
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19</version>
</dependency>
页面代码
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.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;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#uploadFile").click(function(){
// 获取要上传的文件
var photoFile =$("#photo")[0].files[0]
if(photoFile==undefined){
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 action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" type="file">
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>
Controller代码
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 FILESERVER="http://127.0.0.1:8090/upload/";
@RequestMapping("/upload")
@ResponseBody
public Map<String,String> upload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
Map<String,String> map=new 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 newFileName=uuid.concat(extendsName);
// 创建 sun公司提供的jersey包中的client对象
Client client=Client.create();
WebResource resource = client.resource(FILESERVER + newFileName);
// 文件保存到另一个服务器上去了
resource.put(String.class, headPhoto.getBytes());
// 上传成功之后,把文件的名字和文件的类型返回给浏览器
map.put("message", "上传成功");
map.put("newFileName", FILESERVER+newFileName);
map.put("filetype", headPhoto.getContentType());
return map;
}
}
yml中配置文件大小限制
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
多文件同步上传处理方式
<form action="file/upload" method="post" enctype="multipart/form-data">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" name="photo" type="file">
<input id="photos" name="photos" type="file" multiple>
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>
后台接收的处理单元参数处理
public Map<String,String> upload(String name,
String password,String nickname,
@RequestPart("photo") MultipartFile photo,
@RequestPart("photos") MultipartFile[] photos, HttpServletRequest req)
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.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.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
druid:
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall,slf4j
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
stat-view-servlet:
url-pattern: "/druid/*"
allow: 127.0.0.1,192.168.8.109
deny: 192.168.1.188
reset-enable: false
login-username: admin
login-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 {
@Bean
public 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(){
// 当前页 页大小
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
//queryWrapper.likeRight("dname", "A");
Page<Dept> page = deptService.page(new Page<>(1, 2), queryWrapper);
// 当前页数据 总页数 总记录数 当前页 页大小 ... ..
List<Dept> 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());
}
测试代码
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 {
@Autowired
private DeptMapper deptMapper;
@BeforeEach
public void testForeach(){
System.out.println("beforeach");
}
@AfterEach
public void testAftereach(){
System.out.println("aferEach");
}
@BeforeAll
public static void beforeAll(){
System.out.println("beforall");
}
@AfterAll
public static void aferAll(){
System.out.println("afterAll");
}
@RepeatedTest(3)// 重复测试3次
@Timeout(value = 10000,unit = TimeUnit.MILLISECONDS)// 超时时间设置
@DisplayName("Junit测试方法1")
@Test
public void test1(){
System.out.println("a");
System.out.println(deptMapper);
}
@Disabled// 设置不可用
@DisplayName("Junit测试方法2") // 方法描述
@Test
public void test2(){
System.out.println("b");
}
}
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")
@Test
public void testAssertions1(){
int add = add(1, 2);
Assertions.assertEquals(6,add,"add结果计算错误");
}
public int add(int a,int b){
return a+b;
}
@DisplayName("简单断言2")
@Test
public void testAssertions2(){
String s =new String("xxx");
String s2=new String("abc");
Assertions.assertEquals(s,s2,"String对象不一样");
}
// 组合断言
@DisplayName("组合断言")
@Test
public void testAssertAll(){
Assertions.assertAll("AssertAll",
()-> Assertions.assertTrue(true&& false),
()-> Assertions.assertEquals(1,2));
}
// 异常断言 认为应该会出现异常
@DisplayName("异常断言")
@Test
public void testAssertException(){
Assertions.assertThrows(ArithmeticException.class, ()->{ int i=1/0;}, "没有抛出异常");
}
// 超时断言 判断有没有超时
@DisplayName("超时断言")
@Test
public void testAssertTimeOut(){
Assertions.assertTimeout(Duration.ofMillis(1000),()-> Thread.sleep(5000));
}
// 快速失败
@DisplayName("快速失败")
@Test
public 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("测试前提条件")
@Test
public void testAssumptions(){
// 假设为true,才会执行
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("后面的测试代码前提条件");
}
@DisplayName("简单断言1")
@Test
public 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 {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
// 外层的测试不能驱动内层的测试方法
assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach // 内层Test可以驱动外层的BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("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 {
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
System.out.println(argument);
assertTrue(argument > 0 && argument < 4);
}
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana");
}
}
以上是总结的Spring boot 原理、搭建过程、整合Mybatis、整合logbacks、整合PageHelper、拓展了打包、异常处理、拦截器等,共自己及大家学习。