常见参数注解使用:
@PathVariable 路径变量
@RequestHeader 获取请求头
@RequestParam 获取请求参数 (指url问号后的参数)
@CookieValue 获取Cookie值
@RequestBody 获取请求头【POST】
@RequestAttribute 获取request域属性
@MatrixVariable 矩阵变量
@ModelAttribute
@RestController //相当于@Controller+@ResponseBody两个注解的结合,返回Json数据不需要在方法前面加@ResponseBody注解
public class ParameterTestController {
// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){
Map<String,Object> map = new HashMap<>();
// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
}
如果cookie被禁用了,session里面的内容怎么使用?
在页面开发中,session.set(a,b) —> jsessionid ----> cookie ----> 每次发送请求携带
如果禁用cookie,使用Url重写:/abc;jsessionid=xxxx 把cookie的值使用矩阵变量的方式进行传递
需要先手动开启矩阵变量
矩阵变量必须有url路径变量才能被解析
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
创建返回WebMvcConfigurerBean:
@Configuration(proxyBeanMethods = false)
public class WebConfig{
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
}
}
矩阵变量必须有url路径变量才能被解析
@MaxtrixVariable 的使用
@RestController
public class ParameterTestController {
///cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
}
视图解析
Springboot默认不支持jsp,需要引入第三方模板引擎技术实现页面渲染。
Thymeleaf介绍
Thymeleaf是用来开发Web和独立环境项目的服务器端的Java模版引擎
Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注Thymeleaf的语法即可。
Thymeleaf语法
设置属性值:
设置单个值
<input type="submit" value="subscribe!" th:attr="value=#{subscribe.submit}"/>
设置多个值
<img src="../../images/logo.png" th:attr="src=@{/images/logo.png},title=#{logo}"
以上两个的代替写法 th:xxxx
<input type="submit" value="subscribe!" th:value="#{subscribe:submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
</form>
Themeleaf使用
- 引入Starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
</dependency>
自动配好的策略:
- 所有thymeleaf的配置值都在ThymeleafProperties
- 配置好了SpringTemplateEngine
- 配置好了ThymeleafViewResolve
- 只需要直接开发页面 页面放在templates下
public static final String DEFAULT_PREFIX = "classpath:/templates/";//网页放在这路径下
public static final String DEFAULT_SUFFIX = ".html";//xxx.html
测试:
在Template路径下新建html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.baidu.com" th:href="${link}">去豆瓣1</a> <br/>
<a href="www.baidu.com" th:href="@{link}">去豆瓣2</a> <br/>
</h2>
</body>
</html>
在Controller下新建ViewTestController类:
package com.yutou.boot05web01.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ViewTestController {
@GetMapping("/yutou")//通过localhost:8080/yutou访问
public String yutou(Model model){
//model中的数据会被放到请求域中 request.setAttribute("a",aa)
model.addAttribute("msg","你好 yutou");
model.addAttribute("link","http://www.douban.com");
return "success";
}
}
web后台管理系统基本功能
登录:
@Controller
public class IndexController {
//来登录页
@GetMapping(value = {"/","login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
if (StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
//把登录成功的用户保存起来
session.setAttribute("loginUser",user);
//登录成功重定向到main.html,防止表单重复提交
return "redirect:/main.html";
}else {
model.addAttribute("msg","账户密码错误");
//回到登录页面
return "login";
}
}
//去main页面
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
//是否登录,拦截器:过滤器
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null){
return "main";
}else {
//回到登录页面
model.addAttribute("msg","请重新登录");
return "login";
}
}
}
login.html页面:
<input type="text" name="userName" class="form-control" placeholder="用户名" autofocus>
<input type="password" name="password" class="form-control" placeholder="密码">
抽取公共页面:
common.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commonheader">
<!--common-->
<link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
<link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
<script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
<![endif]-->
</head>
<body>
<!-- left side start-->
<div id="leftmenu" class="left-side sticky-left-side">
...
<!-- header section start-->
<div th:fragment="headmenu" class="header-section">
...
</div>
<!-- header section end-->
<div id="commonscript">
<!-- Placed js at the end of the document so the pages load faster -->
<script th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/modernizr.min.js}"></script>
<script th:src="@{/js/jquery.nicescroll.js}"></script>
<script th:src="@{/js/scripts.js}"></script>
</div>
<!--common scripts for all pages-->
</div>
</body>
</html>
将四个公共的部分抽取出来,分别为commonheader,leftmenu,headmenu,commonscript
在其他有这些公共部分的页面用以下语句代替:
<div th:include="common :: commonheader"></div>
<div th:replace="common :: #leftmenu"></div>
<div th:replace="common :: headmenu"></div>
<div th:replace="common :: #commonscript"></div>
表格内容的遍历
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("list", "123444"),
new User("haha", "aaaaa"),
new User("hehe", "aaddd")
);
model.addAttribute("users",users);
return "table/dynamic_table";
}
页面表格:
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats.count}">Trident</td>
<td th:text="${user.userName}"> Internet</td>
<td >[[${user.password}]]</td>
</tr>
</tfoot>
拦截器
- 编写一个拦截器实现HandlerInterceptor
- 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
- 指定拦截规则【如果是拦截所有,静态资源也会被拦截】
//登录检查
//1.配置好拦截器需要拦截哪些请求
//2.把这些配置放在容器中
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
//目标方法执行之前
@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;
}
//拦截住,未登录,跳转到登录页
session.setAttribute("msg","请先登录");
response.sendRedirect("/");
// request.getRequestDispatcher("/").forward(request,response);
return false;
}
//目标方法执行完成以后
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandler执行",modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常",ex);
}
}
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截,包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}
文件上传
文件上传Controller类:
//文件上传测试
@Controller
@Slf4j
public class FormTestController {
@GetMapping("/form_layouts")
public String form_layout(){
return "form/form_layouts";
}
//Mul
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg")MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws Exception{
log.info("上传的信息:email={},username={},header={}},photos={}}",
email,username,headerImg.getSize(),photos.length);
if (!headerImg.isEmpty()){
//保存到文件服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("G:\\test" + originalFilename));
}
if (photos.length > 0){
for (MultipartFile photo : photos) {
if (!photo.isEmpty()){
String originalFilename1 = photo.getOriginalFilename();
photo.transferTo(new File("G:\\test1" + originalFilename1) );
}
}
}
return "main";
}
}
测试日志为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxr8adW9-1619598587767)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423104921250.png)]
默认配置上传的文件大小不超过1M,在application.properties中修改:
spring.servlet.multipart.max-file-size=10MB #上传文件大小为10M
spring.servlet.multipart.max-request-size=100MB #最大为100M
html页面为:
<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>
错误处理
默认规则
- 默认情况下,Springboot提供/error处理所有错误的映射
- 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个"whitelabel"错误视图,以HTML格式呈现相同的数据。
- 要对其进行自定义,添加view解析为error
- 要完全替换默认行为,可以实现
errorController
并注册该类型的Bean定义,或添加ErrorAttributes
类型的组件以使用现有机制但替换其内容。 - error/下的4xx,5xx页面会被自动解析
定制错误处理逻辑
- 自定义错误页 error/404.html
- @ControllerAdvice+@ExceptionHandler处理异常
- 实现 HandlerExceptionResolver处理异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfBrPFuT-1619598587773)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423114308490.png)]
不知道为什么显示不了。。
Web原生组件注入(Servlet、Filter、Listener)
使用Servlet API
@ServletComponentScan(basePackages = "com.yutou.admin")//指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = "/my")//效果:直接响应,没有Spring的拦截器
Filter:
@WebFilter(urlPatterns = {"/css/*","/images/*"})
Listener:
@WebListener
使用RegistConfig
//(proxyBeanMethods = false)保证依赖的组件始终是单实例的
@Configuration(proxyBeanMethods = true)
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();
// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}
}
定制化原理
定制化的常见方式
-
修改配置文件
-
xxxxCustomizer;
-
编写自定义的配置类:xxxConfiguration + @Bean替换,增加容器中默认组件,视图解析器
-
web应用 实现WebMvcConfigurer 即可定制化web功能 + @Bean给容器中再扩展一些组件
@Configuration public class AdminWebConfig implements WebMvcConfigurer
-
@EnableWebMvc + WebMvcConfigurer ——@Bean 可以全面接管SpringMVC,所有规则全部自己重新配置,实现定制和扩展功能
- 原理
- 1、WebMvcAutoConfiguration默认的Spring MVC的自动配置功能类、静态资源、欢迎页等
- 2、一旦使用
@EnableWebMvc
会@Import(DelegatingWebMvcConfiguration.class) - 3、DelegatingWebMvcConfiguration的作用,只保证SpringMvc最基本的使用
- 把所有系统中的WebMvcConfigurer拿过来。所有功能的定制都是这些WebMvcConfigurer合起来一起生效
- 自动配置了一些非常底层的组件。RequestMappingHandlerMapping这些组件依赖的组件都是从容器中获取
- public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- 4、WebMvcConfiguration里面的配置要能生效必须@ConditionalOnMisiingBean(WebMvcConfigurationSupport.class)
- 5、
@EnableWebMvc
导致了 WebMvcAutoConfiguration 没有生效。
原理分析套路:
场景starter + XXXAutoConfiguration + 导入xxx组件 + 绑定xxxProperties + 绑定配置文件项
数据访问
SQL
1、数据源的自动配置
导入JDBC场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BehQeHxh-1619598587774)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423153835869.png)]
数据库驱动?为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下来要要操作哪种
导入Mysql的驱动 默认版本
数据库的版本要和驱动的版本对应
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
想要修改版本:
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性就近优先原则)
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
2、分析自动配置
-
DataSourceAutoConfiguration:数据源的自动配置
- 修改数据源的相关配置:spring.datasource
-
DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置
-
JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以来对数据库进行crud
-
JndiDataSourceAutoConfiguration:jndi的自动配置
-
XADataSourceAutoConfiguration:分布式事务相关的
3、修改配置项
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root123
driver-class-name: com.mysql.jdbc.Driver
druid
druid数据源starter整合方式:
1、引入druid-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
2、分析自动配置
DruidSpringAopConfiguration.class 监控SpringBean的配置
3、配置application.yaml文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root123
driver-class-name: com.mysql.jdbc.Driver
#监控页的配置
druid:
aop-patterns: com.yutou.admin.* #监控springBean
filters: stat,wall,slf4j # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
reset-enable: false
web-stat-filter: # 监控web
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.png,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
log-slow-sql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
整合MyBatis操作
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
1、配置模式
- 全局配置文件
- SqlSessionFactory:自动配置好了
- SqlSession:自动配置了SqlSessionTemplate组合了SqlSession
- Mapper:只要我们写的操作Mybatis的接口标准 @Mapper就会自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class,MybatisLauageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration(){}
@ConfigurationProperties(profix = "mybatis")
public class MybatisProperties
可以修改配置文件中mybatis开始的所有;
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件的位置
mapper-locations: classpath:mybatis/mapper/*.xml #SQL映射文件位置
Mapper接口--->绑定xml
<?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.yutou.admin.mapper.AccountMapper">
<select id="getAcct" resultType="com.yutou.admin.Bean.Account">
select * from tbl_user where id=#{id}
</select>
</mapper>
配置 private Configuration;mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
可以不写全局,配置文件,所有配置文件的配置都放在configuration配置项中即可
- 导入mybatis官方starter
- 编写mapper接口,标准@Mapper注解
- 编写sql映射文件并绑定mapper接口
- 在application.yaml中指定Mapper配置文件的位置
注解
@Service
public class CityService {
@Autowired
CityMapper cityMapper;
public City getById(Long id){
return cityMapper.getByid(id);
}
}
@ResponseBody
@GetMapping("/city")
public City getCityById(@RequestParam("id") Long id){
return cityService.getById(id);
}
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getByid(Long id);
}
2、注解和配置混合版:
注解和配置混合(如果SQL语句很长的话):
@Service
public class CityService {
@Autowired
CityMapper cityMapper;
public void saveCity(City city) {
cityMapper.insert(city);
}
}
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public void insert(City city);
}
@Autowired
CityService cityService;
@ResponsBody
@PostMapping("/city")
public City saveCity(City city){
cityService.saveCity(city);
return city;
}
<?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.yutou.admin.mapper.CityMapper">
<insert id="insert" useGenerateKey="ture" keyProperty="id">
insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
</insert>
</mapper>
最佳实战:
- 引入mybatis-starter
- 配置application.yaml中,指定mapper-location位置即可
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解
- 复杂方法编写mapper.xml进行绑定映射
- @MapperScan(“com.yutou.admin.mappper”)简化,其他的接口就可以不用标注@Mapper注解
整合Mybatis-Plus 完成CRUD
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
自动配置:
- MybatisPlusAutoConfiguration,MybatisPlusProperties配置项绑定,mybatis-plus;xxx就是对mybatis-plus的定制
- SqlSessionFactory自动配置好,底层是容器中默认的数据源
- mapperLocations自动配置好。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件下任意路径下的所有xml都是sql映射文件,建议以后sql映射文件,放在mapper下。
- 容器中也自动配置好了SqlSessionTemplate
- @Mapper标注的接口也会被自动扫描;建议直接@MapperScan(“com.yutou.admin.mapper”)批量扫描就行
优点:
- 只需要我们的Mapper继承BaseMapper就可以拥有CRUD能力
整合阿里云Redis环境
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自动配置:
- RedisAutoConfiguration 自动配置类,RedisProperties 属性类 —>spring.redis.xxx是对redis的配置
- 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
- 自动注入了RedisTemplate<Object, Object>
- 自动注入了StringRedisTemplate;k:v都是String
- 底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作redis
单元测试
JUnit5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUXUO12h-1619598587776)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428110835933.png)]
现在版本:
@SpringBootTest
class Boot05WebAdminApplicationTests{
@Test
void contextLoads(){
}
}
以前:
@SpringBootTest + @RunWith(SpringTest.class)
如果要兼容junit4需要自行引入:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot整合Junit以后:
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired,比如@Transactional标注测试方法,测试完成后自动回滚
JUnit5常用注解:
JUnit5的注解和Junit4的注解有所变化
- @Test:表示方法是测试方法,但是与JUnit4的@Test不同,它的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest:表示方法是参数化测试
- @RepeatedTest:表示方法可重复执行
- @DisplayName:为测试类或者测试方法设置展示名称
- @BeforeEach:表示在每个单元测试之前执行
- @AfterEach:表示在每个单元测试之后执行
- @BeforeAll:表示在所有单元测试之前执行
- @AfterAll:表示在所有单元测试之后执行
- @Tag:表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout:表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith:为测试类或测试方法提供扩展类引用
@DisplayName("junit5功能测试类")
public class Junit5Test {
@DisplayName("测试displayName注解")
@Test
void testDisplayName1(){
System.out.println(1);
}
@Disabled
@DisplayName("测试注解2")
@Test
void testDisplayName2(){
System.out.println(2);
}
@RepeatedTest(5)
@Test
void test3(){
System.out.println(5);
}
//规定方法的超时时间
@Timeout(value = 500,unit = TimeUnit.MICROSECONDS)
@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("所有测试类就要结束了...");
}
}
断言
断言是测试方法中的核心部分,用来对测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
所有测试运行结束以后,会有一个详细的测试报告
简单断言
用来对单个值进行简单的验证。如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sArHifT5-1619598587777)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428134549070.png)]
//前面断言失败后面的代码都不会执行
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions(){
int cal = cal(3,2);
//相等
Assertions.assertEquals(5,cal,"业务逻辑计算失败");
Object object1 = new Object();
Object object2 = new Object();
assertSame(object1,object2,"两个对象不一样");
}
数组断言
@Test
@DisplayName("array assertion")
void array(){
assertArrayEquals(new int[]{2,1}, new int[]{1,2});
}
组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Exexcutable 函数式接口的实例作为要验证的断言,可以通过Lamda表达式很容易的提供这些断言
@Test
@DisplayName("组合断言")
//断言全部需要成功
void all(){
assertAll("test",
()-> assertTrue(true && true,"结果不为true"),
()-> assertEquals(1 , 1,"结果不是1"));
}
异常断言
@DisplayName("异常断言")
@Test
void testException(){
//断定业务逻辑一定出现异常
assertThrows(ArithmeticException.class,()->{
int i = 10 / 0;},"业务逻辑居然正常运行");
}
超时断言
快速失败
@DisplayName("快速失败")
@Test
void testPail(){
if (2 == 2){
fail("测试失败");
}
}
前置条件
JUnit5中的前置条件(Assumption)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止,前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("测试前置条件")
@Test
void testassumption(){
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("11111");
}
嵌套测试
JUnit5可以通过Java中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起,在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
@DisplayName("嵌套测试")
public class TestAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("")
void isInstantiateWithNew(){
new Stack<>();
//在嵌套测试的情况下,外层的Test不能驱动内层的Before(After)Each/ALL之类的方法提前/之后
assertNull(stack);
}
@Nested
@DisplayName("whenNew")
class whenNew{
@BeforeEach
void createNewStack(){
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty(){
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws")
void throwsExceptionWhenPopped(){
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws peeked")
void throwsExceptionWhenPeeked(){
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing")
class AfterPushing{
String anElement = "an element";
@BeforeEach
void pushAnElement(){
stack.push(anElement);
}
//内存的Test可以驱动外层的Before(After)Each/ALL之类的方法提前/之后运行
@Test
@DisplayName("it is no longer empty")
void isNotEmpty(){
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPoped(){
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked and is not empty")
void returnElementWhenPeeked(){
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource:为参数化代码指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource:表示为参数化参数提供一个null的入参
@EnumSource:表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
void testParameterized(int i){
System.out.println(i);
}
@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("stringProvider")
void testParameterized2(String i){
System.out.println(i);
}
static Stream<String> stringProvider(){
return Stream.of("apple","banana","orange");
}
指标监控
SpringBoot Actuator
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等,SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1.x和2.x不同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnzkgQo1-1619598587778)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428154801429.png)]
如何使用?
访问 htttp://localhost:8080/actuator/**
# management:是所有actuator的配置
management:
endpoints:
enabled-by-default: true #默认开启所有监控端点
web:
exposure:
include: '*' # 以web方式暴露所有端点
Health EndPoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状态,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状态的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redies等
- 可以很容易的添加自定义的健康检查机制
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull或者被push方法得到:
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已经Metrics