简化spring应用开发
整个spring技术栈的一个大整合
j2ee开发的一站式解决方案
微服务:架构风格
一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;
单体应用:ALL IN ONE
微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;
向服务器发送 /hello ,服务器返回Hello Spring Boot !
点进去源码看他的上一层:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.6.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>由他来真正管理spring boot里面的所有的依赖版本。
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器
点进这个注解
//表示这是一个springboot的配置类 @SpringBootConfiguration //开启自动配置,springboot自动配置的核心 @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {首先点击去 @SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented //spring的原生注解,标识在一个类上,表示这是一个spring的配置类 @Configuration public @interface SpringBootConfiguration {回到上一层,点击进去@EnableAutoConfiguration
以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited //自动配置包 @AutoConfigurationPackage //@import spring的原生注解,往容器中导入AutoConfigurationImportSelector.class //将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器; @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {点击进入 @AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited //往容器中导入自动配置包的注册器类 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }这两个@Import注解,会将SpringBoot依赖中的 这两个spring.factory文件导入到容器中。 点开spring.factory: 这里面有springboot的大量的自动配置类,容器启动时,就会自动的将我们需要的自动配置导入进来。
J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-2.2.6.RELEASE.jar;
这个注解的底层有一个@Import(AutoConfigurationImportSelector.class), 他往容器中导入了一个AutoConfigurationImportSelector.class。 这个类是什么呢?
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //这个类里面调用了一个loadMetadata()方法,在容器启动的时候,往容器中加载元数据 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }再看loadMetadata():
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, PATH); }这个PATH是什么?
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";他往容器中加载的就是这个配置文件,点开可以看到里面都是springboot的自动配置类。 另外一个@Import(AutoConfigurationPackages.Registrar.class),他往容器中导入了一个AutoConfigurationPackages.Registrar.class,这个类是干什么的呢?
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }这个类实现了ImportBeanDefinitionRegistrar,这个类在ioc容器创建的源码中出现过,再次不再细说,它会将刚才导入进来的bean在容器创建的时候,全部注册到容器中。
整理一下思路:
@SpringBootApplication @SpringBootConfiguration @Configuration @EnableAutoConfiguration @AutoConfigurationPackage @Import(AutoConfigurationPackages.Registrar.class) @Import(AutoConfigurationImportSelector.class) @ComponentScan这里以字符编码过滤器为例
//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件 @Configuration(proxyBeanMethods = false) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中 @EnableConfigurationProperties(HttpProperties.class) //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果 满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass(CharacterEncodingFilter.class) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final HttpProperties.Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; }SpringBoot使用一个全局的配置文件,配置文件名是固定的; application.properties application.yml 配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
yaml
标记语言: 以前的配置文件;大多都使用的是 **xxxx.xml**文件; YAML:**以数据为中心**,比json、xml等更适合做配置文件;示例
server: port: 8081k:(空格)v:表示一对键值对(空格必须有);
以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
server: port: 8081 path: /hello属性和值也是大小写敏感
k: v:字面直接来写;
字符串默认不用加上单引号或者双引号;
“”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi
‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
k: v:在下一行来写对象的属性和值的关系;注意缩进
对象还是k: v的方式
friends: lastName: zhangsan age: 20行内写法:
friends: {lastName: zhangsan,age: 18}用- 值表示数组中的一个元素
pets: - cat - dog - pig行内写法:
pets: [cat,dog,pig]我们可以导入配置文件处理器,以后编写配置就有提示了
<!--导入配置文件处理器,配置文件进行绑定就会有提示--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>配置文件yml还是properties他们都能获取到值;
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
@Component @ConfigurationProperties(prefix = "person") public class Person { /** * <bean class="Person"> * <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> */ @Value("21") private String lastName; @Value("#{person.email}") private String email; private Map<String,Object> maps; private List<String> lists; private Dog dog;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService" class="com.yhd.springboot01.service.PersonService"></bean> </beans>测试类
@Autowired private PersonService service; @Test public void test(){ System.out.println(service); }1、配置类@Configuration------>Spring配置文件
2、使用 @Bean 给容器中添加组件
/** * @author yinhuidong * @createTime 2020-05-05-0:39 * * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件 * * 在配置文件中用<bean><bean/>标签添加组件 * */ @Configuration public class MyConfig { @Bean public PersonService getService(){ return new PersonService(); } }springboot启动会扫描以下位置的application.properties或者application.yaml文件作为spring boot的默认配置文件。
–file:./config/ –file:./ –classpath:/config/ –classpath:/优先级由高到低,高优先级的配置会覆盖低优先级的配置。
spring boot 会从这四个位置全部加载主配置文件,互补配置。
我们还可以通过spring.config.location来改变默认的配置文件位置 项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置; java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
1)命令行参数
所有的配置都可以在命令行上进行指定 java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc 多个配置用空格分开; --配置项=值2)来自java:comp/env的JNDI属性 3)Java系统属性(System.getProperties()
4)操作系统环境变量 5)RandomValuePropertySource配置的random.*属性值
由jar包外向jar包内进行寻找;
优先加载带profile
6)jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件 7)jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件 再来加载不带profile 8)jar包外部的application.properties或application.yml(不带spring.profile)配置文件 9)jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10)@Configuration注解类上的@PropertySource
11)通过SpringApplication.setDefaultProperties指定的默认属性
参考官方文档
配置文件到底能写什么?怎么写?自动配置原理; 配置文件能配置的属性参照
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着‘;配置文件能配置什么就可以参照某个功能对应的这个属性类
@ConfigurationProperties(prefix = "spring.http")//从配置文件中获取指定的值和bean的属 性进行绑定 public class HttpProperties { public static class Encoding { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private Charset charset = DEFAULT_CHARSET; private Boolean force; private Boolean forceRequest; private Boolean forceResponse; private Map<Locale, Charset> mapping;总结: springBoot启动会加载大量的自动配置类
我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
我们再来看这个自动配置类中到底配置了哪些组件
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值;
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置类里面的所有内容才生效;
@Conditional扩展注解作用(判断是否满足当前指定条件)@ConditionalOnJava系统的java版本是否符合要求@ConditionalOnBean容器中存在指定Bean;@ConditionalOnMissingBean容器中不存在指定Bean;@ConditionalOnExpression满足SpEL表达式指定@ConditionalOnClass系统中有指定的类@ConditionalOnMissingClass系统中没有指定的类@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean@ConditionalOnProperty系统中指定的属性是否有指定的值@ConditionalOnResource类路径下是否存在指定资源文件@ConditionalOnWebApplication当前是web环境@ConditionalOnNotWebApplication当前不是web环境@ConditionalOnJndiJNDI存在指定项自动配置类必须在一定的条件下才能生效; 我们怎么知道哪些自动配置类生效; 我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置 类生效;
Positive matches: ----------------- AopAutoConfiguration matched: - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition) AopAutoConfiguration.ClassProxyingConfiguration matched: - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition) - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition) DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition) - found 'session' scope (OnWebApplicationCondition)开发一个日志框架
1、System.out.println("");将关键数据打印在控制台;去掉?写在一个文件? 2、框架来记录系统的一些运行时信息;日志框架 ; zhanglogging.jar; 3、高大上的几个功能?异步模式?自动归档?xxxx? zhanglogging-good.jar? 4、将以前框架卸下来?换上新的框架,重新修改之前相关的API;zhanglogging-prefect.jar; 5、JDBC---数据库驱动; 写了一个统一的接口层;日志门面(日志的一个抽象层);logging-abstract.jar; 给项目中导入具体的日志实现就行了;我们之前的日志框架都是实现的抽象层;市面上的日志框架
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…
日志门面 (日志的抽象层)日志实现JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-loggingLog4j JUL(java.util.logging)Log4j2 Logback左边选一个抽象层接口,右边选一个接口的实现类。
接口:SLF4J
日志实现:Logback
SpringBoot底层是Spring框架,Spring框架默认是JCL
SpringBoot选用SLF4j和logback
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;给系统里面导入slf4j的jar和logback的实现jar。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); } }图示:
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架本身的配置文件。
如何让系统中所有的日志都统一到slf4j?
1、将系统中其他日志框架先排除出去; 2、用中间包来替换原有的日志框架; 3、我们导入slf4j其他的实现SpringBoot日志启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>底层依赖关系
总结:
1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录 2)、SpringBoot也把其他的日志都替换成了slf4j; 3)、中间替换包? @SuppressWarnings("rawtypes") public abstract class LogFactory { static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j"; static LogFactory logFactory = new SLF4JLogFactory();如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉
Spring框架用的是commons-logging;
<dependency> <groupId>org.springframework</groupId> <artifactId>spring‐core</artifactId> <exclusions> <exclusion> <groupId>commons‐logging</groupId> <artifactId>commons‐logging</artifactId> </exclusion> </exclusions> </dependency>SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
SpringBoot默认帮我们配置好了日志
//记录器 Logger logger = LoggerFactory.getLogger(getClass()); @Test public void contextLoads() { //System.out.println(); //日志的级别; //由低到高 trace<debug<info<warn<error //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效 logger.trace("这是trace日志..."); logger.debug("这是debug日志..."); //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root 级别 logger.info("这是info日志..."); logger.warn("这是warn日志..."); logger.error("这是error日志..."); }SpringBoot修改日志的默认配置
logging.level.com.atguigu=trace #logging.path= # 不指定路径在当前项目下生成springboot.log日志 # 可以指定完整的路径; #logging.file=G:/springboot.log # 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件 logging.path=/spring/log # 在控制台输出的日志的格式 logging.pattern.console=%d{yyyy‐MM‐dd} [%thread] %‐5level %logger{50} ‐ %msg%n # 指定文件中日志输出的格式 logging.pattern.file=%d{yyyy‐MM‐dd} === [%thread] === %‐5level === %logger{50} ==== %msg%n日志输出格式
日志输出格式: %d表示日期时间, %thread表示线程名, %‐5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 ‐‐> %d{yyyy‐MM‐dd HH:mm:ss.SSS} [%thread] %‐5level %logger{50} ‐ %msg%n logging.filelogging.pathExampleDescription(none)(none)只在控制台输出指定文件名(none)my.log输出日志到my.log文件(none)指定目录/var/log输出到指定目录的 spring.log 文件中给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
Logging SystemCustomizationLogbacklogback-spring.xml, logback-spring.groovy, logback.xml or logback.groovyLog4j2log4j2-spring.xml or log4j2.xmlJDK (Java Util Logging)logging.propertieslogback.xml:直接就被日志框架识别了; logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot 的高级Profile功能
<springProfile name="staging"> <!‐‐ configuration to be enabled when the "staging" profile is active ‐‐> 可以指定某段配置只在某个环境下生效 </springProfile>example:
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <!‐‐ 日志输出格式: %d表示日期时间, %thread表示线程名, %‐5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 ‐‐> <layout class="ch.qos.logback.classic.PatternLayout"> <springProfile name="dev"> <pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ‐‐‐‐> [%thread] ‐‐‐> %‐5level %logger{50} ‐ %msg%n</pattern> </springProfile> <springProfile name="!dev"> <pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ==== [%thread] ==== %‐5level %logger{50} ‐ %msg%n</pattern> </springProfile> </layout> </appender>sl4j+log4j
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>logback-classic</artifactId> <groupId>ch.qos.logback</groupId> </exclusion> <exclusion> <artifactId>log4j-over-slf4j</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency>切换为log4j2
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="false" scanPeriod="60 seconds" debug="false"> <!-- 定义日志的根目录 --> <property name="LOG_HOME" value="/app/log" /> <!-- 定义日志文件名称 --> <property name="appName" value="atguigu-springboot"></property> <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <!-- 日志输出格式: %d表示日期时间, %thread表示线程名, %-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} 尹会东 [%thread] %-5level %logger{50} - %msg%n</pattern> </layout> </appender> <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 --> <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 指定日志文件的名称 --> <file>${LOG_HOME}/${appName}.log</file> <!-- 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 %i:当文件大小超过maxFileSize时,按照i进行文件滚动 --> <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动, 且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是, 那些为了归档而创建的目录也会被删除。 --> <MaxHistory>365</MaxHistory> <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 日志输出格式: --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern> </layout> </appender> <!-- logger主要用于存放日志对象,也可以定义日志类型、级别 name:表示匹配的logger类型前缀,也就是包的前半部分 level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出, false:表示只用当前logger的appender-ref,true: 表示当前logger的appender-ref和rootLogger的appender-ref都有效 --> <!-- hibernate logger --> <logger name="com.atguigu" level="debug" /> <!-- Spring framework logger --> <logger name="org.springframework" level="debug" additivity="false"></logger> <!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应, 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 --> <root level="info"> <appender-ref ref="stdout" /> <appender-ref ref="appLogAppender" /> </root> </configuration>使用SpringBoot
1)、创建SpringBoot应用,选中我们需要的模块; 2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来 3)、自己编写业务代码;自动配置原理? 这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件; xxxxProperties:配置类来封装配置文件的内容;webjars:以jar包的方式引入静态资源;
www.webjars.org
<!--jquery--> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.0</version> </dependency> <!--bootstrap--> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency>localhost:8080/webjars/jquery/3.5.0/jquery.js
localhost:8080/abc === 去静态资源文件夹里面找abc
localhost:8080/ 找index页面
SpringBoot推荐的Thymeleaf; 语法更简单,功能更强大;
只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;
使用:
1)th:text ;改变当前元素里面的文本内容
th:任意html属性;来替换原生的属性
2)表达式
Simple expressions:(表达式语法) Variable Expressions: ${...}:获取变量值;OGNL; 1)、获取对象的属性、调用方法 2)、使用内置的基本对象: #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. ${session.foo} 3)、内置的一些工具对象: #execInfo : information about the template being processed. #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax. #uris : methods for escaping parts of URLs/URIs #conversions : methods for executing the configured conversion service (if any). #dates : methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : methods for formatting numeric objects. #strings : methods for String objects: contains, startsWith, prepending/appending, etc. #objects : methods for objects in general. #bools : methods for boolean evaluation. #arrays : methods for arrays. #lists : methods for lists. #sets : methods for sets. #maps : methods for maps. #aggregates : methods for creating aggregates on arrays or collections. #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration). Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; 补充:配合 th:object="${session.user}: <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> Message Expressions: #{...}:获取国际化内容 Link URL Expressions: @{...}:定义URL; @{/order/process(execId=${execId},execType='FAST')} Fragment Expressions: ~{...}:片段引用表达式 <div th:insert="~{commons :: main}">...</div> Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:(文本操作) String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:(数学运算) Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _官方文档
springboot自动配置好了springmvc
以下是springboot对springmvc的默认配置(WebMvcAutoConfiguration)
1)自动配置了viewResolver(视图解析器)
ContentNegotiatingViewResolver:组合所有的视图解析器的; 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来。
2)支持静态资源文件夹路径wbjars
index.html 静态首页访问
faicon.ico 定制的logo
3)自动注册了类型转换器和格式化器
Converter:转换器; public String hello(User user):类型转换使用Converter Formatter 格式化器; 2017.12.17===Date;
@Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date‐format")//在文件中配置日期格式化的规则 public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 }自己添加的格式化器转换器,我们只需要放在容器中即可
4)Support for HttpMessageConverters (see below).
支持消息转换
HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json; HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter; 自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中 (@Bean,@Component)
5)定义错误代码生成规则
MessageCodesResolver
6)ConfigurableWebBindingInitializer
其主要作用就是 初始化WebDataBinder;将请求的参数转化为对应的JavaBean,并且会结合上面的类型、格式转换一起使用。 protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { try { //从容器中获取 return (ConfigurableWebBindingInitializer)this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException var2) { return super.getConfigurableWebBindingInitializer(); } }可以发现ConfigurableWebBindingInitializer是从容器(beanFactory)中获取到的,所以我们可以配置一个ConfigurableWebBindingInitializer来替换默认的,只需要在容器中添加一个我们自定义的转换器即可。
编写一个配置类(@Configuration),是WebMvcConfigure类型;不能标注@EnableWebMvc; 既保留了所有的自动配置,也能用我们扩展的配置;
/** * @author yhd * @createtime 2020/10/5 21:15 */ @SpringBootConfiguration public class MyWebMvcConfig implements WebMvcConfigurer { /** * 根据url跳转到指定的路径 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/yhd请求,会跳转到 success 页面 registry.addViewController("/yhd").setViewName("success"); } /** * 自定义路径匹配规则 * 一般用不上 * @param configurer */ @Override public void configurePathMatch(PathMatchConfigurer configurer) { } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { // 是否通过请求参数来决定返回数据,默认为false configurer.favorParameter(true) .ignoreAcceptHeader(true) // 不检查Accept请求头 .parameterName("zyMediaType")// 参数名称,就是通过什么样的参数来获取返回值 .defaultContentType(MediaType.APPLICATION_JSON) .mediaType("json",MediaType.APPLICATION_JSON) .mediaType("xml",MediaType.APPLICATION_XML); // mediaType此方法是从请求参数扩展名(也就是最后一个.后面的值), // 然后绑定在parameterName上面,比如/admin/getUser.xml 等同于/admin/getUser?zyMediaType=xml // 如果不需要这种后缀的,那么就是全部通过参数的方式传递到后台 } /** * 配置异步请求处理选项 * @param configurer */ @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { } /** * 默认静态资源处理器 * @param configurer */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { } /** * 格式化转换器 * @param registry */ @Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new Formatter<Date>() { @Override public Date parse(String text, Locale locale) throws ParseException { return null; } @Override public String print(Date object, Locale locale) { return null; } }); } /** * 添加拦截器 * addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例 * addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截 * excludePathPatterns:用于设置不需要拦截的过滤规则 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { } /** * 自定义静态资源映射目录 * addResoureHandler:指的是对外暴露的访问路径 * addResourceLocations:指的是内部文件放置的目录 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/my/**").addResourceLocations("classpath:/my/"); } /** * 配置跨域 * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { } /** * 视图解析器 * @param registry */ @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(resourceViewResolver()); } /** * 说明:这个方法是我自己定义的, * 配置请求视图映射 * @return */ @Bean public InternalResourceViewResolver resourceViewResolver() { InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver(); //请求视图文件的前缀地址 internalResourceViewResolver.setPrefix("/WEB-INF/views/"); //请求视图文件的后缀 internalResourceViewResolver.setSuffix(".jsp"); return internalResourceViewResolver; } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) { } /** * 消息内容转换配置 * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { } /** * 配置异常处理器 * @param resolvers */ @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(new MyHandlerExceptionResolver()); } @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { } @Override public Validator getValidator() { return null; } @Override public MessageCodesResolver getMessageCodesResolver() { return null; } } public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {原理:
1)WebMvcAutoConfiguration是SpringMVC的自动配置类
2)自动配置类在做其他自动配置时会导入**@Import(EnableWebMvcConfiguration.class)**
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})可以看出EnableWebMvcConfiguration.class是自动配置类的一个内部类。并且继承了DelegatingWebMvcConfiguration
@Configuration(proxyBeanMethods = false) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {进入DelegatingWebMvcConfiguration
//从容器中获取所有的WebMvcConfigurer @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }函数中setConfigurers中可以看到,其将容器中所有的WebMvcConfigurer配置都获取到了。
3)容器中所有的WebMvcConfigurer都会一起起作用; 4)我们的配置类也会被调用; 效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
图示:
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
/** * @author yinhuidong * @createTime 2020-05-06-11:34 */ @EnableWebMvc @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/atguigu").setViewName("index"); } }原理:为什么@EnableWebMvc自动配置就失效了;
1)@EnableWebMvc的核心
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }2)发现这个注解上导入了DelegatingWebMvcConfiguration.class
点击去查看:
@Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {3)发现这个类继承了WebMvcConfigurationSupport
4)然后再回到SpringMVC的自动配置类–WebMvcAutoConfiguration
5)导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
页面引入静态资源
<head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Signin Template for Bootstrap</title> <!--引入wabjars的bootstrap--> <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"> <!-- 引入自定义在resource/static/的css --> <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet"> </head>springmvc处理国际化:
1)、编写国际化配置文件; 2)、使用ResourceBundleMessageSource管理国际化资源文件 3)、在页面使用fmt:message取出国际化内容springboot处理国际化:
点击进入MessageSourceProperties
public class MessageSourceProperties { /** * Comma-separated list of basenames (essentially a fully-qualified classpath * location), each following the ResourceBundle convention with relaxed support for * slash based locations. If it doesn't contain a package qualifier (such as * "org.mypackage"), it will be resolved from the classpath root. */ private String basename = "messages"; //可以通过spring。message。basename设置国际化文件名称位置,默认为messageapplication.properties
#修改项目访问路径 server.servlet.context-path=/crud #设置国际化文件位置 spring.messages.basename=i18n.login此时就达到了页面国际化的效果。
能够根据浏览器语言切换国际化的原因:
Springmvc的自动配置类:WebMvcAutoConfiguration @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); //默认的就是根据请求头带来的区域信息获取Locale进行国际化 localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } 区域信息对象解析器:LocaleResolver public interface LocaleResolver { Locale resolveLocale(HttpServletRequest request); void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale); }在请求链接上绑定区域信息
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>自定义区域信息解析器
/** * @author yinhuidong * @createTime 2020-05-06-15:08 * 自定义区域信息解析器 */ public class MyLocalResever implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); Locale locale = Locale.getDefault(); if (!StringUtils.isEmpty(l)) { String[] s = l.split("_"); locale=new Locale(s[0],s[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }将自定义的区域信息解析器注册到springboot容器中
/** * 将自定义的区域信息解析器注册到springboot容器中 * @return */ //此处方法返回值和方法名都不能改动,因为是要跟源码中的实现方法一致 @Bean public LocaleResolver localeResolver(){ return new MyLocalResever(); }开发期间模板引擎页面修改以后,要实时生效
# 禁用缓存 spring.thymeleaf.cache=false页面修改完成以后ctrl+f9:重新编译;
控制层
/** * @author yinhuidong * @createTime 2020-05-06-16:53 */ @Controller @RequestMapping("/user") public class UserController { @PostMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String,Object> map, HttpSession session){ if (!StringUtils.isEmpty(username)&&"root".equals(password)){ session.setAttribute("loginUser",username); //防止表单重复提交 return "redirect:/main.html"; } map.put("msg","用户名或密码错误!"); return "index"; } }自定义拦截器(登录检查)
/** * @author yinhuidong * @createTime 2020-05-06-17:26 * 自定义拦截器,登录检查 */ public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user==null){ //未登陆,返回登陆页面 request.setAttribute("msg","请先登录后访问!"); request.getRequestDispatcher("/index.html").forward(request,response); return false; } return true; } }将拦截器注册进容器
/** * 在SpringMVC的拓展配置文件里设置: * <h1>默认访问首页</h1> * @return */ //所有的WebMvcConfigurerAdapter组件都会一起起作用 @Bean //将组件注册在容器 public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){ WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); registry.addViewController("/main.html").setViewName("dashboard"); } //注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //super.addInterceptors(registry); //静态资源; *.css , *.js //SpringBoot已经做好了静态资源映射,但是如果自定义拦截器,会默认拦截静态资源,需要手动放行 registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login","/asserts/**","/webjars/**"); } }; return adapter; }登陆错误消息的显示
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>实验要求:
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作)RestfulCRUD查询getEmpemp—GET添加addEmp?xxxemp—POST修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT删除deleteEmp?id=1emp/{id}—DELETEthymeleaf公共页面元素抽取
1、抽取公共片段 <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> 2、引入公共片段 <div th:insert="~{footer :: copy}"></div> ~{templatename::selector}:模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3、默认效果: insert的公共片段在div标签中 如果使用th:insert等属性进行引入,可以不用写~{}: 行内写法可以加上:[[~{}]];[(~{})];三种引入公共片段的th属性: th:insert:将公共片段整个插入到声明引入的元素中 th:replace:将声明引入的元素替换为公共片段 th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer> 引入方式 <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div> 效果 <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div>引入片段的时候传入参数:
<nav class="col‐md‐2 d‐none d‐md‐block bg‐light sidebar" id="sidebar"> <div class="sidebar‐sticky"> <ul class="nav flex‐column"> <li class="nav‐item"> <a class="nav‐link active" th:class="${activeUri=='main.html'?'nav‐link active':'nav‐link'}" href="#" th:href="@{/main.html}"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke‐width="2" stroke‐ linecap="round" stroke‐linejoin="round" class="feather feather‐home"> <path d="M3 9l9‐7 9 7v11a2 2 0 0 1‐2 2H5a2 2 0 0 1‐2‐2z"></path> <polyline points="9 22 9 12 15 12 15 22"></polyline> </svg> Dashboard <span class="sr‐only">(current)</span> </a> </li> <!‐‐引入侧边栏;传入参数‐‐> <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>添加页面
<form> <div class="form‐group"> <label>LastName</label> <input type="text" class="form‐control" placeholder="zhangsan"> </div> <div class="form‐group"> <label>Email</label> <input type="email" class="form‐control" placeholder="zhangsan@atguigu.com"> </div> <div class="form‐group"> <label>Gender</label><br/> <div class="form‐check form‐check‐inline"> <input class="form‐check‐input" type="radio" name="gender" value="1"> <label class="form‐check‐label">男</label> </div> <div class="form‐check form‐check‐inline"> <input class="form‐check‐input" type="radio" name="gender" value="0"> <label class="form‐check‐label">女</label> </div> </div> <div class="form‐group"> <label>department</label> <select class="form‐control"> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> </select> </div> <div class="form‐group"> <label>Birth</label> <input type="text" class="form‐control" placeholder="zhangsan"> </div> <button type="submit" class="btn btn‐primary">添加</button> </form>提交的数据格式不对:生日:日期; 2017-12-12;2017/12/12;2017.12.12; 日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型; 2017-12-12—Date; 类型转换,格式化; 默认日期是按照/的方式;
如果想要修改:在配置文件中:
spring.mvc.date-format=yyyy-MM-dd指定日期格式化的方式
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">控制层
/** * 回显 */ @GetMapping("/emp/{id}") public String see(@PathVariable Integer id,Model model){ Employee employee = employeeDao.get(id); model.addAttribute("emp",employee); Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("depts",departments); return "emp/add"; } /** * 修改 */ @PutMapping("/emp") public String update(Employee employee){ employeeDao.save(employee); return "redirect:/emps"; }前端页面
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button> <form id="deleteEmpForm" method="post"> <input type="hidden" name="_method" value="delete"/> </form> <script> $(".deleteBtn").click(function(){ //删除当前员工的 $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit(); return false; }); </script> <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>控制层
/** * 删除 */ @DeleteMapping("/emp/{id}") public String delete(@PathVariable("id") Integer id){ employeeDao.delete(id); return "redirect:/emps"; }springboot底层会将我们控制器的返回值当做页面进行渲染,如果跳转到非页面的url,应该在
//请求重定向 return "redirect:/emps"; //请求转发 return "forward:/emps";浏览器发送消息的请求头:
错误处理的自动配置:ErrorMvcAutoConfiguration
这个类一共给容器中添加了以下组件:
1.DefaultErrorAttributes
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException()); }点击进入DefaultErrorAttributes
//帮助我们在页面共享信息 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, webRequest); addErrorDetails(errorAttributes, webRequest, includeStackTrace); addPath(errorAttributes, webRequest); return errorAttributes; }2.BasicErrorController
@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().collect(Collectors.toList())); }点击进入BasicErrorController
//产生html类型的数据;浏览器发送的请求来到这个方法处理 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪个页面作为错误页面;包含页面地址和页面内容 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping//产生json数据,其他客户端来到这个方法处理; public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity<>(body, status); }3.ErrorPageCustomizer
@Bean public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) { return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath); }点击进入ErrorPageCustomizer
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage( this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); }点击进入getError()
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {再回到刚才的registerErrorPages方法点击getPath()
//制定了错误页面的访问路径 @Value("${error.path:/error}") private String path = "/error";4.DefaultErrorViewResolver
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); }点击进入DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<Series, String> SERIES_VIEWS; static { Map<Series, String> views = new EnumMap<>(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); } /** 在此处对错误页面进行视图渲染 */ @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; /** 模板引擎可以解析这个页面地址就用模板引擎解析 */ TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { /** 模板引擎可用的情况下返回到errorViewName指定的视图地址 */ return new ModelAndView(errorViewName, model); } /** 模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页 面 error/404.html */ return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error 请求;就会被BasicErrorController处理,响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的,然后有DefaultErrorAttributes帮我们在页面共享信息
自定义异常处理&返回定制的json数据并转发到/error进行自适应效果处理
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由 getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法); 1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中; 2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到; 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
/** * @author yinhuidong * @createTime 2020-05-07-14:16 * 自定义异常处理器 */ @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(RuntimeException.class) public String handlerException(Exception e, HttpServletRequest request) { Map<String, Object> map = new HashMap<>(); /** * 传入我们自己的错误码,4xx,5xx,否则就不会进入定制错误页面的解析流程 * getAttribute("javax.servlet.error.status_code); */ request.setAttribute("javax.servlet.error.status_code", 500); map.put("code", "user.notexist"); map.put("messages", e.getMessage()); //转发到/error return "forward:/error"; } } /** * @author yinhuidong * @createTime 2020-05-07-16:09 * 给容器中加入我们自己定义的ErrorAttributes */ @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String,Object> map=super.getErrorAttributes(webRequest,includeStackTrace); map.put("company","atguigu"); return map; } }SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;
1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】)
server.port=8081 server.context‐path=/crud server.tomcat.uri‐encoding=UTF‐8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx2.编写一个ConfigurableServletWebServerFactory:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
/** * @author yinhuidong * @createTime 2020-05-07-16:52 * tomcat的配置类 */ @Configuration public class TomCatConfiguration { /** * 修改springboot对tomcat的默认配置 */ @Bean public ConfigurableServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.setPort(8083); return factory; } }原理分析
方法返回值ConfigurableServletWebServerFactory
public interface ConfigurableServletWebServerFactory extends ConfigurableWebServerFactory, ServletWebServerFactory { void setContextPath(String contextPath); void setDisplayName(String displayName); void setSession(Session session); void setRegisterDefaultServlet(boolean registerDefaultServlet); void setMimeMappings(MimeMappings mimeMappings); void setDocumentRoot(File documentRoot); void setInitializers(List<? extends ServletContextInitializer> initializers); void addInitializers(ServletContextInitializer... initializers); void setJsp(Jsp jsp); void setLocaleCharsetMappings(Map<Locale, Charset> localeCharsetMappings); void setInitParameters(Map<String, String> initParameters); }点击进入ConfigurableWebServerFactory
public interface ConfigurableWebServerFactory extends WebServerFactory, ErrorPageRegistry { void setPort(int port); void setAddress(InetAddress address); void setErrorPages(Set<? extends ErrorPage> errorPages); void setSsl(Ssl ssl); void setSslStoreProvider(SslStoreProvider sslStoreProvider); void setHttp2(Http2 http2); void setCompression(Compression compression); void setServerHeader(String serverHeader); }new出来的对象TomcatServletWebServerFactory
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final Set<Class<?>> NO_CLASSES = Collections.emptySet(); /** * The class name of default protocol used. */ public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol"; private File baseDirectory; private List<Valve> engineValves = new ArrayList<>(); private List<Valve> contextValves = new ArrayList<>(); private List<LifecycleListener> contextLifecycleListeners = getDefaultLifecycleListeners(); private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>(); private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>(); private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>(); private final List<Connector> additionalTomcatConnectors = new ArrayList<>(); private ResourceLoader resourceLoader; private String protocol = DEFAULT_PROTOCOL; private Set<String> tldSkipPatterns = new LinkedHashSet<>(TldSkipPatterns.DEFAULT); private Charset uriEncoding = DEFAULT_CHARSET; private int backgroundProcessorDelay; private boolean disableMBeanRegistry = true;ConfigurableTomcatWebServerFactory
public interface ConfigurableTomcatWebServerFactory extends ConfigurableWebServerFactory { void setBaseDirectory(File baseDirectory); void setBackgroundProcessorDelay(int delay); void addEngineValves(Valve... engineValves); void addConnectorCustomizers(TomcatConnectorCustomizer... tomcatConnectorCustomizers); void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers); void addProtocolHandlerCustomizers(TomcatProtocolHandlerCustomizer<?>... tomcatProtocolHandlerCustomizers); void setUriEncoding(Charset uriEncoding); }由此可见:
new 的对象 TomcatServletWebServerFactory implements ConfigurableTomcatWebServerFactory implements ConfigurableWebServerFactory 方法返回值 ConfigurableServletWebServerFactory implements ConfigurableWebServerFactory 他们最终都指向了 ConfigurableWebServerFactory由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。 注册三大组件用以下方式:
1.ServletRegistrationBean
/** * @author yinhuidong * @createTime 2020-05-07-18:10 */ public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } } /** * 注册一个servlet * @return */ @Bean public ServletRegistrationBean servletRegistrationBean(){ return new ServletRegistrationBean(new MyServlet(),"/servlet"); }2**.FilterRegistrationBean**
/** * @author yinhuidong * @createTime 2020-05-07-18:14 */ public class MyFilter extends HttpFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("filter()......"); chain.doFilter(request,response); } } /** * 注册一个过滤器 */ @Bean public FilterRegistrationBean filterRegistrationBean(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new MyFilter()); bean.setUrlPatterns(Arrays.asList("/servlet")); return bean; }3.ServletListenerRegistrationBean
/** * @author yinhuidong * @createTime 2020-05-07-18:22 */ public class MyListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { System.out.println("容器创建了"); } public void contextDestroyed(ServletContextEvent sce) { System.out.println("容器销毁了"); } } /** * 注册一个监听器 */ @Bean public ServletListenerRegistrationBean servletListenerRegistrationBean() { return new ServletListenerRegistrationBean<>(new MyListener()); }SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet; DispatcherServletAutoConfiguration中:
@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()); //默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; }默认支持:
Tomcat(默认使用)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐starter‐web</artifactId> 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器; </dependency>Jetty
<!‐‐ 引入web模块 ‐‐> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐starter‐web</artifactId> <exclusions> <exclusion> <artifactId>spring‐boot‐starter‐tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!‐‐引入其他的Servlet容器‐‐> <dependency> <artifactId>spring‐boot‐starter‐jetty</artifactId> <groupId>org.springframework.boot</groupId> </dependency>Undertow
<!‐‐ 引入web模块 ‐‐> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐starter‐web</artifactId> <exclusions> <exclusion> <artifactId>spring‐boot‐starter‐tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!‐‐引入其他的Servlet容器‐‐> <dependency> <artifactId>spring‐boot‐starter‐undertow</artifactId> <groupId>org.springframework.boot</groupId> </dependency>Spring Boot 2.2.6 版本的嵌入式Servlet容器自动配置是通过WebServerFactoryCustomizer定制器来定制的
@FunctionalInterface public interface WebServerFactoryCustomizer<T extends WebServerFactory> { /** * Customize the specified {@link WebServerFactory}. * @param factory the web server factory to customize */ void customize(T factory); }首先项目启动,调用ServletWebServerApplicationContext这个类中的**createWebServer()**方法:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { /** * 获取到一个与当前应用所导入的Servlet类型相匹配的web服务工厂定制器 */ ServletWebServerFactory factory = getWebServerFactory(); /** *生成Tomcat web服务工厂定制器,定制Servlet容器配置 */ this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }该方法最终能够获取到一个与当前应用所导入的Servlet类型相匹配的web服务工厂定制器,例如你的pom文件导入的Servlet依赖为Tomcat,那么最终会生成Tomcat web服务工厂定制器,定制Servlet容器配置。
并通过上述代码中的getWebServer()方法创建对应的Servlet容器,并启动容器。
来到EmbeddedWebServerFactoryCustomizerAutoConfiguration(嵌入式web服务工厂定制器自动配置类),根据导入的依赖信息,该配置类会自动创建相应类型的容器工厂定制器(目前Spring Boot 2.x 版本支持tomcat、jetty、undertow、netty),以tomcat为例,这里会创建TomcatWebServerFactoryCustomizer组件:
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication //倒入配置 @EnableConfigurationProperties(ServerProperties.class) public class EmbeddedWebServerFactoryCustomizerAutoConfiguration { /** * 容器工厂定制器 */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class }) public static class TomcatWebServerFactoryCustomizerConfiguration { @Bean public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } }WebServerFactoryCustomizerBeanPostProcessor(web服务工厂定制器组件的后置处理器),该类负责在bean组件初始化之前执行初始化工作。 该类先从IOC容器中获取所有类型为WebServerFactoryCustomizer(web服务工厂定制器)的组件:
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans()); this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; }获取到所有的定制器后,后置处理器调用定制器的**customize()**方法来给嵌入式的Servlet容器进行配置(默认或者自定义的配置):
@Override public void customize(ConfigurableTomcatWebServerFactory factory) { ServerProperties properties = this.serverProperties; ServerProperties.Tomcat tomcatProperties = properties.getTomcat(); PropertyMapper propertyMapper = PropertyMapper.get(); propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory); propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds) .as(Long::intValue).to(factory::setBackgroundProcessorDelay); customizeRemoteIpValve(factory); propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive) .to((maxThreads) -> customizeMaxThreads(factory, tomcatProperties.getMaxThreads())); propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive) .to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads)); propertyMapper.from(this.serverProperties.getMaxHttpHeaderSize()).whenNonNull().asInt(DataSize::toBytes) .when(this::isPositive) .to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize)); propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes) .to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize)); propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes) .when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0) .to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize)); propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled) .to((enabled) -> customizeAccessLog(factory)); propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding); propertyMapper.from(properties::getConnectionTimeout).whenNonNull() .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull() .to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout)); propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive) .to((maxConnections) -> customizeMaxConnections(factory, maxConnections)); propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive) .to((acceptCount) -> customizeAcceptCount(factory, acceptCount)); propertyMapper.from(tomcatProperties::getProcessorCache) .to((processorCache) -> customizeProcessorCache(factory, processorCache)); propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText() .to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars)); propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText() .to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars)); customizeStaticResources(factory); customizeErrorReportValve(properties.getError(), factory); }从源码分析可以得出配置嵌入式Servlet容器的两种解决方案:
1.在全局配置文件中,通过server.xxx来修改和server有关的配置:
server.port=8081 server.tomcat.xxx...2、实现WebServerFactoryCustomizer接口,重写它的customize()方法,对容器进行定制配置:,但是比较麻烦,可以直接参照上面的分析:ConfigurableServletWebServerFactory
/** * @author yinhuidong * @createTime 2020-05-07-16:52 * tomcat的配置类 */ @Configuration public class TomCatConfiguration { /** * 修改springboot对tomcat的默认配置 */ @Bean public ConfigurableServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.setPort(8083); return factory; } }分析ServletWebServerApplicationContext类的**createWebServer()**方法:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }应用启动后,根据导入的依赖信息,创建了相应的Servlet容器工厂,以tomcat为例,创建嵌入式的Tomcat容器工厂TomcatServletWebServerFactory,调用getWebServer()方法创建Tomcat容器:
public WebServer getWebServer(ServletContextInitializer... initializers) { //创建嵌入式的Tomcat容器 Tomcat tomcat = new Tomcat(); File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this.prepareContext(tomcat.getHost(), initializers); return this.getTomcatWebServer(tomcat); } ... protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { //调用TomcatWebServer类的有参构造器 //如果配置的端口号>=0,创建Tomcat容器 return new TomcatWebServer(tomcat, this.getPort() >= 0); }分析TomcatWebServer类的有参构造器:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) { this.monitor = new Object(); this.serviceConnectors = new HashMap(); Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.initialize(); }执行initialize(),调用start()方法完成了tomcat容器的启动:
private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false)); Object var1 = this.monitor; synchronized(this.monitor) { try { this.addInstanceIdToEngineName(); Context context = this.findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && "start".equals(event.getType())) { this.removeServiceConnectors(); } }); //启动tomcat容器 this.tomcat.start(); this.rethrowDeferredStartupExceptions();1、Spring Boot 根据导入的依赖信息,自动创建对应的web服务工厂定制器; 2、web服务工厂定制器组件的后置处理器获取所有类型为web服务工厂定制器的组件(包含实现WebServerFactoryCustomizer接口,自定义的定制器组件),依次调用customize()定制接口,定制Servlet容器配置; 3、嵌入式的Servlet容器工厂创建tomcat容器,初始化并启动容器。
嵌入式Servlet容器:应用打成可执行的jar 优点:简单、便携; 缺点:默认不支持JSP、优化定制比较复杂(使用定制器,自己编写嵌入式Servlet容器的创建工厂);
外置的Servlet容器:外面安装Tomcat—应用war包的方式打包;
步骤 1)、必须创建一个war项目;(利用idea创建好目录结构) 2)、将嵌入式的Tomcat指定为provided;
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐starter‐tomcat</artifactId> <scope>provided</scope> </dependency>3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { //传入SpringBoot应用的主程序 return application.sources(SpringBoot04WebJspApplication.class); } }4)、启动服务器就可以使用;
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器; servlet3.0(Spring注解版): 8.2.4 Shared libraries / runtimes pluggability: 规则: 1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例: 2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名 3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类; 流程: 1)、启动Tomcat 2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META- INF\services\javax.servlet.ServletContainerInitializer: Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer 3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型 的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例; 4)、每一个WebApplicationInitializer都调用自己的onStartup;
5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { //1、创建SpringApplicationBuilder SpringApplicationBuilder builder = createSpringApplicationBuilder(); StandardServletEnvironment environment = new StandardServletEnvironment(); environment.initPropertySources(servletContext, null); builder.environment(environment); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来 builder = configure(builder); //使用builder创建一个Spring应用 SpringApplication application = builder.build(); if (application.getSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.getSources().add(getClass()); } Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } //启动Spring应用 return run(application); }7)、Spring的应用就启动并且创建IOC容器
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新IOC容器 refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }启动Servlet容器,再启动SpringBoot应用。
