SpringBoot2
课前先知
1、session
定义:
当浏览器第一次访问服务器时,服务器创建一个session对象(该 对象有一个唯一的id,一般称之为sessionId),服务器会将sessionId 以cookie的方式发送给浏览器。 当浏览器再次访问服务器时,会将sessionId发送过来,服务器依据 sessionId就可以找到对应的session对象。
获取:
方式一 HttpSession s = request.getSession(boolean flag);
方式二 HttpSession s = request.getSession();
常用方法:
绑订数据:
session.setAttribute(String name,Object obj);
依据绑订名获得绑订值:
Object session.getAttribute(String name);
注:如果绑订名对应的值不存在,返回null。
解除绑订:
session.removeAttribute(String name);
Session 的销毁
-
超时自动销毁。
-
从用户最后一次访问网站开始,超过一定时间后,服务器自动销毁Session,以及保存在Session中的数据。
-
Tomcat 服务器默认的Session超时时间是30分钟
-
可以利用web.xml设置超时时间单位是分钟,设置为0表示不销毁。
20
-
-
调用API方法,主动销毁Session
- session.invalidate()
2、常见错误
400 一般是请求参数传递错误或请求方式错误。
3、过滤器和拦截器的区别
- 出生不同
- 过滤器来自于 Servlet。
- 拦截器来自于 Spring 框架。
- 触发时机不同
- 请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller)。
- 过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法。
- 实现不同
- 过滤器是基于方法回调实现的,当我们要执行下一个过滤器或下一个流程时,需要调用 FilterChain 对象的 doFilter 方法进行回调执行
- 拦截器是基于动态代理(底层是反射)实现的。
- 支持的项目类型不同
- 过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中。
- 拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中。
- 使用的场景不同
- 因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务。
- 过滤器通常是用来实现通用功能过滤的。比如:敏感词过滤、字符集编码设置、响应数据压缩等功能。
定义
高层框架,底层为Spring框架。
特点:
创建独立的 Spring 应用程序
直接嵌入 Tomcat、Jetty 或 Undertow(无需部署 WAR 文件)
提供自以为是的“入门”依赖项以简化您的构建配置
尽可能自动配置 Spring 和 第三方库
提供生产就绪功能,例如指标、健康检查和外部化配置
完全无需代码生成,无需 XML 配置
微服务:
微服务是一种架构风格
一个应用拆分为一组小型服务
每个服务运行在自己的进程内,也就是可独立部署和升级服务之间使用轻量级HTTP交互
服务围绕业务功能拆分
可以由全自动部署机制独立部署
去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
分布式:
微服务的出现产生了分布式,需要用SpringBoot+SpringCloud解决。
开启
1、创建SpringBoot工程(使用maven3.3即以上版本【加入1.8jdk和阿里云】,jdk1.8即以上版本)、
如果pom文件报红,先清理,在刷新(或者重建)
然后就可以编写代码(Controller层)
无需配置,直接使用自动创建的类运行即可。(该类必须在其他类同级或高级)
依赖管理:
SpringBoot帮助我们直接将常用包都导入了。
如果默认的包版本要跟换,直接在pom文件里修改。
例:
1、 首先查看org.springframework.boot里面的版本(记住key)
在当前项目重写
<properties>
<mysql.version>5.1.43</ mysql.version>
</ properties>
Starter场景启动器 spring-boot-starter-xx
1、
2、
3、
4、
自动配置:
底层原理:
个个配置都有默认值
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
@ComponentScan 扫描路径
@SpringBootApplication
等于
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
@Configuration(proxyBeanMethods=true)
告诉Spring这是一个配置类(其本身也是一个组件)
proxyBeanMethods: 代理Bean的方法(为true:代理对象调用方法,即单例)
@Bean 给容器中添加组件,以方法名作为组件ID(默认单实例)
@Import() 给容器导入组件
@Conditional() 否加载该bean到springIOC容器中去 (其子类全是条件装配)
@ConditionalOnBean(name=”aa”) 判断该容器是否有组件aa,没有则无法加载bean到容器中(条件装配)
@ImportResource(classpath:beans.xml) 载入配置文件
配置绑定:
只有在容器中的组件才有SpringBoot提供的强大功能
@ConfigurationProperties(prefix=”xx”) 属性绑定
1、使用@ConfigurationProperties
和@Component
注解到bean
定义类上。对应配置文件写值(application.properties配置文件),符合条件则进行赋值。
2、使用@ConfigurationProperties
和@Bean
注解在配置类的Bean
定义方法上。将@ConfigurationProperties指定的属性值配置给bean对应的属性。
3、使用@ConfigurationProperties
注解到普通类,然后再通过@EnableConfigurationProperties
定义为Bean
。
原理:
自动包装原理:
@SpringBootApplication (由下面三个组成)
@SpringBootConfiguration
@Configuration 代表当前是一个配置类
@EnableAutoConfiguration
@AutoConfigurationPackage //自动配包,利用Registrar给Application全部组件导入进来
@Import({AutoConfigurationPackages.Registrar.class}) //给容器中导入一个组件
@Import({AutoConfigurationImportSelector.class})
利用getAutoConfigurationEntry(annotationMetadata); 给容器中批量导入一些组件(组件是写死的,数量一定),但会按需 加载,条件装配
public@interfaceEnableAutoConfiguration {}
@ComponentScan 指定扫描
总结:
1、SpringBoot先加载所有的自动配置类 ***AutoConfiguration(自动装配)
2、每个自动配置类按照条件进行生效 Conditional***(进行条件判断)
***Properties(里面拿,并与配置文件进行绑定)
3、生效的配置类会给容器中装配许多组件(则有其功能)
4、只要用户有自己配置的,则以用户配置的优先
定制配置:
第一种:直接用自己的@Bean替换
第二种:用户去获取组件的配置文件值,然后修改
例如:
xxxxxx-AutoConfigurationè组件èxxxxx-Properties里面拿值èapplication.properties
最佳实践
1、引入场景依赖
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters、
2、查看自动配置有哪些(选做)
2.1、在 下
2.2、在配置文件中debug=true开启自动配置报告。
技巧
Yaml :标记语言(适合做以数据为中心的配置文件)
开启配置提示信息
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Web开发
1、静态资源目录及访问规则
只要静态资源放在类路径下: called /static or /public or /resources or /NETA-INF/resources、
访问:当前项目根路径/+静态资源名
原理:静态映射:/**
访问规则:请求进入会先找Controller能否处理,不能则所有交给静态资源处理器,没有则404
静态资源默认,没有前缀
添加访问前缀
spring:
mvc:
static-path-pattern: /res/**
改变默认静态资源路径
spring:
resources:
static-locations: classpath:/haha
1.1、欢迎页
spring:
resources:
static-locations: classpath:/haha //不能用,可能会导致 index.jsp不能被默认访问
网络图标
1.2、静态资源底层源码
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) { //判断是否需要默认的静态资源规则
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
}
spring:
resources:
add-mappings: false //禁用所有静态资源规则
1.3、欢迎页底层源码
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
//要用欢迎页功能必须是/**
logger.info("Adding welcome page: " + welcomePage.get());
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
2、请求参数处理
2.1、rest原理
@GetMapping
@PutMapping
@DeleteMapping
@PostMapping
------------------------------源码----------------------------------------------
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
前端发送方式
#开启rest风格
spring:
mvc:
hiddenmethod:
filter:
enabled: true #选择性开启,因为现在是前后端分离,不会用到页面交互
2.2、请求映射原理
建议去看springmvc原理
DispatcherServlet
RequestMappingHandlerMapping //匹配所有的HandlerMapping
WelcomePageHandlerMapping //能访问到欢迎页(index.jsp)
2.3、传参
@PathVariable //将路径变量的值赋给参数
@RequestHeader //将请求头的值赋给参数
@RequestParam //获取请求参数的值
@CookieValue //获取cook值
@RequestBody //获取请求体【post】
@RequestAttribute //获取请求域中的值
@MatrixVariable //获取矩阵变量 /car/{path;iow=34;breaf=as,dfs,as};(矩阵变量)
ServletApI
复杂参数:
Map Model //会被放在request的请求域中 request.get
RedirectAttributes //重定向携带数据
ServletResponse
@MatrixVariable #获取矩阵变量 /car/{path;iow=34;breaf=as,dfs,as};(矩阵变量)
#矩阵变量可以出现在任何路径片段中,每一个矩阵变量都用分号(;)隔开。
#SpringBoot默认是关闭的,需要手动开启
#对于路径的处理都是用UrlPathHelper进行解析的。里面的removeSemicolonContent属性就是支持矩阵变量的
----------------------------------------------------------------------------------------------------------
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper=new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@CookieValue("Webstorm-be5713b6") String aa){
2.4、参数处理原理
- HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler找一个适配器(HandlerAdapter)
3、视图解析器
第三方模板引擎技术:thymeleaf
thymeleaf的基本语法
表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、session域、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include 作用,引入公共页面片段 |
字面量
- 文本值: ‘one text’ , ‘Another one!’ ,…
- 数字: 0 , 34 , 3.0 , 12.3 ,…
- 布尔值: true , false
- 空值: null
- 变量: one,two,… 变量不能有空格
文本操作
- 字符串拼接: +
- 变量替换: |The name is ${name}|
数学运算
- 运算符: + , - , * , / , %
布尔运算
- 运算符: and , or
- 一元运算: ! , not
比较运算
- 比较: > , < , >= , <= ( gt , lt , ge , le )
- 等式: == , != ( eq , ne )
条件运算
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
特殊操作
-
无操作: _
设置属性值-th:attr
- 设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}"> <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/> </fieldset> </form>设置多个值
- 设置多个值
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
使用thymeleaf
1、引入Starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、SpringBoot自动配置好了
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {
public ThymeleafAutoConfiguration() {}
页面放这:
视图解析原理流程:
-
目标方法处理的过程中(阅读
DispatcherServlet
源码),所有数据都会被放在ModelAndViewContainer
里面,其中包括数据和视图地址。 -
方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在
ModelAndViewContainer
。 -
任何目标方法执行完成以后都会返回
ModelAndView
(数据和视图地址)。 -
processDispatchResult()
处理派发结果(页面改如何响应)-
render(mv, request, response);
进行页面渲染逻辑- 根据方法的
String
返回值得到View
对象【定义了页面的渲染逻辑】
- 所有的视图解析器尝试是否能根据当前返回值得到
View
对象 - 得到了
redirect:/main.html --> Thymeleaf new RedirectView()
。 ContentNegotiationViewResolver
里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。view.render(mv.getModelInternal(), request, response);
视图对象调用自定义的render进行页面渲染工作。
RedirectView
如何渲染【重定向到一个页面】- 获取目标url地址
response.sendRedirect(encodedURL);
- 根据方法的
-
视图解析:
- 返回值以 `forward:` 开始: `new InternalResourceView(forwardUrl);` --> 转发`request.getRequestDispatcher(path).forward(request, response);`
- 返回值以 `redirect:` 开始: `new RedirectView()` --> render就是重定向
- 返回值是普通字符串:`new ThymeleafView()`--->
4、拦截器
1、定义一个类(拦截器类)实现HandlerInterceptor接口
2、实现其方法
public class LoginInterceptor implements HandlerInterceptor {
//Controller方法处理之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录检查逻辑
String requestURI = request.getRequestURI();
log.info("拦截的请求路径是:"+requestURI);
HttpSession session= request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser !=null){//放行
return true;
}
//拦住了。未登录,需要跳转到登录页面。
request.setAttribute("msg","请先登录");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
//preHandle返回true,且Controller方法处理完之后
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//preHandle返回true,且DispatcherServlet进行视图的渲染之后多用于清理资源
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
3、创建web配置文件(xxxxWebConfig)实现WebMvcConfigurer接口,实现addInterceptors方法,配置拦截器
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) //addInterceptor添加拦截器
.addPathPatterns("/**") //所有请求都会被拦截(包括静态资源)
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //不拦截哪些
}
}
5、文件上传
使用:
<div class="panel-body">
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos ) throws IOException {
log.info("上传信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
String originalFilename = headerImg.getOriginalFilename(); //获取传入文件的名字
headerImg.transferTo(new File("E:\\webfile\\"+originalFilename)); //将其转存到本地文件夹下
}
if(photos.length>0){
for (MultipartFile photo:photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("E:\\webfile\\"+originalFilename));
}
}
}
return "main";
}
#上传文件请求大小
spring.servlet.multipart.max-file-size=10MB
#上传文件总大小
spring.servlet.multipart.max-request-size=100MB
使用MultipartAutoConfiguration下的MultipartProperties配置所有文件默认值。
源码
MultipartAutoConfiguration—MultipartProperties
- 请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
- 参数解析器来解析请求中的文件内容封装成MultipartFileo
- 将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
6、异常处理
1、默认规则
- 默认情况下,Spring Boot提供/error处理所有错误的映射
- 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据
2、自定义
- 要对其进行自定义,添加view解析为error
- 要完全替换默认行为,可以实现 ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
第一种方式:error/下的4xx,5xx页面会被自动解析;有精确的错误状态码页面就匹配精确,没有就找4xx.html;如果都没有就触发白页
第二种方式:@ControllerAdvice+@ExceptionHandler处理全局异常;底层是ExceptionHandlerExceptionResolver支持的
@Slf4j //日志记录
@ControllerAdvice
public class GlobalExceptionHander {
//规定处理什么异常(数学运算异常,空指针异常)
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String handleArithException(Exception e){
log.info("异常是:{}",e);
return "login"; //返回一个视图地址
}
}
第三种方式:@ResponseStatus +自定义异常;底层是ResponseStatusExceptionResolver,把responsestatus注解的信息底层调用response.sendError(statusCode, resolvedReason); tomcat发送的/error
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多") //
public class UserToolManyException extends RuntimeException{
public UserToolManyException(String message){
super(message);
}
public UserToolManyException() {
}
}
第四种方式:自定义实现 HandlerExceptionResolver处理异常;可以作为默认的全局异常处理规则
@Order(value = Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object handler, Exception ex) {
try {
httpServletResponse.sendError(511,"我设置的错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
第五种方式:ErrorViewResolver实现自定义处理异常;
- oresponse.sendError 。error请求就会转给controller
- 你的异常没有任何人能处理。tomcat底层response.sendError。error请求就会转给controllero basicErrorController要去的页面地址是ErrorViewResolver ;
3、源码
ErrorMvcAutoConfiguration自动配置异常处理规则
容器中的组件:类型:DefaultErrorAttributes -> id: errorAttributes
public class DefaultErrorAttributes implements ErrorAttributes,HandlerExceptionResolverDefaultErrorAttributes:定义错 误页面中可以包含哪些数据。|
容器中的组件:类型:BasicErrorController --> id: basicErrorController (json+白页适配响应)
处理默认/error路径的请求;页面响应new ModelAndView(“error”, model);
容器中有组件View->id是error;(响应默认错误页)
容器中放组件BeanNameViewResolver(视图解析器):按照返回的视图名作为组件的id去容器中找View对象。
容器中的组件:类型:DefaultErrorViewResolver -> id: conventionErrorViewResolver
如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面error/404、5xx.html
7、Web原生组件注入(servlet,Filter,Listener)
1、使用servletAPI
- servlet
使用两个注解:
@WebServlet(urlPatterns = “/my”) //声明Servlet
@ServletComponentScan(basePackages = “com.example.springboot4”) //指定原生Servlet都放在哪里
@ServletComponentScan(basePackages = "com.example.springboot4") //指定原生Servlet都放在哪里
@SpringBootApplication
public class Springboot4Application { //SpringBoot启动器
public static void main(String[] args) {
SpringApplication.run(Springboot4Application.class, args);
}
}
@WebServlet(urlPatterns = "/my") //声明Servlet
public class Myservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("666666");
}
}
- Filter
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/images/*"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter工作");
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
- Listener
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目初始化完成");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目销毁");
}
}
2、XXXRegistrationBean
@Configuration
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
Myservlet myservlet = new Myservlet();
return new ServletRegistrationBean(myservlet,"/my","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MyServletContextListener myServletContextListener = new MyServletContextListener();
return new ServletListenerRegistrationBean(myServletContextListener);
}
}
8、嵌入式Servlet容器
-
默认支持的WebServer
Tomcat
,Jetty
, orUndertow
。ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory
并引导创建服务器。
-
原理
- SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat。
- web应用会创建一个web版的IOC容器
ServletWebServerApplicationContext
。 ServletWebServerApplicationContext
启动的时候寻找ServletWebServerFactory
(Servlet 的web服务器工厂——>Servlet 的web服务器)。- SpringBoot底层默认有很多的WebServer工厂(
ServletWebServerFactoryConfiguration
内创建Bean),如:TomcatServletWebServerFactory
JettyServletWebServerFactory
UndertowServletWebServerFactory
- 底层直接会有一个自动配置类
ServletWebServerFactoryAutoConfiguration
。 ServletWebServerFactoryAutoConfiguration
导入了ServletWebServerFactoryConfiguration
(配置类)。ServletWebServerFactoryConfiguration
根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有TomcatServletWebServerFactory
TomcatServletWebServerFactory
创建出Tomcat服务器并启动;TomcatWebServer
的构造器拥有初始化方法initialize——this.tomcat.start();
- 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。
Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
9、定制Servlet容器
-
实现
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
-
- 把配置文件的值和
ServletWebServerFactory
进行绑定
- 把配置文件的值和
-
修改配置文件
server.xxx
-
直接自定义
ConfigurableServletWebServerFactory
xxxxxCustomizer
:定制化器,可以改变xxxx的默认规则
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
10、定制化原理-SpringBoot定制化组件的几种方式(小结,以后核心重点)
定制化的常见方式
-
修改配置文件
-
xxxxxCustomizer
-
编写自定义的配置类
xxxConfiguration
+@Bean
替换、增加容器中默认组件,视图解析器 -
Web应用 编写一个配置类实现
WebMvcConfigurer
即可定制化web功能 +@Bean
给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
}
@EnableWebMvc
+WebMvcConfigurer
—@Bean
可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍)。- 原理:
WebMvcAutoConfiguration
默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。- 一旦使用
@EnableWebMvc
,会@Import(DelegatingWebMvcConfiguration.class)
。 DelegatingWebMvcConfiguration
的作用,只保证SpringMVC最基本的使用- 把所有系统中的
WebMvcConfigurer
拿过来,所有功能的定制都是这些WebMvcConfigurer
合起来一起生效。 - 自动配置了一些非常底层的组件,如
RequestMappingHandlerMapping
,这些组件依赖的组件都是从容器中获取如。 public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
。
- 把所有系统中的
WebMvcAutoConfiguration
里面的配置要能生效必须@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
。- @EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。
- 原理:
原理分析套路
场景starter - xxxxAutoConfiguration
- 导入xxx组件 - 绑定xxxProperties
- 绑定配置文件项。
SQL
11、数据访问
1、数据源的自动配置
- 导入JDBC场景
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!-- oracle驱动-->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
2、分析自动配置并修改配置
查本地IP(win+r【cmd】》【ipconfig】》IPv4地址)
C:\Users\Administrator.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.3.7.RELEASE\spring-boot-autoconfigure-2.3.7.RELEASE.jar!\org\springframework\boot\autoconfigure\jdbc
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8
username: root
password: 123456
测试
@Slf4j
@SpringBootTest
class Springboot4ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
Long along= jdbcTemplate.queryForObject("select count(*) from user", Long.class);
log.info("记录数:{}",along);
}
}
12、自定义整合druid数据源
Druid是什么?
它是数据库连接池,它能够提供强大的监控和扩展功能。
Spring Boot整合第三方技术的两种方式:
-
自定义
-
找starter场景
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
配置Druid数据源:
@Configuration
public class MyConfig {
@Bean
@ConfigurationProperties("spring.datasource")//复用配置文件的数据源配置
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
return druidDataSource;
}
}
配置Druid的监控页功能:
-
Druid内置提供了一个
StatViewServlet
用于展示Druid的统计信息。官方文档 - 配置_StatViewServlet配置。这个StatViewServlet
的用途包括:- 提供监控信息展示的html页面
- 提供监控信息的JSON API
-
Druid内置提供一个
StatFilter
,用于统计监控信息。官方文档 - 配置_StatFilter -
WebStatFilter
用于采集web-jdbc关联监控的数据,如SQL监控、URI监控。官方文档 - 配置_配置WebStatFilter -
Druid提供了
WallFilter
,它是基于SQL语义分析来实现防御SQL注入攻击的。官方文档 - 配置 wallfilter
@Configuration
public class MyConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//加入监控和防火墙功能功能
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
/**
* 配置 druid的监控页功能
* @return
*/
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(statViewServlet, "/druid/*");
//监控页账号密码:
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
13、数据访问-druid数据源starter整合方式
官方文档 - Druid Spring Boot Starter
引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
分析自动配置:
- 扩展配置项
spring.datasource.druid
- 自动配置类
DruidDataSourceAutoConfigure
DruidSpringAopConfiguration.class
, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class
, 监控页的配置。spring.datasource.druid.stat-view-servlet
默认开启。DruidWebStatFilterConfiguration.class
,web监控配置。spring.datasource.druid.web-stat-filter
默认开启。DruidFilterConfiguration.class
所有Druid的filter的配置:
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
14、整合MyBatis操作
starter的命名方式:
- SpringBoot官方的Starter:
spring-boot-starter-*
- 第三方的:
*-spring-boot-starter
1、配置模式:
- 全局配置文件
SqlSessionFactory
:自动配置好了SqlSession
:自动配置了SqlSessionTemplate
组合了SqlSession
@Import(AutoConfiguredMapperScannerRegistrar.class)
Mapper
: 只要我们写的操作MyBatis的接口标准了@Mapper
就会被自动扫描进来
2、配置文件
1、导入mybatis官方starter
2、编写mapper接口。标准@Mapper注解
3、编写sqI映射文件并绑定mapper接口
4、在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息(建议;配置在mybatis.configuration)
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
spring:
datasource:
username: root
password: 1234
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.jdbc.Driver
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/*.xml #sql映射文件位置
配置private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值
可以不写全局,配置文件,所有全局配置文件的配置都放在configuration配置项中即可(注意**:如果不用mybatis-config.xml配置文件,需要在yaml或properties中删除他的引用**)。
spring:
datasource:
username: root
password: 1234
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.jdbc.Driver
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/*.xml #sql映射文件位置
configuration: #指定mybatis全局配置文件的相关配置项
map-underscore-to-camel-case: true #驼峰命名法
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 由于Spring Boot自动配置缘故,此处不必配置,只用来做做样。-->
</configuration>
Mapper接口:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lun.boot.mapper.UserMapper">
<select id="getUser" resultType="com.lun.boot.bean.User">
select * from user where id=#{id}
</select>
</mapper>
import com.lun.boot.bean.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
public User getUser(Integer id);
}
配置private Configuration configuration;
也就是配置mybatis.configuration
相关的,就是相当于改mybatis全局配置文件中的值。(也就是说配置了mybatis.configuration
,就不需配置mybatis全局配置文件了)
# 配置mybatis规则
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
# 可以不写全局配置文件,所有全局配置文件的配置都放在configuration配置项中了。
# config-location: classpath:mybatis/mybatis-config.xml
configuration:
map-underscore-to-camel-case: true
3、纯注解的使用
/**
*简单的可以直接用@Select等注释
*/
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{id}")
public User getByid(Integer id);
}
困难的如下:
<insert id="insert" useGeneratedKeys="true" keyProperty="id"> #标识自增
insert into city(name,state,country) vaLues(#{ name },#{state },#{ country})
</insert>
@Mapper
public interface UserMapper {
@Insert("insert into city('name' , 'state ' , country` ) values(#{name} ,#{state} ,#{country}
@options(useGeneratedKeys = true,keyProperty = "id") //所有原mapper可配置的东西,option也可以
public void insert(city city );
}
15、整合oracle
<!--oracle驱动-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@192.168.43.142:1521:mldn
spring.datasource.username=ch
spring.datasource.password=ch
16、整合mybatis-plus
1、下载mybatisx插件
2、添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
注意:引入 MyBatis-Plus
之后请不要再次引入 MyBatis
以及 MyBatis-Spring
,以避免因版本差异导致的问题。
MybatisPlusAutoConfiguration
配置类,MybatisPlusProperties
配置项绑定。SqlSessionFactory
自动配置好了,底层是容器中默认的数据源。mapperLocations
自动配置好的,有默认值classpath*:/mapper/**/*.xml
,这表示任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件放在 mapper下。- 容器中也自动配置好了
SqlSessionTemplate
。 @Mapper
标注的接口也会被自动扫描,建议直接@MapperScan("com.lun.boot.mapper")
批量扫描。
3、配置高级mapper文件
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lun.hellomybatisplus.model.User;
public interface UserMapper extends BaseMapper<User> {
}
MyBatisPlus优点之一:只需要我们的Mapper继承MyBatisPlus的BaseMapper
就可以拥有CRUD能力,减轻开发工作。
即:直接可以去service层调用
17、数据访问(mybatis-plus)
注意一般先写service接口在通过接口调用实现类(serviceimpl)
@service
public class userServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
//里面默认带了单表增删改查的各种方法,但没有复杂的方法。
}
但推荐不使用,因为现实中的业务都是比较复杂的。
18、分页操作
分页去controller层
添加分页插件:
@Configuration
public class MyBatisConfig {
/**
* MybatisPlusInterceptor
* @return
*/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
//这是分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user: ${users.records}">
<td th:text="${user.id}"></td>
<td>[[${user.name}]]</td>
<td th:text="${user.age}">Win 95+</td>
<td th:text="${user.email}">4</td>
<td>
<a th:href="@{/user/delete/{id}(id=${user.id},pn=${users.current})}"
class="btn btn-danger btn-sm" type="button">删除</a>
</td>
</tr>
</tfoot>
</table>
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第[[${users.current}]]页 总计 [[${users.pages}]]页 共[[${users.total}]]条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<li th:class="${num == users.current?'active':''}"
th:each="num:${#numbers.sequence(1,users.pages)}" >
<a th:href="@{/dynamic_table(pn=${num})}">[[${num}]]</a>
</li>
<li class="next disabled"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
userService.removeById(id);
ra.addAttribute("pn",pn);
return "redirect:/dynamic_table"; //删除成功后重定向到该页面
}
@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
//表格内容的遍历
//从数据库中查出user表中的用户进行展示
//构造分页参数
Page<User> page = new Page<>(pn, 2);
//调用page进行分页
Page<User> userPage = userService.page(page, null);
model.addAttribute("users",userPage);
return "table/dynamic_table";
}
19、数据访问-准备阿里云Redis环境
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
RedisAutoConfiguration
自动配置类,RedisProperties 属性类 --> spring.redis.xxx是对redis的配置。- 连接工厂
LettuceConnectionConfiguration
、JedisConnectionConfiguration
是准备好的。 - 自动注入了
RedisTemplate<Object, Object>
,xxxTemplate
。 - 自动注入了
StringRedisTemplate
,key,value都是String - 底层只要我们使用
StringRedisTemplate
、RedisTemplate
就可以操作Redis。
外网Redis环境搭建:
-
阿里云按量付费Redis,其中选择经典网络。
-
申请Redis的公网连接地址。
-
修改白名单,允许
0.0.0.0/0
访问。
数据访问-Redis操作与统计小实验
相关Redis配置:
spring:
redis:
# url: redis://lfy:Lfy123456@r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com:6379
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
client-type: jedis
jedis:
pool:
max-active: 10
# lettuce:# 另一个用来连接redis的java框架
# pool:
# max-active: 10
# min-idle: 5
测试Redis连接:
@SpringBootTest
public class Boot05WebAdminApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
System.out.println(redisConnectionFactory.getClass());
}
}
Redis Desktop Manager:可视化Redis管理软件。
URL统计拦截器:
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
//默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
return true;
}
}
注册URL统计拦截器:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(redisUrlCountInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**","/aa/**");
}
}
Filter、Interceptor 几乎拥有相同的功能?
- Filter是Servlet定义的原生组件,它的好处是脱离Spring应用也能使用。
- Interceptor是Spring定义的接口,可以使用Spring的自动装配等功能。
调用Redis内的统计数据:
@Slf4j
@Controller
public class IndexController {
@Autowired
StringRedisTemplate redisTemplate;
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");
ValueOperations<String, String> opsForValue =
redisTemplate.opsForValue();
String s = opsForValue.get("/main.html");
String s1 = opsForValue.get("/sql");
model.addAttribute("mainCount",s);
model.addAttribute("sqlCount",s1);
return "main";
}
}
20、单元测试
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的测试引擎。
1、常用注释
- @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest:表示方法是参数化测试。
- @RepeatedTest:表示方法可重复执行。
- @DisplayName:为测试类或者测试方法设置展示名称。
- @BeforeEach:表示在每个单元测试之前执行。
- @AfterEach:表示在每个单元测试之后执行。
- @BeforeAll:表示在所有单元测试之前执行。
- @AfterAll:表示在所有单元测试之后执行。
- @Tag:表示单元测试类别,类似于JUnit4中的@Categories。
- @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。
- @Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
- @ExtendWith:为测试类或测试方法提供扩展类引用。
import org.junit.jupiter.api.*;
@DisplayName("junit5功能测试类")
public class Junit5Test {
@DisplayName("测试displayname注解")
@Test
void testDisplayName() {
System.out.println(1);
System.out.println(jdbcTemplate);
}
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
@Disabled
@DisplayName("测试方法2")
@Test
void test2() {
System.out.println(2);
}
@RepeatedTest(5)
@Test
void test3() {
System.out.println(5);
}
/**
* 规定方法超时时间。超出时间测试出异常
*
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始了...");
}
@AfterEach
void testAfterEach() {
System.out.println("测试结束了...");
}
@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
@AfterAll
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
}
2、断言机制(assertions)
断言Assertion是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
1、简单断言
用来对单个值进行简单的验证。(注意:前面的断言失败后,后面的断言无法运行)
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
2、数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
3、组合断言
assertAll()
方法接受多个 org.junit.jupiter.api.Executable
函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。(必须都正确才能执行成功)
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
4、异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule
注解的ExpectedException
变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()
,配合函数式编程就可以进行使用。
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
5、超时断言
JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
6、快速失败
通过 fail 方法直接使得测试失败。
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
3、前置条件(assumptions)
前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue
和 assumFalse
确保给定的条件为 true
或 false
,不满足条件会使得测试执行终止。
assumingThat
的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable
对象才会被执行;当条件不满足时,测试执行并不会终止。
4、嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested
注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach
和@AfterEach
注解,而且嵌套的层次没有限制。
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
5、参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现**ArgumentsProvider
**接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
21、指标监控
SpringActuator
学习到此。。
技巧
1、登录页面
先设计默认跳转到登录页
@GetMapping(value = {"/","login"})
public String loginPage(){ //默认启动跳转登录页面。
return "login";
}
判断用户名与密码,正确不直接跳转到主页面,而是重定向到xxx请求(防止重复提交表单)
@PostMapping("login")
public String main(User user, HttpSession session, Model model){ //判断密码是否正确,正确到下一个方法,错误回到登录页。
if(StringUtils.hasLength(user.getUserName())&&"123456".equals(user.getPassword())){
session.setAttribute("logerUser",user);
//登录成功重定向到mian.html;(防止表单重复提交)
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
//回到登录界面
return "login";
}
}
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){ //输入正确密码后,为防止表单重复提交,
Object logUser=session.getAttribute("logerUser");
if (logUser!=null){
return "main";
}else {
model.addAttribute("msg","请重新登录");
return "login";
}
}
5、参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现**ArgumentsProvider
**接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
21、指标监控
SpringActuator
学习到此。。
技巧
1、登录页面
先设计默认跳转到登录页
@GetMapping(value = {"/","login"})
public String loginPage(){ //默认启动跳转登录页面。
return "login";
}
判断用户名与密码,正确不直接跳转到主页面,而是重定向到xxx请求(防止重复提交表单)
@PostMapping("login")
public String main(User user, HttpSession session, Model model){ //判断密码是否正确,正确到下一个方法,错误回到登录页。
if(StringUtils.hasLength(user.getUserName())&&"123456".equals(user.getPassword())){
session.setAttribute("logerUser",user);
//登录成功重定向到mian.html;(防止表单重复提交)
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
//回到登录界面
return "login";
}
}
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){ //输入正确密码后,为防止表单重复提交,
Object logUser=session.getAttribute("logerUser");
if (logUser!=null){
return "main";
}else {
model.addAttribute("msg","请重新登录");
return "login";
}
}