微服务网关-Spring Cloud Gateway(下)

简介: 微服务网关-Spring Cloud Gateway(下)

正文


六、动态网关


动态网管可以使用配置中心,Redis,和数据库(我用的方法)

直接上代码,其实就是增删改查


package com.xiaojie.entity;
import lombok.Data;
/**
 * @Description: 路由实体类
 * @author: xiaojie
 * @date: 2021.07.15
 */
@Data
public class Route {
    private Integer id;
    private String routeId; //路由id
    private String routeName;//路由名称
    private String routePattern;//路由转发地址,
    private Integer routeType;//路由类型1从注册中心获取,0-转发地址
    private String routeUrl;//转发到的地址
    private Integer isOpen;//是否开放0-否1-开放
    private Integer isUse;//是否可用0-否1-开放
}


package com.xiaojie.mapper;
import com.xiaojie.entity.Route;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
 * @Description:Mapper接口
 * @author: xiaojie
 * @date: 2021.07.15
 */
public interface RouteMapper {
    @Select("SELECT id,routeId,routeName,routePattern,routeType,routeUrl,is_open isOpen,is_use isUse FROM `tb_route` WHERE is_open=1 and is_use=1;")
    List<Route> selectAllRoute();
    @Insert("INSERT INTO tb_route(routeId,routeName,routePattern,routeType,routeUrl,is_open,is_use) " +
            "VALUES(#{routeId},#{routeName},#{routePattern},#{routeType},#{routeUrl},#{isOpen},#{isUse})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    Integer add(Route route);
    @Update("UPDATE tb_route set routePattern=#{routePattern},routeUrl=#{routeUrl} WHERE routeId=#{routeId}")
    Integer update(Route route);
    @Delete("DELETE FROM tb_route WHERE routeId=#{routeId}")
    Integer delete(String routeId);
}


package com.xiaojie.service.impl;
import com.xiaojie.entity.Route;
import com.xiaojie.mapper.RouteMapper;
import com.xiaojie.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @Description:
 * @author: xiaojie
 * @date: 2021.07.15
 */
@Service
@Slf4j
public class RouteServiceImpl  implements RouteService, ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private RouteMapper routeMapper;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Override
    public List<Route> getAll() {
        List<Route> routes = routeMapper.selectAllRoute();
        for (Route  route: routes){
            loadRoute(route);
        }
        return routes;
    }
    public void loadRoute(Route route) {
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        URI uri = null;
        if (1==route.getRouteType()) {
            // 如果配置路由type为1的话 则从注册中心获取服务地址
            uri = UriComponentsBuilder.fromUriString(route.getRouteUrl()).build().toUri();
        } else {
            uri = UriComponentsBuilder.fromHttpUrl(route.getRouteUrl()).build().toUri();
        }
        // 定义的路由唯一的id
        definition.setId(route.getRouteId());
        predicate.setName("Path");
        //路由转发地址
        predicateParams.put("pattern", route.getRoutePattern());
        predicate.setArgs(predicateParams);
        filterDefinition.setName("StripPrefix");
        filterParams.put("_genkey_0", "1");
        filterDefinition.setArgs(filterParams);
        definition.setPredicates(Arrays.asList(predicate));
        definition.setFilters(Arrays.asList(filterDefinition));
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
        log.info("路由刷新成功.............................");
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher=applicationEventPublisher;
    }
}


定义接口,当修改路由之后,需要刷新路由规则


  @RequestMapping("/refreshGw")
    public Object refreshGw() {
        return routeService.getAll();
    }


SQL如下


/*
 Navicat MySQL Data Transfer
 Source Server         : 本地
 Source Server Type    : MySQL
 Source Server Version : 80024
 Source Host           : localhost:3306
 Source Schema         : my_test
 Target Server Type    : MySQL
 Target Server Version : 80024
 File Encoding         : 65001
 Date: 15/07/2021 15:25:24
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_route
-- ----------------------------
DROP TABLE IF EXISTS `tb_route`;
CREATE TABLE `tb_route`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `routeId` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由id',
  `routeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由名称',
  `routePattern` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由转发地址',
  `routeType` tinyint NULL DEFAULT NULL COMMENT '路由类型1从注册中心获取,0-转发地址',
  `routeUrl` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '转发到的地址',
  `is_open` tinyint NULL DEFAULT NULL COMMENT '是否开放0-否1-开放',
  `is_use` tinyint NULL DEFAULT NULL COMMENT '是否可用0-否1-开放',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '动态网关表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_route
-- ----------------------------
INSERT INTO `tb_route` VALUES (1, 'member', 'xiaojie-member', '/member/**', 1, 'lb://xiaojie-member/', 1, 1);
INSERT INTO `tb_route` VALUES (3, 'baidu', 'xiaojie-baidu', '/bai/**', 0, 'http://www.baidu.com', 1, 1);
SET FOREIGN_KEY_CHECKS = 1;


配置文件如下


server:
  port: 82
####服务网关名称
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
          ###路由策略
                discovery:
                  locator:
                    enabled: true
    nacos:
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat
    initialSize: 10
    maxActive: 50
    maxOpenPreparedStatements: 20
    maxWait: 60000
    minEvictableIdleTimeMillis: 300000
    minIdle: 10
    password: root
    poolPreparedStatements: true
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    validationQuery: select 1


七、网关解决跨域


package com.xiaojie.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * @Description: 解决微服务的跨域问题
 * @Author: yan
 * @Date: 2021/5/12 23:18
 * @return: null
 **/
