微服务
微服务是一种架构风格,由于单体架构不利于团队协作完成并且代码量较大,后期维护成本较高,逐渐有了微服务架构。微服务是将一个项目拆分成不同的服务,各个服务之间相互独立互不影响,互相通过轻量级机制通信比如http通信。各个服务模块可以看成是一个单独的项目,可以由不同的编程语言,不同的数据存储技术进行开发,从而有效的实现“松耦合”。**微服务的目的就在于拆分应用、模块独立开发和部署。**相比于单体架构,微服务最大的优势就是实现了拆分应用、统一管理。
什么是springBoot
SpringBoot是一款基于Spring的快速开发框架,使用springBoot可以快速开发spring应用
springBoot为了简化使用ssm的大量复杂的配置文件,因此它内置了大量的市面主流组件,而任何一个springBoot内置的组件都对应有一个配置类。比如模板引擎thymeleaf对应的配置类ThymeleafProperties。并且我们依然可以在此基础上继续扩展或者进行其他配置,可以通过application.yml或者application.properties进行配置
SpringBoot内部集成了TomCat,不需要额外的配置,启动主程序即可运行
搭建SpringBoot
Spring内置了大量的第三方组件供我们使用,我们不再需要单独的去导入这些组件的依赖,这些组件都位于spring-boot-starter-xxx中可以理解为是一个启动器。并且spring也提供了这些组件的各个参数,来实现自定义组件开发,spring会根据我们的配置文件以及所导入的启动器来进行自动装配,自动装配是spring的核心所在
自动装配基于启动器,启动器配置依赖在Pom.xml文件中,比如
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
将需要使用的依赖项配置即可,官网示例
下面是spring提供的主程序,也就是整个项目的入口。因为spring已经内置了一些服务器组件比如tomcat,我们从主程序启动就会由spring来完成服务器的项目部署以及运行。
@SpringBootApplication
public class PpplllApplication {
public static void main(String[] args) {
SpringApplication.run(PpplllApplication.class, args);
}
}
spring自动装配原理
@SpringBootApplication注解,其中包含
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 上述四个是定义注解需要 核心注解是下面三个
@SpringBootConfiguration // SpringBoot配置
@EnableAutoConfiguration // 自动配置
@ComponentScan( // 扫描包
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
可以看到@SpringBootApplication是一个符合注解,也就是说如果直接定义@SprngBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解也可以启动项目
@SpringBootConfigUration的内部其实是一个@Configuration注解,其作用就是将该注解所修饰的当前类作为配置类并且交给Spring容器托管,那么spring在扫描配置时便会扫描到该类,配置即可生效
@ComponentScan扫描包,相当于在spring的application-context.xml文件中去配置了conext:component-scan
,如果不配置路径就会扫描当前所在包及其子包中的所有注解。目的就是让@Controller、@Service等注解生效
@EnableAutoConfiguration是实现自动装配的关键,它包含两个注解分别是@Import和@AutoConfigurationPackage
@Import引入了一个类AutoConfigurationImportSelector.class
该类是完成自动装配的重点:
- 先看该类下的selectImports方法,
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断注解的数据是否存在
if (!this.isEnabled(annotationMetadata)) {
// 没有数据九返回 No-IMPORTS一个空的String数组
return NO_IMPORTS;
} else {
// 否则将注解数据封装为自动配置的实体对象
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return
// 获取注解中的全部数据并转换成String数组 返回
StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
- 点开封装注解数据的方法getAutoConfigurationEntry(annotationMetadata)
- getCandidateConfigurations方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
可以发现当中给出了错误提示:根据@EnableConfiguration的全限定名去META-INF/spring.factories文件中获取我们需要导入的类,
@EnableConfiguration全限定名为:
org.springframework.boot.autoconfigure.EnableAutoConfiguration
最终会找到文件spring.factories,也就是大量需要自动装配的类
@AutoConfigurationPackage就是将主程序所在的包作为自动配置的包,spring默认会扫描本包及子包下的注解
@ConfigurationProperties注解可以将该配置类中的每一个属性值映射到yml文件当中进行绑定 该注解提供prefix字段用于指定yml同名的属性,我们可以使用该注解指定配置类
松散绑定:yml中的属性字段可以使用-进行风格,则其对应配置类中的属性就是连接-前后的字段并改为驼峰命名的属性。
例如:yml文件中:student-count对应student类中的studentCount属性,与mybatis中的驼峰命名映射类似
yml语法:
# 以key-value形式存储 key不能重复
server:
port: 8081
# 对象 行内写法
student: { name: yuqu,age: 13 }
# 对象
student2:
name: yuqu
age: 13
# 数组
students:
- 张三
- 李四
- 王五
进行配置时,你可以使用@Value注解直接为某字段赋值,也可以使用properties文件进行赋值,但是两者都没有yml更加灵活
JSR303校验
@Validatad数据校验注解,放在配置类的声明上,放置后则可以在该类下的字段上进行其他具体的校验配置,比如邮箱@Email()则该字段必须符合邮箱格式,否则报错,记得要添加JSR303校验的启动器,就是依赖
<!-- JSR303数据校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Validated
public class Person {
@Email()
private String name;
常用HSR303注解
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber 信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
SpringBoot核心:自动配置
导入静态资源
-
可以使用下面方式处理静态资源
- webjars
- public
- static
- /* *
- resource
-
这些是spring规定的默认扫描静态资源的位置,优先级为
static > public > resource
定制首页:
默认情况下spring会在静态资源中寻找名叫index.html的页面作为首页
在templates下面的所有页面只能通过controller进行跳转,但需要模板引擎的支持
thymeleaf模板引擎
导入依赖
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
之前在使用springmvc开发时,依然是使用jsp视图与后端进行数据交互。但是spring并不推崇使用jsp,因为jsp的前后端耦合性过高。spring推荐使用html,只负责前端视图的展示,而数据交互就使用模板引擎比如上面提到的thymeleaf。简单来说就是之前我们使用servlet+jsp 换成了现在的servlet+thymeleaf+html
- 所有的模板引擎卸载templates包下
- 必须是以.html后缀结尾
- 引入命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
示例:
@Controller
// 自动交由thymeleaf解析,并跳转至templates下的hello页面
// 注意如果没有配置thymeleaf依赖是无法跳转的
public class controller {
@RequestMapping(value = "/hello")
public String hello(){
return "hello";
}
}
由此,我们不需要再去配置springmvc的视图解析器并配置指定包下的.jsp文件后缀,全部交由模板引擎thymeleaf可以正常完成跳转
一些常用的thymeleaf语法:
<!--th:text 设置当前元素的文本内容,常用,优先级不高-->
<p th:text="${thText}" />
<!--th:value 设置当前元素的value值,常用,优先级仅比th:text高-->
<input type="text" th:value="${thValue}" />
<!-- 遍历数组 -->
<div>
<p th:each="user: ${users}" th:text="${user}"></p>
</div>
<!--th:if 条件判断,类似的有th:switch,th:case,优先级仅次于th:each, 其中#strings是变量表达式的内置方法-->
<p th:text="${thIf}" th:if="${not #strings.isEmpty(thIf)}"></p>
<!-- 使用format函数指定格式 -->
th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"
<!-- restFul风格传参 -->
th:href="@{/delete/}+${emp.getId()}"
<!-- js函数传参 -->
th:οnclick="update([[${emp.getId()}]])"
fragment抽取公共页面
在需要抽离的html中的最大标签定义th:fragment="sidebar"
,名称可以自定义,在需要引入的地方插入th:insert="~{dashboard::sidebar}"
,对应dashboard就是页面名称,后边为自定义字段
自动配置SpringMVC
自动转换器:前端提交的数据,经自动转换器自动封装为后台接收的对象
视图解析器、静态资源支持等等这些是SpringBoot已经内置好的springmvc的特性,我们依然可以通过手动去继续扩展其他配置比如拦截器、格式化器等等
自定义配置类,实现WebMvcConfigurer接口,利用@Configuration注解修饰,而且不能标注@EnableWebMvc。以自定义视图解析器DispatcherServlet为例:
@Configuration
public class MyConfig implements WebMvcConfigurer {
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
}
排错关于静态资源加载
针对thymeleaf加载静态资源时出现No mapping for GET /css/bootstrap.min.css
等一系列资源找不到的问题,原因是利用@EnableWebMvc注解修饰了配置类,导致springBoot无法根据默认的静态资源位置进行扫描所以找不到。解决方法两种:
- 取出@EnableWebMvc注解
- 实现WebMvcConfigurer接口之后重新定义CLASSPATH_RESOURCE_LOCATIONS静态资源路径和资源处理器,如下
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
// 资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!registry.hasMappingForPattern("/webjars/**")) {
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
}
if (!registry.hasMappingForPattern("/**")) {
registry.addResourceHandler("/**").addResourceLocations(
CLASSPATH_RESOURCE_LOCATIONS);
}
国际化消息转换LocaleResolver(区域设置解析)
配置i18n文件:
分别对应英文和中文,配置完成后修改前端界面的具体字段,以“请 登 录”为例:
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.login}">Please sign in</h1>
也可以继续进行扩展,利用a标签实现点击切换中英文对照,如下:
<a class="btn btn-sm" th:href="@{/index.html(lang='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(lang='en_US')}">English</a>
配置地区分解组件,并将其注入到配置类中托管给spring
// 地区分解 视图中英文调换
public class MyLocaleResolve implements LocaleResolver {
// 解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 获取请求的语言参数
String lang = request.getParameter("lang");
System.out.println("获取到视图传回的lang="+lang);
// 默认语言参数
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(lang)){
// 不为空 使用请求的语言
String[] s = lang.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
注入@Configuration配置类中
// 自定义地区分解组件注入 生效
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolve();
}
yml配置:
spring:
# 关闭thymeleaf缓存
thymeleaf:
cache: false
# 配置中英转换
messages:
basename: i18n.login
拦截器
设置未登录用户权限,拦截请求
- 自定义登录拦截器 LoginHandlerInterceptor实现HandlerInterceptor接口并重写preHandle方法,根据用户是否登录和请求url进行判断,
// 登录拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
// 配置拦截器 请求拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 查看当前用户是否已经登录
Object currentUser = request.getSession().getAttribute("currentUser");
if (currentUser==null){
request.setAttribute("message","没有权限,请先登录!");
// 没有登录返回登陆界面
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
- 向配置类中注入自定义的拦截器
// 注入自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/*","/js/*","/img/*");
}