16.SpringBoot(1)

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项目:
springboot1
选择依赖
springboot2
选择以上依赖后的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项目启动日志:
springboot项目启动日志
可以看到tomcat started on port 8080 with context path ‘’,由于此时项目的上下文路径为空串,我们在前端请求时不带application context直接请求映射路径就可以,如下图:
请求springboot项目
在我们第一次请求时,发现dispatcherServlet对象也被创建了:
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的配置类位置:
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中就可以带上项目路径了:
springboot-with-application context

2.yml配置文件
yml配置文件在实际开发中用的比较多,新建application.yml文件(注意配置文件必须以application开头)。如果我们的resources下同时有properties、yml、yaml文件,这些配置文件都会被springboot加载(如果配置信息冲突了怎么办?后面说)
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.当前项目根目录中(不推荐)
配置文件1
2.当前项目下创建一个config目录,放在config目录中(不推荐)
配置文件2
3.放在resources目录下(推荐)
配置文件3
4.resources下再创建config目录,放在config中
配置文件4
resources直接目录下的配置文件在编译后直接处于classes根目录下:
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
springboot项目结构
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表:
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步):

  1. 在pom.xml中导入mybatis-starter依赖
  2. 在application.yml中配置数据库连接4要素、mapper接口位置、mapper.xml位置
  3. 使用框架进行开发:开发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>

此时就多打印了一些日志:
logback日志
@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();
    }
}

7.SpringBoot-整合PageHelper插件

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值