@Component
public class CrossOriginFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
        return chain.filter(exchange);
    }
}


八、网关实现限流


使用的算法是令牌桶 Redis+Lua


令牌流与令牌桶


系统会以一定的速度生成令牌,并将其放置到令牌桶中,可以将令牌桶想象成一个缓冲区(可以用队列这种数据结构来实现),当缓冲区填满的时候,新生成的令牌会被扔掉。 这里有两个变量很重要:


第一个是生成令牌的速度,一般称为 rate 。比如,我们设定 rate = 2 ,即每秒钟生成 2 个令牌,也就是每 1/2 秒生成一个令牌;

第二个是令牌桶的大小,一般称为 burst 。比如,我们设定 burst = 10 ,即令牌桶最大只能容纳 10 个令牌。


数据流


数据流是真正的进入系统的流量,对于接口来讲,如果平均每秒钟会调用2次,则认为速率为 2次/s 。


算法原理


系统接收到一个单位数据(对于网络传输,可以是一个包或者一个字节;对于微服务,可以是一个请求)后,从令牌桶中取出一个令牌,然后对数据或请求进行处理。如果令牌桶中没有令牌了,会直接将数据或者请求丢弃。当然,对于微服务,就不能是丢弃这么简单了:可以返回一个异常消息,用于提示用户其请求速率超过了系统限制。


有以下三种情形可能发生:


数据流的速率 等于 令牌流的速率。这种情况下,每个到来的数据包或者请求都能对应一个令牌,然后无延迟地通过队列;

数据流的速率 小于 令牌流的速率。通过队列的数据包或者请求只消耗了一部分令牌,剩下的令牌会在令牌桶里积累下来,直到桶被装满。剩下的令牌可以在突发请求的时候消耗掉。

数据流的速率 大于 令牌流的速率。这意味着桶里的令牌很快就会被耗尽。导致服务中断一段时间,如果数据包或者请求持续到来,将发生丢包或者拒绝响应。

比如前面举的例子,生成令牌的速率和令牌桶的大小分别为 rate = 2, burst = 10 ,则系统能承受的突发请求速率为 10次/s ,平均请求速率为 2次/s 。


摘自:https://blog.csdn.net/xgw1010/article/details/107595141


代码如下

package com.xiaojie.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
 * @Description:
 * @author: xiaojie
 * @date: 2021.07.16
 */
