目录
Web原生组件注入(Servlet、Filter、Listener)
目录
Web原生组件注入(Servlet、Filter、Listener)
WEB开发
拦截器
拦截器配置
@Configuration
public class AdminWeConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**").//所有请求都被拦截
excludePathPatterns("/","/login","/css/**","/fonts/**","/js/**","/images/**");//放行的请求
}
}
重写拦截器
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@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;
}
/**
* 目标方法执行完之后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 页面渲染以后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
文件上传
前端固定写法:请求方式method="post" ;enctype="multipart/form-data"
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
/**
* 文件上传测试
*/
@Slf4j
@Controller
public class FormTestController {
@GetMapping("/form_layouts")
public String form_layouts(){
return "form/form_layouts";
}
/**
* MultipartFile 自动封装上传过来的文件
*/
@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()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\"+originalFilename));
}
if(photos.length>0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\"+originalFilename));
}
}
}
return "main";
}
}
Web原生组件注入(Servlet、Filter、Listener)
@ServletComponentScan(basePackages = "com.gr.boot") :指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器
@WebFilter(urlPatterns={"/css/*","/images/*"})
@WebListener
主类加注释
@ServletComponentScan(basePackages = "com.gr.boot")
@SpringBootApplication
public class Boot05WebAdminApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05WebAdminApplication.class, args);
}
}
servlet
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("6666");
}
}
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 request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("MyFilter工作");
chain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
Listener
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目销毁");
}
}
数据访问
数据源的自动配置
引入JDBC依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
引入数据库依赖
<!--默认版本:<mysql.version>8.0.22</mysql.version>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<version>5.1.49</version>-->
</dependency>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
application.yml中修改配置项
spring:
datasource:
url: jdbc:mysql://localhost:13306/mybatis_plus?useSSL=false&server.Timezone=UTC
username: root
password: "abc123"
driver-class-name: com.mysql.jdbc.Driver
使用Druid数据源
druid官方github地址
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
application.yml中设置配置项 (示例)
druid:
aop-patterns: com.gr.boot.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true #开启监控页
login-username: admin #设置账号
login-password: abc123 #设置密码
reset-enable: false #是否开启重置功能
web-stat-filter: # 监控web
enabled: true #是否开启
url-pattern: /* #拦截请求前缀
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #放行请求
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
log-slow-sql: true
enabled: true
wall:
enabled: true
整合MyBatis-Plus
加入依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
Mapper类继承BaseMapper<>类即可实现基本CRUD功能
public interface UserMapper extends BaseMapper<User> {
}
@TableField(exist = false)注解表示表中不存在该属性
@TableField(exist = false)
private String userName;
@TableField(exist = false)
private String password;
分页插件
model传入分页属性内容page
@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value = "pn",defaultValue = "1")Integer pn, Model model){
List<User> list = userService.list();
// model.addAttribute("Users",list);
//分页查询数据
Page<User> userPage = new Page<>(pn, 2);//Page(long current 当前页面页数, long size 每页显示几条数据)
//分页查询的结果
Page<User> page = userService.page(userPage, null);//page(E page, Wrapper<T> queryWrapper)
model.addAttribute("page",page);
return "table/dynamic_table";
}
IOC中添加分页插件:
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInnerInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInnerInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
查询的list数据实现分页功能
单元测试
JUnit5常用注解
JUnit5的注解与JUnit4的注解有所变化
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行
- @AfterAll :表示在所有单元测试之后执行
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
断言
引入静态类
import static org.junit.jupiter.api.Assertions.*;
简单断言
方法 | 说明 |
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());
}
数组断言
@Test
@DisplayName("数组断言")
void array() {
assertArrayEquals(new int[]{2, 1}, new int[]{1, 2}, "数组内容不相等");
}
组合断言
//所有断言全部需要成功
@Test
@DisplayName("组合断言")
void all() {
assertAll("test",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 < 0,"2号出错了")
);
}
异常断言
@Test
@DisplayName("异常断言")
void testException(){
assertThrows(ArithmeticException.class,
()->{int i = 1/0;},
"业务逻辑居然正常运行");
}
超时断言
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
快速失败
//快速失败
@Test
@DisplayName("快速失败")
void testFail(){
if( 1 == 2){
fail("测试失败");
}
}
前置条件
//测试前置条件
@Test
@DisplayName("测试前置条件")
void testAssumptions(){
Assumptions.assumeTrue(false,"结果不满足");
System.out.println(111);
}
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
@DisplayName("嵌套测试")
public class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("创建一个栈Stack")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested //嵌套测试类
@DisplayName("when new")
class WhenNew {
@BeforeEach //在所有测试之前执行
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("栈是否为空")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("抛出EmptyStackException(栈为空)异常")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("抛出EmptyStackException(栈为空)异常")
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); //给栈中放入元素anElement
}
//内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行
@Test
@DisplayName("判断栈中不为空")
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());
}
}
}
}
参数化测试
@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);
}
指标监控
配置
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yml文件中配置暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
yml文件中配置选择暴露监控信息为HTTP
management:
endpoints:
enabled-by-default: false #是否暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
info:
enabled: true
beans:
enabled: true
metrics:
enabled: true
访问 htpp://localhost:8080/actuator/*
最常用的Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
ID | 描述 |
| 暴露当前应用程序的审核事件信息。需要一个 |
| 显示应用程序中所有Spring Bean的完整列表。 |
| 暴露可用的缓存。 |
| 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
| 显示所有 |
| 暴露Spring的属性 |
| 显示已应用的所有Flyway数据库迁移。 |
| 显示应用程序运行状况信息。 |
| 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个 |
| 显示应用程序信息。 |
| 显示Spring |
| 显示和修改应用程序中日志的配置。 |
| 显示已应用的所有Liquibase数据库迁移。需要一个或多个 |
| 显示当前应用程序的“指标”信息。 |
| 显示所有 |
| 显示应用程序中的计划任务。 |
| 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
| 使应用程序正常关闭。默认禁用。 |
| 显示由 |
| 执行线程转储。 |
如果是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
| 返回 |
| 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖 |
| 返回日志文件的内容(如果已设置 |
| 以Prometheus服务器可以抓取的格式公开指标。需要依赖 |
Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
yml文件中配置查看详细信息 :
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
show-details: always
Profile功能
application.yml 不带后缀的为默认配置文件,任何时候都会被加载
server.port=8080
#指定激活的环境
spring.profiles.active=test
application-test.yml 指定环境配置文件,其中配置当前环境下的配置内容 (可以有多个,后缀内容为默认配置选定的条件)
person:
name: test-张三
server:
port: 7000
@Profile条件装配功能
@Profile注解后加配置文件后缀,选为当前配置下生效
@Profile("test")
@ConfigurationProperties("person")
@Component
@Data
public class Worker implements Person {
private String name;
private Integer age;
}
profile分组
数组前为自定义名称,可选定多个配置文件
server.port=8080
#指定激活的环境
spring.profiles.active=myprod
spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod
spring.profiles.group.mytest[0]=test