1、SpringBoot
回顾下什么是Spring?
Spring是一个开源框架,2003年兴起的一个轻量级的java开发框架,是为了解决企业级应用开发的复杂性而创建的,简化开发
Spring是如何简化Java开发的?
为了降低java开发的复杂性,Spring采用了以下4中关键策略:
- 基于POJO的轻量级和最小侵入性编程;
- 通过IOC,依赖注入(DI)和面向接口实现松耦合
- 基于切面(AOP)和管理进行声明式编程;
- 通过切面和模板减少代码;
什么是SpringBoot?
学习过javaweb的同学就知道,开发一个web应用,从最初的servlt+tomcat跑一个helloworld都要经历一些很复杂的步骤;后来就用了框架Struts,再到SpringMVC,最后到现在的SpringBoot,过一两年可能又到其他的web框架;目前SpringBoot在当下是非常火的,那什么是SpringBoot呢?就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置,能迅速的开发web应用,几行代码开发一个http接口;
java企业级应用->J2EE->Spring->SpringBoot的过程
Springboot的核心思想:约定大于配置
Springboot的主要优点:
Springboot的主要优点:
-
为所有Spring开发者更快的入门
-
开箱即用,提供各种默认配置来简化项目配置
-
内嵌式容器简化web项目
-
没有冗余代码生成和xml配置的要求
2、微服务
什么是微服务?
微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合,可以通过http的方式进行互通,要说微服务架构,先得说说过去我们的单体应用架构。
单体应用架构
所谓单体应用架构(all in one)是指,我们将一个应用中的所有服务都封装成一个应用中。
无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问等等各个功能放到一个war包内
- 这样做的好处是,易于开发和测试,也十分方便部署,当需要扩展时,只需要将war包复制多份,然后放到多个服务器上,再做负载均衡就可以了。
- 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包,部署这个应用war包,特别是对于一个大型应用,我们不可能把所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。
微服务架构
所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来,把独立出来的功能元素动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素,所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。这样的好处是:
- 节省了调用资源
- 每个功能元素的服务都是一个可替换的,可独立升级的软件代码
如何构建微服务?
一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元素就是一个功能元素,他们各自完成自己的功能,然后通过http相互请求调用,比如一个电话系统,查缓存,连接数据库,浏览页面,结账,支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务功能构建了一个庞大的系统,如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
但是这种庞大的系统架构给部署和运维带来很大的难度,于是,spring为我们带来了构建大型分布式微服务的全套、全程产品
- 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用。
- 大型分布式网络服务的调用跟,这部分由spring cloud来完成,实现分布式
- 在分布式中间,进行流式数据计算,批处理,我们有spring cloud data flow
- spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案
3、第一个SpringBoot程序
方式一:
可通过官网快速创建一个项目,然后再使用idea导入项目即可
https://start.spring.io/
方式二:
通过idea直接创建一个SpringBoot项目
创建完之后,直接写一个controller来测试即可
package com.ryan.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
可以修改banner:在resources目录中新建一个banner.text,把想要的banner写进去即可,
获取banner网站推荐:https://www.bootschool.net/ascii
体验了第一个springboot程序之后↓↓↓↓
4、原理初探
为什么会这么简单呢?因为springboot中间帮我们做了很多事情,我们来看看一下
4.1、pom.xml
- spring-boot-starter-parent=》spring-boot-starter-parent:核心依赖都在父工程中
- 我们在写或者引入一些Springboot依赖的时候,不需要指定版本号,就是因为这些版本仓库
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 启动器说白了就是Springboot的启动场景
- 比如上面这个spring-boot-starter-web,他会帮我妈自动导入web环境所有的依赖
- springboot会将所有的功能场景,都变成一个个的启动器
- 我们需要使用什么功能,就只需要找到对应的启动器就可以了
- 启动器有很多,我们可以通过官网文档可以看到:
https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/using-spring-boot.html#using-boot-starter
4.2、主程序(重点)
package com.ryan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloApplication {
//将springboot应用启动
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloApplication.class, args);
}
}
这个程序看起来很简单, 但是里面的注解和启动程序并不简单啊
我们先来分析这个注解都干了什么!!
@SpringBootApplication
作用:标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
进入这个注解,可以看到还有很多其他注解!
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...}
我们来看核心的几个注解即可
@ComponentScan
这个注解在Spring中很重要,他对应XML配置中的元素
作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类
我们继续进去这个注解查看
@Configuration
public @interface SpringBootConfiguration {
}
这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
继续点
@Component
public @interface Configuration {
}
里面的@Component这就说明,启动类本身也是Spring中的一个组件,负责启动应用!
到这里,我们再回到SpringApplication注解中继续看另外一个重要注解
@EnableAutoConfiguration
@EnableAutoConfiguration:开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我妈配置;
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效,我们点进去注解继续看看这个注解都干了些什么
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
首先是@AutoConfigurationPackage:自动配置包
点进这个注解看看
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@Import:Spring底层注解@Import,给容器导入一个组件
Registrar.class作用:将主启动类的所在包及包下所有子包里面的所有组件扫描到Spring容器
这个分析完了,我们退回上一步继续看另一个注解
@Import(AutoConfigurationImportSelector.class):给容器导入组件
AutoConfigurationImportSelector:自动配置导入选择器,那么他会导入哪些组件的选择器呢?我们点击进去这个类看源码:
- 这个类中有一个这样的方法
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
- 这个方法又调用了 SpringFactoriesLoader 类的静态方法,我们进入 SpringFactoriesLoader 类的静态方法类的loadFactoryNames() 方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//这里它又调用了 loadSpringFactories 方法
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
- 我们继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//去获取一个资源 "META-INF/spring.factories"
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//将读取到的资源遍历,封装成为一个Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
- 发现一个多次出现的文件,spring.factories,全局搜索它
Spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
WebMvcAutoConfiguration
我们在上面的自动配置类随便找一个打开看看,比如WebMvcAutoConfiguration
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 他会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件
- 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作
现在应该大概的了解了下,SpringBoot的运行原理,后面我们还会深化一次
run方法
略
5、SpringBoot配置
配置文件
SpringBoot使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yaml(官方推荐)
- 语法结构:key:空格value
配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了。
YAML
百度百科:YAML是"YAML Ain’t a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
标记语言
我们来对比一下xml和yaml
yaml配置:
server:
port: 8080
xml配置:
<server>
<port>8080</port>
</server>
yaml语法
基础语法:
k: (空格)v
以此来表示一对键值对(空格不能省略);以空格的缩进来控制层级关系,只要左边对齐的一系列数据都是同一个层级的。
注意属性和值的大小写都是十分敏感的
值得写法
字面量: 普通的值(数字,布尔值,字符串)
k: v
字面量直接写在后面就可以,字符串默认不用加上双引号或者单引号
""双引号,不会转义字符串里面的特殊字符,特殊字符作为本身想要表示的意思;
比如name: “kuang \n shen” 输出:kuang 换行 shen
对象、map集合:比如一个student对象,有name和age属性
#对象
student:
name: ryan
age: 3
#行内写法(注意对象冒号的空格也不能省略)
student: {
name: ryan,age: 8}
数组,类似markdown语法的列表语法:-空格
pets:
- cat
- dog
- bird
#行内写法
pet: [cat,dog,bird]
一般掌握以上三种就差不多够用了
6、给属性赋值的几种方式
方式一:使用原来的@Value注解
@Component
public class Dog {
//给实体类属性赋值
//方式一:使用@Value注解
@Value("旺财,你死的好惨啊")
private String name;
@Value("2")
private Integer age;
}
测试
@SpringBootTest
class Springboot02YamlApplicationTests {
//别忘了自动装配,而这里自动装配的前提是实体类中使用@Component等注解
@Autowired
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);//Dog{name='旺财,你死的好惨啊', age=2}
}
}
方式二:使用配置文件:application.yaml
@Component//注册bean
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类的所有属性和配置文件中相关的配置进行绑定;
参数prefix + "person":将配置文件中的person下面的所有属性一一对应
只有这个组件是容器的组件,才能使容器提供的@@ConfigurationProperties功能
*/
@ConfigurationProperties(prefix = "dog")
public class Dog {
private String name;
private Integer age;
}
application.yaml
# 方式二:使用yaml配置文件的方式来实现给实体类属性赋值
# 不过前提是你得在实体类中使用注解:@ConfigurationProperties(prefix = "dog")
dog:
name: "大家好,我是旺财"
age: 3
测试:略
再来试试复杂的一点的实体类
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private boolean sex;
private Date birth;
private List<Object> list;
private Map<String, Object> map;
private Dog dog;
}
application.yaml
# 复杂一点的实体类我,注意一些对象或者集合或者数组的写法
person:
name: 鸡你太美
age: 10
sex: false
birth: 2020/02/20
list:
- 唱
- 跳
- rap
map: {
k1: v1,k2: v2}
dog:
name: 柯基
age: 0
测试:
@SpringBootTest
class Springboot02YamlApplicationTests {
//别忘了自动装配,而这里自动装配的前提是实体类中使用@Component等注解
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
/*
Person{name='鸡你太美',
age=10, sex=false,
birth=Thu Feb 20 00:00:00 CST 2020,
list=[唱, 跳, rap],
map={k1=v1, k2=v2},
dog=Dog{name='柯基', age=0}}
*/
}
}
注意:
- 实体类别忘了加上@Component注解,否则无法被扫描到,测试的时候,对象别忘了加上自动装配注解@Autowired
- 使用yaml的时候,别忘了在实体类中添加一个注解,并加上前缀(对象名称):@ConfigurationProperties(prefix = “person”)
对比yaml配置和@Value,发现yaml还是挺强大的
- cp只需要写一次即可,value则需要每个字段都添加
- 松散绑定:这个什么意思呢?比如我的yaml中写last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的,这就是松散绑定
- JSR303数据校验,这个就是我们可以在字段增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yaml中可以封装对象,使用@Value就不支持
方式三:properties配置
先在实体类中添加注解@PropertySource(“classpath:xxx.properties”),属性使用SPEL表达式取出配置文件的值
@PropertySource("classpath:xxx.properties")
public class Person {
//SPEL表达式取出配置文件的值
@Value("${name}")
private String name;
private Integer age;
private boolean sex;
private Date birth;
private List<Object> list;
private Map<String, Object> map;
private Dog dog;
}
在properties配置文件中写键值对即可
name=ryan
测试:略
注意:如果想要使用properties的话,记得在idea中设置properties的编码格式为utf-8,不然会乱码
结论:
- 配置yaml和配置properties都可以获取到值,强烈推荐yaml
- 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下@Value
- 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@ConfigurationProperties,不要犹豫!
7、JSR303数据校验
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。
在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。
使用:
-
启动validation自动装配(导入依赖)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
结合参数给对象添加校验注解
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class User {
private Integer id;
@NotBlank(message = "用户名不能为空")
private String username;
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
private String password;
@Email
private String email;
private Integer gender;
}
具体怎么去校验,后面再去学
8、配置文件路径
SpringBoot启动会扫描以下位置的application.properties或者application.yaml文件作为springboot的默认配置文件
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到第排列,高优先级的配置会覆盖低优先级的配置
再强调一下,强烈推荐使用yaml去配置
如果有多个环境,可以这样
# 如果不同环境需要用到不同的端口,我们可以写多个端口出来,并使用---来分隔开
server:
port: 8080
# 然后通过设置使用哪个端口
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: test
9、自动装配原理再理解
思考:配置文件中到底能写什么?怎么写?
从springboot官方文档中可以看到大量的配置, 我们不可能真的去背他的,要去了解他的一些规律和原理
以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({
HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = 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 Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//..............
}
一句话总总结:根据当前不同的条件判断,决定这个配置类是否生效!
- 一旦这个配置类生效,这个配置类就会给容器添加各种组件
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
- 所有在配置文件中能配置的属性都是在xxxProperties类封装着的
- 配置文件能配置什么就可以参照某个功能对应的这个属性类
例如随便点进一个JdbcTemplateAutoConfiguration
在配置文件能写的也就是上面的一些属性
这就是自动装配的原理
精髓:
- springboot启动会加载大量的自动配置类
- 我们看我们需要的功能有没有在SpringBoot默认写好的自动装配类当中
- 我们再来看这个自动装配类中到底配置了哪些组件(只要我们要用的组件存在其中,我们就需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
xxxAutoConfiguration:自动配置类;给容器添加组件
xxxProperties:封装配置文件中相关属性
了解:@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
(没有条件的类)**
10、SpringBoot web开发
说到web开发,我们在springboot要思考一下如何处理以下问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化
从webmvcConfiguration源码中可以获取到一些如何以上问题的方法
10.1、静态资源处理
-
webjars
localhost:8080/webjars
(极少使用) -
classpath(resources)目录下的
- public, static, /**, resources
localhost:8080/
优先级:resources>static(默认)>public
- public, static, /**, resources
10.2、首页如何定制
在静态资源目录下新建一个index.html即可
10.3、thymeleaf
使用thymeleaf之前,我们要知道templates目录下的资源只能通过controller去访问
简单使用:
- 先导入相关依赖,可以直接开启一个启动器,或者直接导入相关依赖
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 编写一个controller
@Controller
public class TestController {
@RequestMapping("/t1")
public String test(Model model){
model.addAttribute("msg", "hello,thymeleaf");
return "test";
}
}
- 在templates目录下编写一个test.html,加入thymeleaf约束,并通过thymeleaf语法获取值
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>
如何通过thymeleaf表达式获取前端返回的值,先了解两种
- 普通值
- 集合遍历
@Controller
public class TestController {
@RequestMapping("/t1")
public String test(Model model){
model.addAttribute("msg", "<h1>hello,thymeleaf</h1>");
model.addAttribute("users", Arrays.asList("ryan","kobe"));
return "test";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<!--遍历:使用each,将users集合取出元素为user,然后再遍历每个元素的值-->
<div th:each="user:${users}" th:text="${user}"></div>
</body>
</html>
10.4、webmvc自动配置原理
在写项目之前,我们先了解一下webmvc的自动配置原理以及怎么去扩展webmvc
原理:可通过源码分析,此处略
扩展:
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;
package com.ryan.config;
import com.ryan.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//项目的首页定制,添加一个视图解析器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//通过以下访问路径可以访问到index.html页面,
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
//登陆后不希望在地址栏上显示相关信息,可自定义一个地址,登陆时重定向到此即可
registry.addViewController("/main.html").setViewName("dashboard");
}
//将自定义的国际化组件放到容器中
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
//拦截器配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求,除了登陆页面,静态资源后面再继续完善配置
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**").
excludePathPatterns("/index.html", "/user/login","/","/css/**","/js/**","/img/**");
}
}
- 实体类
package com.ryan.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
package com.ryan.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String name;
private String email;
private Integer gender;//1表示男生,0表示女生
private Department department;
private Date birth;
public Employee(Integer id, String name, String email, Integer gender, Department department) {
this.id = id;
this.name = name;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();
}
}
- dao+伪造数据库
package com.ryan.dao;
import com.ryan.pojo.Department;
import com.ryan.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
@Autowired
private DepartmentDao departmentDao;
private static Map<Integer, Employee> employees = new HashMap<>();
static{
employees.put(1001,new Employee(1001,"小王", "wang@qq.com",1,new Department(101, "教学部")));
employees.put(1002,new Employee(1002,"小红", "wang@qq.com",0,new Department(102, "教研部")));
employees.put(1003,new Employee(1003,"小明", "wang@qq.com",1,new Department(103, "市场部")));
employees.put(1004,new Employee(1004,"小花", "wang@qq.com",0,new Department(104, "后勤部")));
employees.put(1005,new Employee(1005,"小张", "wang@qq.com",1,new Department(105, "技术部")));
}
//CRUD
//增加一个员工
private static Integer employeeId = 1006;
public void save(Employee employee){
if (employee.getId() == null){
employee.setId(employeeId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//删除
public void deleteEmployeeById(Integer id){
employees.remove(id);
}
//查询所有员工
public Collection<Employee> getAllEmployee(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
}
package com.ryan.dao;
import com.ryan.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class DepartmentDao {
//使用java模拟一个数据库
private static Map<Integer, Department> departments = new HashMap<>();
static{
departments.put(101,new Department(101, "教学部"));
departments.put(102,new Department(102, "教研部"));
departments.put(103,new Department(103, "市场部"));
departments.put(104,new Department(104, "后勤部"));
departments.put(105,new Department(105, "技术部"));
}
//CRUD
//查询所有部门
public Collection<Department> getAllDepartment(){
return departments.values();
}
//通过id查询所在部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
-
前端模板
- 对于公共部分(导航栏+侧边栏),可抽出来代码复用
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <!-- Bootstrap core CSS --> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link th:href="@{/css/dashboard.css}" rel="stylesheet"> </head>