16.SpringBoot
文章目录
1.SpringBoot-简介
Spring的优点:
Spring是一个轻量级框架,Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象实现了EJB的功能。
Spring的缺点:
虽然Spring的组件代码是轻量级的,但是Spring的配置却是重量级的。
SpringBoot优点:
- 使用SpringBoot可以创建独立的Spring应用程序
- 在SpringBoot中直接嵌入了Tomcat、Jetty、Undertow等Web容器,在使用SpringBoot做Web开发时不需要部署war文件
- 通过提供自己的启动器(Starter)依赖,简化项目构建配置
- 尽量的自动配置Spring和第三方库
- 不需要繁杂的xml配置文件
SpringBoot简介:
SpringBoot是Spring公司的一个顶级项目,和Spring Framework一个级别。
SpringBoot实际上是利用Spring Framework 4 自动配置特性完成的,编写项目时不需要编写xml文件。SpringBoot目前已经有很好的生态,各种主流技术都已经提供了SpringBoot的启动器。
什么是SpringBoot的启动器:
SpringBoot的启动器实际上就是一个依赖,这个依赖中包含了整个这个技术的相关jar包,还包含了这个技术的自动配置,以前绝大多数XML配置都不再需要我们手动配置了(约定大于配置,解决一个技术的相关jar包版本冲突的问题)。我们在SpringBoot中按照约定即可简化大量的配置,剩下的少量配置在properties中或者yaml中完成即可。如果是Spring公司自己封装的启动器 其artifactid通常为:spring-boot-starter-xxx,如果是第三方公司提供的启动器,其artifactid通常为:xxx-spring-boot-starter。以后使用SpringBoot整合其他技术时优先考虑导入该技术的springboot启动器。
SpringBoot实际就是对于ssm整合的一个简化,最直观的感受就是只要按照springboot的约定去搭建项目目录,我们就把精力放在直接写业务代码上就行了,而不用再浪费大量的时间在繁杂的配置上。
2.SpringBoot-项目搭建
约定大于配置:
1.SpringBoot启动类必须和其他代码(controller、mapper、service)在同一层,这样其他代码的诸如@Controller等注解才能被扫描到
使用idea自带的SpringBoot项目初始化插件快速搭建SpringBoot项目:
选择依赖
选择以上依赖后的pom.xml:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父maven项目,其中定义了各个依赖的版本号等-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot01</name>
<description>springboot01</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!--web启动器-->
<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>
<!--junit启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
springboot项目启动日志:
可以看到tomcat started on port 8080 with context path ‘’,由于此时项目的上下文路径为空串,我们在前端请求时不带application context直接请求映射路径就可以,如下图:
在我们第一次请求时,发现dispatcherServlet对象也被创建了:
可以把springboot理解为简化xml版本的ssm整合
3.SpringBoot-启动原理分析
SpringBoot依赖导入:
基本依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
父项目spring-boot-starter-parent的父项目spring-boot-dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
</parent>
spring-boot-dependencies中定义了各个依赖的版本等,对各种可能将来会和springboot集成的jar包进行定义:
SpringBoot包扫描原理:
springboot项目启动类:
package com.example.springboot01;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Springboot01Application {
public static void main(String[] args) {
SpringApplication.run(Springboot01Application.class, args);
}
}
点击进入@SpringBootApplication注解:
@SpringBootConfiguration注解:springboot启动相关配置的注解
@EnableAutoConfiguration注解:启动自动配置相关的注解
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @SpringBootConfiguration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 1.2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* <p>
* <strong>Note:</strong> this setting is an alias for
* {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
* scanning or Spring Data {@link Repository} scanning. For those you should add
* {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
* {@code @Enable...Repositories} annotations.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* <p>
* <strong>Note:</strong> this setting is an alias for
* {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
* scanning or Spring Data {@link Repository} scanning. For those you should add
* {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
* {@code @Enable...Repositories} annotations.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
* <p>
* The default value of the {@link BeanNameGenerator} interface itself indicates that
* the scanner used to process this {@code @SpringBootApplication} annotation should
* use its inherited bean name generator, e.g. the default
* {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
* application context at bootstrap time.
* @return {@link BeanNameGenerator} to use
* @see SpringApplication#setBeanNameGenerator(BeanNameGenerator)
* @since 2.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even in
* case of direct {@code @Bean} method calls in user code. This feature requires
* method interception, implemented through a runtime-generated CGLIB subclass which
* comes with limitations such as the configuration class and its methods not being
* allowed to declare {@code final}.
* <p>
* The default is {@code true}, allowing for 'inter-bean references' within the
* configuration class as well as for external calls to this configuration's
* {@code @Bean} methods, e.g. from another configuration class. If this is not needed
* since each of this particular configuration's {@code @Bean} methods is
* self-contained and designed as a plain factory method for container use, switch
* this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>
* Turning off bean method interception effectively processes {@code @Bean} methods
* individually like when declared on non-{@code @Configuration} classes, a.k.a.
* "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
* equivalent to removing the {@code @Configuration} stereotype.
* @since 2.2
* @return whether to proxy {@code @Bean} methods
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@EnableAutoConfiguration注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@AutoConfigurationPackage注解:
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages
* base packages} or {@link #basePackageClasses base package classes} are specified, the
* package of the annotated class is registered.
*
* @author Phillip Webb
* @since 1.3.0
* @see AutoConfigurationPackages
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
/**
* Base packages that should be registered with {@link AutoConfigurationPackages}.
* <p>
* Use {@link #basePackageClasses} for a type-safe alternative to String-based package
* names.
* @return the back package names
* @since 2.3.0
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages to be
* registered with {@link AutoConfigurationPackages}.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the base package classes
* @since 2.3.0
*/
Class<?>[] basePackageClasses() default {};
}
@Import(AutoConfigurationPackages.Registrar.class)的Resigtrar内部类 完成包扫描操作:
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//扫描SpringBoot启动类所在的包下的所有包
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
SpringBoot自动配置原理:
springboot用配置类替换掉了诸如我们之前用xml完成的ssm整合等配置,springboot的配置类位置:
比如我们看一个dispatcherServlet的配置:
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
* {@link DispatcherServlet}. Should work for a standalone application where an embedded
* web server is already present and also for a deployable application using
* {@link SpringBootServletInitializer}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet);
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@SuppressWarnings({ "deprecation", "removal" })
private void configureThrowExceptionIfNoHandlerFound(WebMvcProperties webMvcProperties,
DispatcherServlet dispatcherServlet) {
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
List<String> dispatchServletBeans = Arrays
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome
.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome
.noMatch(message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (dispatchServletBeans.isEmpty()) {
return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
}
return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
.items(Style.QUOTE, dispatchServletBeans)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
}
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DispatcherServletRegistrationCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
if (!outcome.isMatch()) {
return outcome;
}
return checkServletRegistration(beanFactory);
}
private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {
boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
if (!containsDispatcherBean) {
return ConditionOutcome.match();
}
List<String> servlets = Arrays
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
if (!servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch(
startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
return ConditionOutcome.match();
}
private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {
ConditionMessage.Builder message = startMessage();
List<String> registrations = Arrays
.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));
boolean containsDispatcherRegistrationBean = beanFactory
.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
if (registrations.isEmpty()) {
if (containsDispatcherRegistrationBean) {
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());
}
if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
return ConditionOutcome.noMatch(message.found("servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
if (containsDispatcherRegistrationBean) {
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
return ConditionOutcome.match(message.found("servlet registration beans")
.items(Style.QUOTE, registrations)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
private ConditionMessage.Builder startMessage() {
return ConditionMessage.forCondition("DispatcherServlet Registration");
}
}
}
springboot怎么知道到这个位置来找配置类呢?
在启动类的@SpringBootApplication注解中@EnableAutoConfiguration->@Import(AutoConfigurationImportSelector.class)。AutoConfigurationImportSelector的getCandidateConfigurations方法:
springboot自动配置
springboot-自动配置原理
/**
* Return the auto-configuration class names that should be considered. By default,
* this method will load candidates using {@link ImportCandidates}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found in "
+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
4.SpringBoot-项目配置
SpringBoot使用约定>配置的方式为我们简化了ssm整合时xml的配置,SpringBoot的默认配置通过配置类的方式被加载到容器中,如果我们想自己进行一些配置的话,可以在yml/yaml/properties格式的配置文件中进行配置(SpringBoot默认读取项目下名字为application开头的yml yaml properties配置文件)。其中yml格式的配置文件使用较多
<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>
具体配置怎么写可以参考spring提供的官方文档:
springboot配置-官方文档
比如我们可以参考官网配置文档,先在application.properties中配一个web项目的上下文路径:
1.application.properties:
# web项目的application context名称,默认为空串
server.servlet.context-path=/springboot01
注意到此时springboot的启动日志有:
2023-11-29T22:43:59.069+08:00 INFO 82254 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/springboot01'
此时在访问web服务时,在url中就可以带上项目路径了:
2.yml配置文件
yml配置文件在实际开发中用的比较多,新建application.yml文件(注意配置文件必须以application开头)。如果我们的resources下同时有properties、yml、yaml文件,这些配置文件都会被springboot加载(如果配置信息冲突了怎么办?后面说)
ps:当yml和properties中的配置项冲突时,properties中的冲突配置项优先级更高。
yml基本格式要求:
1.大小写敏感
2.使用缩进代表层级关系(properties文件中使用.代表层级关系)
3.相同的部分只出现一次
4.注意key: value间的空格
我们还可以在yml中定义一些业务相关的自定义配置:
# yml文件:相比properties,突出了层级关系
server:
# key: value,value和逗号间要有一个空格。idea会自动处理这个缩进
port: 8088
servlet:
context-path: '/springboot01'
# 自定义配置
person:
name: zy
age: 20
gender: nan
# 或者可以写成json格式,一样注意空格
person2: {name: zy,age: 20}
# 自定义数组类型配置
city:
- beijing
- tianjin
- shanghai
# json
city2: [beijing,tianjin,shanghai]
如果同一个目录下,既有application.yml又有application.properties,默认先读取application.properties。如果同一个配置属性,在多个配置文件中都配置了,默认使用第一个读取到的配置属性对应的值,后面读取到的不覆盖前面读取到的。
springboot配置文件存放位置:
springboot中,配置文件可以放在以下4个地方:
1.当前项目根目录中(不推荐)
2.当前项目下创建一个config目录,放在config目录中(不推荐)
3.放在resources目录下(推荐)
4.resources下再创建config目录,放在config中
resources直接目录下的配置文件在编译后直接处于classes根目录下:
bootstrap配置文件
后面介绍
springboot项目结构:
**java:**下面放java代码
**public:**放所有的公开资源,注意url访问public下的资源时,不用带public文件名 eg:localhost:8080/springboot01/a.html 而不是localhost:8080/springboot01/public/a.html (这样访问就报404)
**static:**放所有的静态资源,同public,url访问时不带static文件名。eg:localhost:8080/springboot01/a.png,而不是localhost:8080/springboot01/static/a.png(报404)
**templates:**类似WEB-INF,放受保护的资源,前端无法通过url直接访问到templates目录下的资源,只能通过请求转发方式访问到。经常会放页面资源的模版,比如freemarker,thymeleaf
public/static目录下的资源引用static/public下的资源时,就好像public,static下的资源都在一个目录下似的引用。
eg:在public/a.html中引用static/a.png,不用带任何额外的路径
<img src="a.png">
**webapp目录:**只有当使用jsp时才需要webapp目录,springboot项目默认不创建webapp目录。如果我们需要写jsp,就要手动创建webapp目录,并在facets中标记它是一个web resource目录。现在基本上不这么用
5.SpringBoot-整合MyBatis
1.在pom.xml中导入mybatis启动器依赖(这个mybatis启动器就是第三方公司封装的启动器)、mysql连接器和lombok依赖:注意这里我们的spring-boot-starter-parent版本要和mybatis-spring-boot-starter兼容(mybatis-spring-boot-starter低于3.0.3就报错)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父maven项目,其中定义了各个依赖的版本号等-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot01</name>
<description>springboot01</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!--web启动器-->
<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>
<!--junit启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--导入mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.在application.yml中添加如下配置:
application.yml:
# yml文件:相比properties,突出了层级关系
server:
# key: value,value和逗号间要有一个空格。idea会自动处理这个缩进
port: 8088
servlet:
context-path: '/springboot01'
# 配置连接数据库的4要素
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/ssm?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# mybatis配置
mybatis:
# 指定mapper接口位置
type-aliases-package: com.example.springboot01.mapper
# 此时我们的mapper配置文件不用非得和mapper接口同级了(com.example.springboot01.mapper),我们可以随便放 但是需要在这里指定mapper配置文件的存放位置
# 如果mapper配置文件和mapper接口编译后在一个目录下的话,这里不配置也可以
mapper-locations: classpath:mybatis/*.xml
3.实现小需求,查询数据库ssm中的users表并把结果返回给浏览器
users表:
准备pojo类:
package com.example.springboot01.pojo;/**
* @Author:zhoayu
* @Date:2023/12/4 22:57
* @Description:com.example.springboot01.pojo
* @version:1.0
*/
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName User
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/4
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User implements Serializable {
private Long id;
private String name;
private String password;
private String nickname;
private String photo;
private String filetype;
}
Controller:
package com.example.springboot01.controller;/**
* @Author:zhoayu
* @Date:2023/12/4 22:59
* @Description:com.example.springboot01.controller
* @version:1.0
*/
import com.example.springboot01.pojo.User;
import com.example.springboot01.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;
/**
* @ClassName UserController
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/4
*/
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user")
@ResponseBody
public List<User> findAllUser(){
return userService.findAllUser();
}
}
Service:接口
package com.example.springboot01.service;
import com.example.springboot01.pojo.User;
import java.util.List;
/**
* @Author:zhoayu
* @Date:2023/12/4 23:01
* @Description:com.example.springboot01.service
* @version:1.0
*/
public interface UserService {
public List<User> findAllUser();
}
Service:实现类
package com.example.springboot01.service.impl;/**
* @Author:zhoayu
* @Date:2023/12/4 23:01
* @Description:com.example.springboot01.service.impl
* @version:1.0
*/
import com.example.springboot01.mapper.UserMapper;
import com.example.springboot01.pojo.User;
import com.example.springboot01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @ClassName UserServiceImpl
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/4
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAllUser() {
return userMapper.selectAllUsers();
}
}
Mapper:
package com.example.springboot01.mapper;
import com.example.springboot01.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Author:zhoayu
* @Date:2023/12/4 23:02
* @Description:com.example.springboot01.mapper
* @version:1.0
*/
@Mapper
public interface UserMapper {
public List<User> selectAllUsers();
}
Maapper配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0/EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springboot01.mapper.UserMapper">
<select id = "selectAllUsers" resultType="com.example.springboot01.pojo.User">
select * from users
</select>
</mapper>
请求和响应:
返回一个json数组,每个user对象都转换为了一个json对象
springboot整合mybatis总结(这也是通常使用框架的3步):
- 在pom.xml中导入mybatis-starter依赖
- 在application.yml中配置数据库连接4要素、mapper接口位置、mapper.xml位置
- 使用框架进行开发:开发mapper接口,在mapper.xml中定义对应方法名为id的具体查询语句
注意:保持好习惯,Mapper接口和它对应的xml文件同名
6.SpringBoot-整合logback
SpringBoot默认使用logback 组件作为日志管理,logback是由log4j创始人设计的一个开源日志组件。
在SpringBoot项目中我们不需要额外添加logback的依赖,因为在spring-boot-starter或者spring-boot-starter-web中已经包含了logback的依赖。
logback读取配置文件的步骤:
1.在classpath下查找logback-test.xml
2.如果文件不存在,则查找logback.xml
准备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" />
<!--日志同步到数据库-->
<!-- <appender-ref ref="DB"/> -->
</root>
<!--个别指定某个包下的日志打印级别-->
<logger name="com.example.springboot01.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>
此时就多打印了一些日志:
@Transactional注解:
@Transactional注解可以加到类上or方法上,当加到类上时,这个类下的所有方法都是一个事务。当加到方法上时,这个方法是一个事务。
package com.example.springboot01.service.impl;/**
* @Author:zhoayu
* @Date:2023/12/4 23:01
* @Description:com.example.springboot01.service.impl
* @version:1.0
*/
import com.example.springboot01.mapper.UserMapper;
import com.example.springboot01.pojo.User;
import com.example.springboot01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @ClassName UserServiceImpl
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/4
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public List<User> findAllUser() {
return userMapper.selectAllUsers();
}
}