一个功能:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串
<properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> //这个依赖是让测试类@SpringBootTest能够自己找到springboot主启动类,不用再加@Runwith注解了 <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> //将应用打包成jar包的插件 <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> //@SpringBootApplication: 标注这个类是一个springboot的应用: 启动类下的所有资源被导入 //Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot 就应该运行这个类的main方法来启动SpringBoot应用 @SpringBootApplication public class HelloworldApplication { public static void main(String[] args) { //将springboot应用启动 //SpringApplication类co //run方法 SpringApplication.run(HelloworldApplication.class, args); } } @RestController //注:这里的@RestController 就是指处理器方法不跳转页面而是直接返回结果(数据), // 它的作用相当于@Controller + 处理器方法上加ResponseBody public class HelloController { @RequestMapping("/hello") public String Hello(){ return "Hello!!!朱哥"; } }POM文件
父项目
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> //它的父项目是 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.3.RELEASE</version> </parent> //他来真正管理Spring Boot应用里面所有的依赖版本 //所以我们以后导入依赖默认是不需要写版本的(除非没在dependencies里面管理) <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> //spring-boot-starter: springboot 场景启动器;帮我们导入了web模块正常运行所依赖的组件; //spring-boot 将所有的功能场景都抽取出来, 做成一个个的Staters(启动器), 只需要在项目里引入这些Starter相关的场景的所有依赖都会导入进来。主程序类, 主入口类
@SpringBootApplication部分源码
@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 {@SpringBootConfiguration:Spring Boot的配置类; 标注在某个类上,表示这是一个Spring Boot的配置类;
@Configuration:配置类上来标注这个注解; 配置类 ----- 配置文件;配置类也是容器中的一个组件;@Component
@EnableAutoConfiguration:开启自动配置功能; 以前我们需要配置的东西,Spring Boot帮我们自动配置;
@EnableAutoConfiguration告诉SpringBoot开启自 动配置功能;这样自动配置才能生效; ```java @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ```@AutoConfigurationPackage:自动配置包 @Import(AutoConfigurationPackages.Registrar.class): Spring的底层注解@Import,给容器中导入一个组件,也就是 AutoConfigurationPackages.Registrar.class;
@Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器
(注: 是将主程序类所在包里所有组件扫描进去,所以说那些controller包和service包一定要跟主类并列,而不是包含,否则可能出错,扫描不到容器里)
@Import(AutoConfigurationImportSelector.class);给容器中导入组件
AutoConfigurationImportSelector:导入哪些组件的选择器; 将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中; 会给容器中导入非常多的自动配置类(xxxAutoConfiguration); 就是给容器中导入这个场景需要的所有组件, 并配置好这些组件;
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作; SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader); Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值, 从源码不难看出加载类扫描的路径 ~~~java ```java public final class SpringFactoriesLoader { /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; ~~~ ```java 将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作; J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-2.3.3.RELEASE.jarSpringBoot使用一个全局的配置文件,配置文件名是固定的;
•application.properties
•application.yml
配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
以前的配置文件;大多都使用的是 xxxx.xml文件;
YAML:以数据为中心,比json、xml等更适合做配置文件; 是一个标记语言
基本语法
k:(空格)v:表示一对键值对(空格必须有); 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级
server: port: 8081属性和值也是大小写敏感;
值的写法
字面量,普通的值(数字,字符串,布尔)
k: v:字面直接来写; 字符串默认不用加上单引号或者双引号; “”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi ‘’:单引号;会转义特殊字符,特殊字符终只是一个普通的字符串数据
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
对象、Map(属性和值)(键值对)
k: v:在下一行来写对象的属性和值的关系;注意缩进 对象还是k: v的方式
student: name: zhuge age: 20数组(List、Set)
用- 值表示数组中的一个元素
pets: -cat -dog -pig行内写法
petss: [cat,dog,pig]@Value获取值和@ConfigurationProperties获取值比较
配置文件yml还是properties他们都能获取到值;如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;配置文件注入值数据校验
@Component @ConfigurationProperties(prefix = "person") @Validated//数据校验 public class Person { /** * <bean class="Person"> * <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/# {SpEL}"></property> * <bean/> */ //Name必须是邮箱格式 @Email(message="邮箱格式错误") //@Value("${person.name}") private String name; //@Value("#{11*2}") private Integer age; //@Value("true") private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog;注: 2.3.0之后的版本需要导入相应的依赖才能进行数据校验
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.17.Final</version> <scope>compile</scope> </dependency>@PropertySource
@PropertySource(value = {"classpath:application.yml"}) @Component @ConfigurationProperties(prefix = "dog") public class Dog { // @Value("旺财") private String firstName; // @Value("3") private Integer age;@PropertySource:加载指定的配置文件;
第一种方式: 配置类@Configuration------>Spring配置文件
@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;
Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
@ImportResource(locations = {"classpath:beans.xml"}) //导入Spring的配置文件让其生效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="helloService" class="com.zhuge.service.HelloService"></bean> </beans> package com.zhuge.service; /** * @author ZCH * @date 2020/9/24 0024 - 下午 4:03 */ public class HelloService { }第二种方式: 使用@Bean给容器中添加组件
@Configuration //指明当前类是一个配置类;就是来替代之前的Spring配置文件 public class MyMvcConfig implements WebMvcConfigurer { //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } }随机数
${random.value}、${random.int}、${random.long} ${random.int(10)}、${random.int[1024,65536]}占位符获取之前配置的值, 如果没有可以使用: 指定默认值 ${person.hello:hello} 默认是hello
person: name: hello age: 20 happy: false birth: 2020/8/24 maps: {k1: v1,k2: v2} lists: - code - music - girl dog: name: ${person.hello:hello}_旺财 age: 3多Profile文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml 默认使用application.properties的配置;
server: port: 8081 spring: profiles: active: dev #激活指定profile --- server: port: 8082 spring: profiles: dev --- server: port: 8083 spring: profiles: test #指定属于哪个环境激活指定profile
在配置文件中指定 spring.profiles.active=dev
命令行: java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
可以直接在测试的时候,配置传入命令行参数
虚拟机参数;
-Dspring.profiles.active=dev
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
(注: 这里classpath 指的是resources文件夹)
–file:./config/ 代表读取jar包所在目录下的config文件–file:./ 代表读取jar包所在目录–classpath:/config/ 代表类路径下的config包下的文件–classpath:/ 代表类路径下的文件SpringBoot会从这四个位置全部加载主配置文件;互补配置;
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默 认加载的这些配置文件共同起作用形成互补配置;
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties
见原理初探
SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
其内部有两个非常重要的注解:
@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)(自动配置包)
AutoConfigurationPackage源码
@Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {Spring的底层注解@Import,给容器中导入一个组件,也就是 AutoConfigurationPackages.Registrar.class;
Register类的内部
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); }Register类是 AutoConfigurationPackage 下的一个静态内部类。 这个类的作用是给容器中导入组件
主要看 new PackageImports(metadata).getPackageNames(), 它是导入一个metadata(注释源信息)到PackageImports中,然后获取这个包的包名。 所以说这个实例对象获取的包名是什么呢?
注: 要在debug模式下,选中new PackageImports(metadata)所在行作为断点
当调用到register()时, 我们就会发现packageNames就是我们的主配置类所在包的包名
总结: @AutoConfigurationPackage 的作用就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器
这个注解的作用是给容器中导入组件,该组件就是AutoConfigurationImportSelector:自动配置导入选择器,它可以帮我们选择要导入的组件
进入AutoConfigurationImportSelector的源码, 它与SpringBoot1.x 版本有一些区别, 2.x中有一个静态内部类
其大致的意思是 自动配置组 ,能够帮我们完成一系列的自动配置的操作。
重点回到该内部类下的**process()**方法
调用了 **getAutoConfigurationEntry()**方法, 该方法的作用就是告诉Spring容器需要导入什么组件, 并以 String[] 的形式返回全类名
从 标蓝的一行 可以看出, SpringBoot已经帮我们自动导入了 127 个组件, 从下图可以看出都是以全类名的形式返回。而且这些组件都是以 …AutoConfiguration 的形式命名, 也就是什么自动配置类
有了这些自动配置类, 我们就免去了手动配置注入功能组件的操作了。
那么为何能做到自动配置呢??(之前的AutoConfigurationImportSelector只是帮助我们选择要导入的组件,AutoConfigurationPackage是将组件扫描到Spring容器 )其实是 configurations 这个变量是由getCandidateConfigurations()方法(上图标蓝的上一行)得到的, 也就是获取候选的位置。
这里调用了
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); //传入了两个参数 protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; }于是便要知道loadFactoryNames()的作用
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }在这里又调用了loadSpringFactories(classLoader)并将类加载器作为参数. (注: classloader的实参就是getBeanClassLoader())
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }首先是在 133 行,从类加载器中加载一个资源,资源路径为FACTORIES_RESOURCE_LOCATION其实就是
META-INF/spring.factories
将获取到的一个资源赋值给 Enumeration类型的变量urls, 如果该变量中有下一个元素,说明这里面又包含资源,那就将这个资源加载成 properties 配置文件,并转换成为键值对即 Map类型的数据 进行返回
public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; ... }META-INF/spring.factories位置
总结:
SpringBoot 在启动的时候就从类路径下的 META-INF/spring.factories 中获取EnableAutoConfiguration指定的值,并将这些值加载到自动配置类导入到容器中,自动配置类 就生效,帮助我们进行自动配置功能。 而这些自动配置类 全都在 spring-boot-autoconfigure-2.3.3.RELEASE.jar 该jar包之下
如果要看自动配置类配置了什么功能,就点进==…AutoConfiguration==的源码即可
每一个这样的==…AutoConfiguration==类都是容器中的一个组件, 都加入到容器中; 用他们来做自动配置;
举例(以HttpEncodingAutoConfiguration(Http编码自动配置)为例)
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(ServerProperties.class) ///启动指定类的 ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把 HttpEncodingProperties加入到ioc容器中 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //Spring底层@Conditional注解),根据不同的条件,如果 满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(ServerProperties properties) { this.properties = properties.getServlet().getEncoding(); } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @ConditionalOnMissingBean//判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE)); return filter; } @Bean public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() { return new LocaleCharsetMappingsCustomizer(this.properties); } static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered { private final Encoding properties; LocaleCharsetMappingsCustomizer(Encoding properties) { this.properties = properties; } @Override public void customize(ConfigurableServletWebServerFactory factory) { if (this.properties.getMapping() != null) { factory.setLocaleCharsetMappings(this.properties.getMapping()); } } @Override public int getOrder() { return 0; } } }所有在配置文件中能配置的属性都是在xxxxProperties类中封装着‘;配置文件能配置什么就可以参照某个功 能对应的这个属性类
@ConfigurationProperties( prefix = "spring.http.encoding" //从配置文件中获取指定的值和bean的属性进行绑定 ) public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET; ... }@Conditional派生注解(Spring注解版原生的@Conditional作用) 作用:必须是@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
debug=true
默认是false,开启Springboot的debug模式,会非常好用,我们来运行一下,一旦debug模式进来以后呢,控制台就会告诉我们哪些用了
哪些没用,自动配置报告
#application.properties debug=true server.port=8081 ============================ CONDITIONS EVALUATION REPORT ============================ 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) DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched: - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition) - Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition) DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration matched: - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition) - DispatcherServlet Registration did not find servlet registration bean (DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition) DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration matched: - @ConditionalOnBean (names: dispatcherServlet types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet' (OnBeanCondition) EmbeddedWebServerFactoryCustomizerAutoConfiguration matched: - @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition) EmbeddedWebServerFactoryCustomizerAutoConfiguration.TomcatWebServerFactoryCustomizerConfiguration matched: - @ConditionalOnClass found required classes 'org.apache.catalina.startup.Tomcat', 'org.apache.coyote.UpgradeProtocol' (OnClassCondition) ErrorMvcAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.servlet.Servlet', 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition) - found 'session' scope (OnWebApplicationCondition) ErrorMvcAutoConfiguration#basicErrorController matched: - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorController; SearchStrategy: current) did not find any beans (OnBeanCondition) ErrorMvcAutoConfiguration#errorAttributes matched: - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorAttributes; SearchStrategy: current) did not find any beans (OnBeanCondition) ErrorMvcAutoConfiguration.DefaultErrorViewResolverConfiguration#conventionErrorViewResolver matched: - @ConditionalOnBean (types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet'; @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; SearchStrategy: all) did not find any beans (OnBeanCondition) ...这样我们就可以很方便的知道哪些自动配置类生效;(下面还有很多没打出来)
创建SpringBoot应用,选中我们需要的模块;
SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
自己编写业务代码;
自动配置原理? 这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件; xxxxProperties:配置类来封装配置文件的内容;第一种方式
所有 /webjars/**(指的是这个访问路径) ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源(比如 jquery,css等等);
//引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可 <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency>第二种方式
@ConfigurationProperties(prefix = "spring.mvc") public class WebMvcProperties { ... /** * Path pattern used for static resources. */ private String staticPathPattern = "/**"; ... }“/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META‐INF/resources/", "classpath:/resources/",(注:这里是指在maven给定的resources下再建一个resources文件夹) "classpath:/static/", "classpath:/public/" "/":当前项目的根路径第三种方式
欢迎页;静态资源文件夹下的所有index.html页面;被“/**”映射;
localhost:8080/(也满足/**) 找index.html(需要放到静态资源的文件夹下)
导入坐标
<!-- thymeleaf, 基于3.x开发,之前的版本还得在properties标签里改版本 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> </dependency>只要我们把HTML页面==放在classpath:/templates/==下 , thymeleaf就能自动渲染;
导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">th:text ; 改变当前元素里面的文本内容 ;
th: 任意html属性;来替换原生属性的值
表达式
补充:(OGNL):OGNL(Object-Graph Navigation Language)的全称是对象图导航语言,它是一种功能强大的开源表达式语言,比 EL(只能从域或内置对象中)表达式更强大,使用这种表达式语言,可以通过某种表达式语法,OGNL可以存取Java任意对象的任意属性,调用Java对象的方法,同时能够自动实现必要的类型转换。如果把表达式看作是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。
Simple expressions: 1.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 #在thymeleaf官方文档里 You can read the full reference of these objects in Appendix A. 可以看用法 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). #在thymeleaf官方文档里 You can read the full reference of these objects in Appendix B. 可以看用法 2.Selection Variable Expressions: *{...} :选择表达式:和${} 在功能上是一样的; 补充: 配合 <div th:object="${session.user}"> (span标签里的 * 就相当于${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> 3.Message Expressions: #{...} : 获取国际化信息 4.Link URL Expressions: @{...} : 定义url <!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a> #1.我们可以看出 用thymeleaf写url时可以把 http://localhost:8080/gtvg(项目名)省略掉, #2.本来地址中的请求参数?orderId=3 可以写为(orderId=${o.id})的形式 #3.如果有多个参数,可以 (execId=${execId},execType='FAST') <!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a> 5.Fragment Expressions: ~{...} : 片段引用表达式 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: _ All these features can be combined and nested:小例子
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1> Hello !!! </h1> <!--th:text 将div里面的内容设置为--> <div id="div01" class="mydiv" th:id="${hello}" th:class="${hello}" th:text="${hello}">这里显示欢迎信息</div> <!--th:属性名 将修改对应的属性值--> <hr/> <div th:text="${hello}"></div> <!--th:text 会转义特殊字符: 就是把本来的标签直接以字符串的形式输出来--> <div th:utext="${hello}"></div> <!--th:utext 不会转义, 就是说<h1>你不好</h1>中的<h1>会以html标签的效果展示出来--> <hr/> <!--th:each每次遍历都会生成当前这个标签--> <!--这里会生成四个 h4 标签--> <h4 th:text="${user}" th:each="user:${users}"></h4> <hr/> <h4> <!--这里会生成四个span标签--> <span th:each="user:${users}"> [[${user}]] </span> <!--直接在文本里写的话 [[...]] 相当于th:text [(...)]相当于th:utext --> </h4> </body> </html>Spring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. ()
自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何 渲染(转发?重定向?))
ContentNegotiatingViewResolver:组合所有的视图解析器的;
WebMvcAutoConfiguration类: @Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) //在没有名为"viewResolver",类型为ContentNegotiatingViewResolver的bean对象的情况下调用此方法 public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class)); // ContentNegotiatingViewResolver uses all the other view resolvers to locate // a view so it should have a high precedence resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; } public interface ViewResolver { /** 视图解析器接口只有一个方法, 就是根据名称解析出视图信息(一个视图对象View) 为了允许ViewResolver链接,ViewResolver应该 *如果未定义具有给定名称的视图,则返回{@code null}。 *一些ViewResolvers将总是尝试以给定的名称构建视图对象,就不返回{@code null} */ @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }补充: 在IDEA中查看一个方法在哪里被调用了:
选中要查的方法名
快捷键==(Ctrl + G)==(在idea快捷键被改成了eclipse的情况下)
或者是右键, 选择 Find Usages
ContentNegotiatingViewResolver类: implements ViewResolver @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); //获取候选的视图对象 View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); //选择一个最适合的视图对象 if (bestView != null) { return bestView; //然后将最适合的视图对象返回 } } String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : ""; if (this.useNotAcceptableStatusCode) { if (this.logger.isDebugEnabled()) { this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo); } return NOT_ACCEPTABLE_VIEW; } else { this.logger.debug("View remains unresolved" + mediaTypeInfo); return null; } } private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception { List<View> candidateViews = new ArrayList(); if (this.viewResolvers != null) { Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set"); Iterator var5 = this.viewResolvers.iterator(); //使用迭代器获取所有的视图解析器对象 while(var5.hasNext()) { ViewResolver viewResolver = (ViewResolver)var5.next(); View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } Iterator var8 = requestedMediaTypes.iterator(); while(var8.hasNext()) { MediaType requestedMediaType = (MediaType)var8.next(); List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); Iterator var11 = extensions.iterator(); while(var11.hasNext()) { String extension = (String)var11.next(); String viewNameWithExtension = viewName + '.' + extension; view = viewResolver.resolveViewName(viewNameWithExtension, locale); if (view != null) { candidateViews.add(view); } } } } } if (!CollectionUtils.isEmpty(this.defaultViews)) { candidateViews.addAll(this.defaultViews); } return candidateViews;//返回给List<View> candidateViews 作为候选视图解析器对象 } 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;说明:
根据上面WebMvcAutoConfiguration类 以及 ContentNegotiatingViewResolver类
可以大致推断出如果我们自己定义了一个类实现ViewResolver接口
并且将该类型的对象添加到容器中(即用@Bean注解),就可以实现自己的视图解析器了。
由下方的代码可以知道,ContentNegotiatingViewResolver类中的initServletContext方法会通过BeanFactory工具 从容器中获取所有的视图解析器,自然也包括我们自己创建的
@Override protected void initServletContext(ServletContext servletContext) { Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values(); //通过BeanFactory工具 从容器中获取所有的视图解析器 if (this.viewResolvers == null) { this.viewResolvers = new ArrayList<>(matchingBeans.size()); for (ViewResolver viewResolver : matchingBeans) { if (this != viewResolver) { this.viewResolvers.add(viewResolver); //把获得的视图解析器放到 //private List<ViewResolver> viewResolvers; 里了 } } } else { for (int i = 0; i < this.viewResolvers.size(); i++) { ViewResolver vr = this.viewResolvers.get(i); if (matchingBeans.contains(vr)) { continue; } String name = vr.getClass().getName() + i; obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name); } } AnnotationAwareOrderComparator.sort(this.viewResolvers); this.cnmFactoryBean.setServletContext(servletContext); } 验证我们的想法 @SpringBootApplication public class Springboot09TestthymeleafApplication { public static void main(String[] args) { SpringApplication.run(Springboot09TestthymeleafApplication.class, args); } public ViewResolver myViewResolver(){ return new MyViewResolver(); } public static class MyViewResolver implements ViewResolver{ @Override public View resolveViewName(String s, Locale locale) throws Exception { return null; } } }于是,我们要通过调试来确认DispatcherServlet 中的 doDispatch()方法 (因为所有请求一进来都会来到该方法)中用到的视图解析器是否包含我们自己定义的那个
可以看到我们自己定义的视图解析器也被调用了!!!
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径 /webjars
Static index.html support. 静态首页访问
Custom Favicon support (see below). favicon.ico
自动注册了 of Converter , GenericConverter , Formatter beans.
Converter:转换器; public String hello(User user);类型转换使用Converter 把页面发来的文本类型转换为对应的类型)Formatter 格式化器;2017.12.17(从页面传来的===Date;先把字符串转为日期类型,还得按照传来的格式。 @Bean @Override public FormattingConversionService mvcConversionService() { Format format = this.mvcProperties.getFormat(); // 通过 private final WebMvcProperties mvcProperties。中的getFormat()方法 获取格式化信息 WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); //Create a new WebConversionService that configures formatters with the provided date, time, and date-time formats, or registers the default if no custom format is provided //WebConversionService是用来设置格式化器的 return conversionService; } @ConfigurationProperties(prefix = "spring.mvc") public class WebMvcProperties { ... private final Format format = new Format(); @Deprecated /** 对@Deprecated 以及 @DeprecatedConfigurationProperty的补充说明: 1.这个注解表明当前属性被弃用了 2.弃用可以在代码中声明性地指定,方法是将@DeprecatedConfigurationProperty注释添加 到暴露不赞成使用的属性的getter中(即被@Deprecated标注了的) 3.例如下面的 spring.mvc.date‐format 被重命名为 spring.mvc.format.date */ @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date") //在文件中配置日期格式化的规则 //这个注解的意思就是说在properties文件中写日期格式的形式从过期的替换为spring.mvc.format.date //以前的是spring.mvc.date‐format public String getDateFormat() { return this.format.getDate(); } public Format getFormat() { return this.format;//注: 这个方法就是上一个代码块this.mvcProperties.getFormat();处调用的 //其实就是给WebMvcProperties中的format赋值 } 自己添加的格式化器转换器,我们只需要放在容器中即可 @Override public void addFormatters(FormatterRegistry registry) { ApplicationConversionService.addBeans(registry, this.beanFactory); //addFormatters()通过调用ApplicationConversionService里的addBeans()来添加格式化器 //参数为格式化器注册器 和 bean工厂(容器) //该方法由往上数第2个代码块中的addFormatters(conversionService);处调用 }补充说明:
在IDEA快捷键为eclipse时,点击某个方法 按住 Ctrl + Alt + b即可查看这个方法的具体实现方法 public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) { Set<Object> beans = new LinkedHashSet();//定义一个LinkedHashSet来存放组件(各种转换器) beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values()); //该方法是由 StaticListableBeanFactory 类(实现了ListableBeanFactory接口)来实现的 //用于获取所有实现了(。。。.class)的类并将它们添加到beans中去 beans.addAll(beanFactory.getBeansOfType(Converter.class).values()); beans.addAll(beanFactory.getBeansOfType(Printer.class).values()); beans.addAll(beanFactory.getBeansOfType(Parser.class).values()); Iterator var3 = beans.iterator(); while(var3.hasNext()) { Object bean = var3.next(); if (bean instanceof GenericConverter) { registry.addConverter((GenericConverter)bean); } else if (bean instanceof Converter) { registry.addConverter((Converter)bean); } else if (bean instanceof Formatter) { registry.addFormatter((Formatter)bean); } else if (bean instanceof Printer) { registry.addPrinter((Printer)bean); } else if (bean instanceof Parser) { registry.addParser((Parser)bean); } //如果各个类实例化了,就放到格式化器注册器中去 } }Support for HttpMessageConverters (see below).
HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中 (@Bean,@Component)Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则
Automatic use of a ConfigurableWebBindingInitializer bean (see below). 我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)
//初始化WebDataBinder; //请求数据=====JavaBean;org.springframework.boot.autoconfigure.web:web的所有自动场景;
//If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration //(interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type //WebMvcConfigurerAdapter , but without @EnableWebMvc . If you wish to provide custom instances of //RequestMappingHandlerMapping , RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver //you can declare a WebMvcRegistrationsAdapter instance providing such components. //If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with //@EnableWebMvc编写一个配置类(@Configuration),是WebMvcConfigurer类型;不能标注@EnableWebMvc;
(注:WebMvcConfigurerAdapter已经过时了,官方推荐用WebMvcConfigurer)
既保留了所有的自动配置,也能用我们扩展的配置;
//使用WebMvcConfigurer可以来扩展SpringMvc的功能 @Configuration public class MyMvcConfig implements WebMvcConfigurer{ /** * 当我们不需要往页面里传入什么数据的时候,只需要定义一个ViewControllers就可以了 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/zhuge请求 来到 index.html页面 registry.addViewController("/zhuge").setViewName("index"); } }原理:
WebMvcAutoConfiguration是SpringMVC的自动配置类在做其他自动配置时会导入; @Import(EnableWebMvcConfiguration.class) ,比如WebMvcAutoConfigurationAdapter @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();//下个代码块是实现类 //自动注入说明方法的参数便要从容器中获取,参数就是容器中所有的WebMvcConfigurer @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } //举一个DelegatingWebMvcConfiguration中的例子 @Override protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); } class WebMvcConfigurerComposite implements WebMvcConfigurer { private final List<WebMvcConfigurer> delegates = new ArrayList<>(); @Override public void addViewControllers(ViewControllerRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { //从这里可以看出该方法把容器中所有WebMvcConfigurer都拿来 delegate.addViewControllers(registry); //然后再把这些Configurer的addViewControllers方法都调用一遍 } } 。。。 容器中所有的WebMvcConfigurer都会一起起作用;我们的配置类也会被调用; 效果:SpringMVC的自动配置和我们的扩展配置都会起作用;SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了我们
需要在配置类中添加==@EnableWebMvc==即可;
补充说明:
@see : 可以在注释中实现链接跳转 //使用WebMvcConfigurer可以来扩展SpringMvc的功能 @EnableWebMvc @Configuration public class MyMvcConfig implements WebMvcConfigurer{ @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/zhuge请求 来到 index.html页面 registry.addViewController("/zhuge").setViewName("index"); } } /** 这个时候,我们连静态资源都访问不了了,因为SpringMVC的自动配置都失效了 */原理:
为什么有@EnableWebMvc自动配置就失效了?
@EnableWebMvc的核心 @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { } @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { 回头看下WebMvcAutoConfiguration @Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) //在容器中没有这个组件({WebMvcConfigurationSupport.class)的时候,这个自动配置类才生效 //然而当我们使用了@EnableWebMvc后,DelegatingWebMvcConfiguration类(WebMvcConfigurationSupport的子类在实例化时会先调用父类构造函数),所以,WebMvcAutoConfiguration就失效了 @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;模式:
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如 果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默 认的组合起来;在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置这样做的好处
当我们修改了项目访问的路径时 server.servlet.context-path=/zhuge th:href 和 th:src会自动将添加的路径补到对应的属性值中去 (以下为网页源代码) <!-- Bootstrap core CSS --> <link href="/zhuge/webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"> <!----> <!-- Custom styles for this template --> <link href="/zhuge/asserts/css/signin.css" rel="stylesheet"> <img class="mb-4" src="/zhuge/asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">步骤:
编写国际化配置文件,抽取页面需要显示的国际化消息 SpringBoot自动配置好了管理国际化资源文件的组件; @Conditional(ResourceBundleCondition.class) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; /** * 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";//该属性是在MessageSourceAutoConProperties类下面的 //我们的配置文件可以直接放在类路径下叫messages.properties;(但是我们是放在 i18n下的,所以需要自己指定一下) @Bean @ConfigurationProperties(prefix = "spring.messages") public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } @Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(properties.getBasename())) { //设置国际化资源文件的基础名(去掉语言国家代码的) messageSource.setBasenames(StringUtils .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; } 。。。 } spring.messages.basename=i18n.login #指定一下basename(国际化配置文件的路径)
去页面获取国际化的值;
补充说明:
在IDEA里,settings里的设置仅对当前项目生效,若要修改所有的,点other settings里的default settings如果中文时有乱码,就在设置file encoding里把转化为ascll码给勾上 <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> <!----> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <label class="sr-only" th:text="#{login.username}">Username</label> <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""> <label class="sr-only" th:text="#{login.password}">Password</label> <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me" > [[#{login.remember}]] <!--注意:这里不能使用th:text="#{login.remember}" 因为text是该标签体里面的内容,而input是用户在页面输入的内容,是字节数的,没有标签体 所以我们要用行内写法 --> </label> </div> <button class="btn btn-lg btn-primary btn-block" th:text="#{login.btn}" type="submit">Sign in</button>效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
@Bean @ConditionalOnMissingBean//这个注解的意思是如果springmvc容器中有了LocaleResolver组件,该类就不会被使用 @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { //这里指的是如果有设置好的LocaleResolver.FIXED,就用它 return new FixedLocaleResolver(this.mvcProperties.getLocale()); } //否则就用这个AcceptHeaderLocaleResolver的对象() AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } AcceptHeaderLocaleResolver类: @Override public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { //默认的就是根据请求头带来的区域信息获取Locale进行国际化 return defaultLocale; } Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = getSupportedLocales(); if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) { return requestLocale; } Locale supportedLocale = findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } return (defaultLocale != null ? defaultLocale : requestLocale); }这里是网页中的信息
点击链接切换国际化(我们要实现自己的LocaleResolver组件) /** * @author ZCH * @date 2020/10/2 0002 - 下午 9:19 * * 可以在页面的链接上携带区域信息 */ public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); //从请求头中接收名为l的属性的值 Locale locale = Locale.getDefault(); //如果l没有值,最后就还是用浏览器默认的 if (!StringUtils.isEmpty(l)){ String[] split = l.split("_"); // “_“ 的左右两边分别是 语言代码 和 国家代码 locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } } MyMvcConfig类: @Bean//将我们的区域解析器放到springmvc容器中去(注:不能直接在MyLocaleResolver类上加@Bean) public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }开发期间模板引擎页面修改以后,要实时生效
禁用模板引擎的缓存 # 禁用缓存 spring.thymeleaf.cache=false 页面修改完成以后ctrl+f9:重新编译; <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> 代码实现补充说明
重复提交: 多次提交表单中的数据(这里只说一种情况,就是提交完表单后,不做其他操作直接刷新页面,会提交多次表单)根本原因: Servlet处理完请求后, 直接转发到目标页面, 这样整个业务只发送了一次请求,点击刷新是会一直刷新之前的请求解决办法: 不用转发到另一个页面, 采用重定向的方式跳转到目标页面危害: 数据库可能会多次保存相同的数据安全问题,如多次支付等服务器性能受损 @Controller public class LoginController { @PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, //@RequestParam("username") 用于与表单中name为username的input标签传来的参数对应 @RequestParam("password") String password, Map<String,Object> map){ if (!StringUtils.isEmpty(username) && "123456".equals(password)) { //登陆成功,防止表单重复提交,可以重定向到这个主页 return "redirect:/main.html"; }else { //登录失败 map.put("msg","用户名或密码错误"); return "login"; } } }登陆错误消息的显示
<!--判断--> <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>拦截器进行登录检查(没登录的不能访问后台)
public class LoginHandlerInterceptor implements HandlerInterceptor { //目标方法执行之前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser");//没有进行过登录操作,处理器方法就没在session里添加"loginUser"(key),所以user就会为null if (user == null){ //未登录, 返回登录页面 request.setAttribute("msg","没有权限请先登录"); request.getRequestDispatcher("/index.html").forward(request,response); return false; }else { //已登录,放行请求 return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }注册拦截器
@Bean public WebMvcConfigurer webMvcConfigurer(){ WebMvcConfigurer configurer = new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); registry.addViewController("/main.html").setViewName("dashboard"); } //注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //静态资源: css,js //springboot已经做好了静态资源映射(但2.x又没了) registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**") // "/**" 表示所有的地址都被拦截 .excludePathPatterns("/index.html","/","/user/login","/webjars/bootstrap/**","/asserts/**"); //排除掉不用拦截的请求 /* 注: springboot2.x依赖的 spring 5.x版本, 针对资源的拦截器初始化时有区别, 具体源码在WebMvcConfigurationSupport中,所以我们要手动把静态资源的请求路径添上 */ } }; return configurer; }将登陆进去后的页面左上角改成当前用户的名字
if (!StringUtils.isEmpty(username) && "123456".equals(password)) { //登陆成功,防止表单重复提交,可以重定向到这个主页 session.setAttribute("loginUser",username); return "redirect:/main.html"; } //其余代码见登录中的 LoginController类 // session.setAttribute("loginUser",username);的作用是让后台左上角显示当前用户名 <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a> <!--将登陆进去后的页面左上角改成当前用户的名字-->实验要求:
RestfulCRUD:CRUD满足Rest风格; URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作)RestfulCRUD查询getEmpemp—GET添加addEmp?xxxemp—POST修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT删除deleteEmp?id=1emp/{id}—DELETE 实验的请求架构; 实验功能请求URI请求方式查询所有员工empsGET查询某个员工(来到修改页面)emp/1GET来到添加页面empGET添加员工empPOST来到修改页面(查出员工进行信息回显)emp/1GET修改员工empPUT删除员工emp/1DELETE 2. 处理器方法用于查询所有员工返回列表页面
//查询所有员工返回列表页面 @GetMapping("/emps") public String list(Model model){ Collection<Employee> employees = employeeDao.getAll(); //放在请求域中 model.addAttribute("emps",employees); //thymeleaf默认就会拼串 //"classpath:/templates/xxxx.html" return "emp/list"; }添加员工时很容易发生的一个问题
提交的数据格式不对:生日:日期; 2017-12-12;2017/12/12;2017.12.12;日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型; 2017-12-12—Date; 类型转换,格式化; 默认日期是按照 / 的方式;其实这个问题在前面 Spring MVC auto-configuration 里有提到过 DateTimeFormatters类: public DateTimeFormatters dateFormat(String pattern) { if (isIso(pattern)) { this.dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE; this.datePattern = "yyyy-MM-dd"; //可以看到我们默认的日期格式 } else { this.dateFormatter = formatter(pattern); this.datePattern = pattern; } return this; } WebMvcAutoConfiguration类: @Bean @Override public FormattingConversionService mvcConversionService() { Format format = this.mvcProperties.getFormat();//通过mvcProperties获取格式化信息,所以我们点到getFormat里去 WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; } WebMvcProperties类: @ConfigurationProperties(prefix = "spring.mvc") public class WebMvcProperties { private final Format format = new Format(); 。。。 public Format getFormat() { return this.format; } 。。。 public static class Format { /** * Date format to use, for example `dd/MM/yyyy`. */ private String date; 。。。 } /* 我们可以直接在springboot的配置文件里 用spring.mvc.(WebMvcProperties里的成员变量) 来修改默认的配置 */ spring.mvc.format.date=yyyy-MM-dd因为是修改添加二合一的页面(add),所以要处理好如何判断到底是哪个
==${emp!=null}?==是判断的关键语句(三元运算)
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <!--需要区分是添加还是修改--> <form th:action="@{/emp}" method="post"> <!--发送put请求修改员工数据--> <!-- 1、SpringMvc中配置HiddenHttpMethodFilter;(Springboot已经自动配置好了) 2、页面创建一个post表单 3、创建一个input项, name="_method";值就是我们指定的请求方式 --> <input type="hidden" name="_method" value="put" th:if="${emp!=null}"> <div class="form-group"> <label>LastName</label> <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}"> </div> <div class="form-group"> <label>Email</label> <input name="email" type="email" class="form-control" placeholder="3272548251@qq.com" th:value="${emp!=null}?${emp.email}"> </div> <div class="form-group"> <label>Gender</label> <div class="form-check form-check-inline"> <input type="radio" class="form-check-input" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input type="radio" class="form-check-input" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <!--提交的是部门的id--> <select class="form-control" name="department.id"> <option th:selected="${emp!=null}?${dept.id == emp.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"> </div> <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button> </form> 写处理修改请求(保存)的处理器方法 //员工修改:需要提交员工id @PutMapping("/emp") public String updateEmployee(Employee employee){ System.out.println("修改的员工数据:"+employee); employeeDao.save(employee); return "redirect:/emps"; } <input type="hidden" name="_method" value="put" th:if="${emp!=null}"> <!--其余代码见上面第二个代码块--> <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"> <!--这个标签用于修改时回显员工id-->