@Configuration
public class MyKeyResolver {
    /*
     *  ip限流 每一个ip只能在限流的规则内访问的规则,超出规则,进行限流
     * @todo
     * @author xiaojie
     * @date 2021/7/16 9:44
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
//    @Bean("ipKeyResolver")
//    @Primary
//    public KeyResolver ipKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
//    }
    /*
     *  用户限流 每一个用户的限流规则 访问时需要携带userId 参数
     * @todo
     * @author xiaojie
     * @date 2021/7/16 9:44
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
    @Bean("userKeyResolver")
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }
    /*
     * 接口限流 根据接口路径限流
     * @todo
     * @author xiaojie 
     * @date 2021/7/16 9:45 
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
//    @Bean("apiKeyResolver")
//    KeyResolver apiKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getPath().value());
//    }
}


路由配置如下


server:
  port: 82
####服务网关名称
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
          ###路由策略
#          routes:
#            ###根据我们的服务名称查找地址实现调用
#            - id: member
#              #uri: http://127.0.0.1:8090
#              uri: lb://xiaojie-member/
#              filters:
#                - StripPrefix=1
#              ###匹配规则
#              predicates:
#                - Path=/member/*
                routes:
                  - id: requestratelimiter_route
                    uri: lb://xiaojie-member/
                    filters:
                      - name: RequestRateLimiter
                        args:
                          #允许用户每秒执行多少请求,而没有任何丢弃的请求。这是令牌桶填充的速率
                          redis-rate-limiter.replenishRate: 1
                          #允许用户在一秒内执行的最大请求数。这是令牌桶可以容纳的令牌数量。将此值设置为零会阻止所有请求。
                          redis-rate-limiter.burstCapacity: 1
                          #每个请求从存储桶中获取的令牌数量,默认为 1
                          redis-rate-limiter.requestedTokens: 1
                          key-resolver:
                            - "#{@userKeyResolver}" #根据用户限流
                      - StripPrefix=1
                    predicates:
                      - Path=/member/*
                #从注册中心获取服务
                discovery:
                  locator:
                    enabled: true
    nacos:
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat
    initialSize: 10
    maxActive: 50
    maxOpenPreparedStatements: 20
    maxWait: 60000
    minEvictableIdleTimeMillis: 300000
    minIdle: 10
    password: root
    poolPreparedStatements: true
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    validationQuery: select 1
    #基于redis实现限流
  redis:
    host: 192.168.6.130
    password: xiaojie
    port: 6379


限流后返回429状态码


动态限流 基于Sentinel


九、设置黑白名单


package com.xiaojie.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
/**
 * @Description: 黑名单
 * @author: xiaojie
 * @date: 2021.07.16
 */
@Component
@Slf4j
public class IpFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ipAddress = getIpAddress(request);
        log.info("获取的IP地址为{}",ipAddress);
        //实际生产可以基于redis,数据库实现记录
        if ("127.0.0.1".equals(ipAddress)){
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders httpHeaders = response.getHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            String msg="已被加入黑名单";
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }
    /*
     * 获取用户真实ip 
     * @todo
     * @author xiaojie 
     * @date 2021/7/16 14:52
     * @return java.lang.String
     */
    public static String getIpAddress(ServerHttpRequest  request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }
}


参考:https://blog.csdn.net/lizz861109/article/details/108530364


https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-prefixpath-gatewayfilter-factory


https://blog.csdn.net/qq_32652767/article/details/112257082


https://blog.csdn.net/xgw1010/article/details/107595141


感谢几位大佬

相关文章
|
数据可视化 Java BI
将 Spring 微服务与 BI 工具集成:最佳实践
本文探讨了 Spring 微服务与商业智能(BI)工具集成的潜力与实践。随着微服务架构和数据分析需求的增长,Spring Boot 和 Spring Cloud 提供了构建可扩展、弹性服务的框架,而 BI 工具则增强了数据可视化与实时分析能力。文章介绍了 Spring 微服务的核心概念、BI 工具在企业中的作用,并深入分析了两者集成带来的优势,如实时数据处理、个性化报告、数据聚合与安全保障。同时,文中还总结了集成过程中的最佳实践,包括事件驱动架构、集中配置管理、数据安全控制、模块化设计与持续优化策略,旨在帮助企业构建高效、智能的数据驱动系统。
535 1
将 Spring 微服务与 BI 工具集成:最佳实践
|
存储 安全 Java
管理 Spring 微服务中的分布式会话
在微服务架构中,管理分布式会话是确保用户体验一致性和系统可扩展性的关键挑战。本文探讨了在 Spring 框架下实现分布式会话管理的多种方法,包括集中式会话存储和客户端会话存储(如 Cookie),并分析了它们的优缺点。同时,文章还涵盖了与分布式会话相关的安全考虑,如数据加密、令牌验证、安全 Cookie 政策以及服务间身份验证。此外,文中强调了分布式会话在提升系统可扩展性、增强可用性、实现数据一致性及优化资源利用方面的显著优势。通过合理选择会话管理策略,结合 Spring 提供的强大工具,开发人员可以在保证系统鲁棒性的同时,提供无缝的用户体验。
280 0
|
消息中间件 Java 数据库
Spring 微服务中的数据一致性:最终一致性与强一致性
本文探讨了在Spring微服务中实现数据一致性的策略,重点分析了最终一致性和强一致性的定义、优缺点及适用场景。结合Spring Boot与Spring Cloud框架,介绍了如何根据业务需求选择合适的一致性模型,并提供了实现建议,帮助开发者在分布式系统中确保数据的可靠性与同步性。
707 0
|
12月前
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
1160 117
|
11月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
1511 2
Spring Boot 3.x 微服务架构实战指南
|
11月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
12月前
|
监控 安全 Java
Spring Cloud 微服务治理技术详解与实践指南
本文档全面介绍 Spring Cloud 微服务治理框架的核心组件、架构设计和实践应用。作为 Spring 生态系统中构建分布式系统的标准工具箱,Spring Cloud 提供了一套完整的微服务解决方案,涵盖服务发现、配置管理、负载均衡、熔断器等关键功能。本文将深入探讨其核心组件的工作原理、集成方式以及在实际项目中的最佳实践,帮助开发者构建高可用、可扩展的分布式系统。
676 1
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1502 0
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1458 0
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
621 0