
持之以恒
能力说明:
了解变量作用域、Java类的结构,能够创建带main方法可执行的java应用,从命令行运行java程序;能够使用Java基本数据类型、运算符和控制结构、数组、循环结构书写和运行简单的Java程序。
阿里云技能认证
详细说明前言 之前介绍了 SpringBoot 整合 Mybatis 实现数据库的增删改查操作,分别给出了 xml 和注解两种实现 mapper 接口的方式;虽然注解方式干掉了 xml 文件,但是使用起来并不优雅,本文将介绍 mybats-plus 的常用实例,简化常规的 CRUD 操作。 mybatis-plus MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 学习 mybatis-plus:https://mp.baomidou.com/guide 常用实例 1. 项目搭建 1.1 pom.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency> </dependencies> 1.2 application.yaml # spring setting spring: datasource: url: jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false username: root password: zwqh@0258 1.3 实体类 UserEntity @TableName(value="t_user") public class UserEntity { @TableId(value="id",type=IdType.AUTO) private Long id; private String userName; private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } @TableName 指定数据库表名,否则默认查询表会指向 user_entity ;@TableId(value="id",type=IdType.AUTO) 指定数据库主键,否则会报错。 1.4 Dao层 UserDao 继承 BaseMapper,T表示对应实体类 public interface UserDao extends BaseMapper<UserEntity>{ } 1.5 启动类 在启动类添加 @MapperScan 就不用再 UserDao 上用 @Mapper 注解。 @SpringBootApplication @MapperScan("cn.zwqh.springboot.dao") public class SpringBootMybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMybatisPlusApplication.class, args); } } 1.6 分页插件配置 @Configuration public class MybatisPlusConfig { /** * mybatis-plus分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor page = new PaginationInterceptor(); page.setDialectType("mysql"); return page; } } 2.示例 2.1 新增 新增用户 UserEntity user=new UserEntity(); user.setUserName("朝雾轻寒"); user.setUserSex("男"); userDao.insert(user); 2.2 修改 根据id修改用户 UserEntity user=new UserEntity(); user.setUserName("朝雾轻晓"); user.setUserSex("男"); user.setId(25L); userDao.updateById(user); 根据entity条件修改用户 UserEntity user=new UserEntity(); user.setUserSex("女"); userDao.update(user,new QueryWrapper<UserEntity>().eq("user_name", "朝雾轻寒")); 2.3 查询 根据id查询用户 UserEntity user = userDao.selectById(id); 根据entity条件查询总记录数 int count = userDao.selectCount(new QueryWrapper<UserEntity>().eq("user_sex", "男")); 根据 entity 条件,查询一条记录,返回的是实体 QueryWrapper<UserEntity> queryWrapper=new QueryWrapper<UserEntity>(); UserEntity user=new UserEntity(); user.setUserName("朝雾轻寒"); user.setUserSex("男"); queryWrapper.setEntity(user); user = userDao.selectOne(queryWrapper); 如果表内有两条或以上的相同数据则会报错,可以用来判断某类数据是否已存在 根据entity条件查询返回第一个字段的值(返回id列表) QueryWrapper<UserEntity> queryWrapper=new QueryWrapper<UserEntity>(); UserEntity user=new UserEntity(); user.setUserSex("男"); queryWrapper.setEntity(user); List<Object> objs= userDao.selectObjs(queryWrapper); 根据map条件查询返回多条数据 Map<String, Object> map=new HashMap<String, Object>(); map.put("user_name", username); map.put("user_sex",sex); List<UserEntity> list = userDao.selectByMap(map); 根据entity条件查询返回多条数据(List) Map<String, Object> map=new HashMap<String, Object>(); map.put("user_sex","男"); List<UserEntity> list = userDao.selectList(new QueryWrapper<UserEntity>().allEq(map)); 根据entity条件查询返回多条数据(List> ) Map<String, Object> map=new HashMap<String, Object>(); map.put("user_sex","男"); List<Map<String, Object>> list = userDao.selectMaps(new QueryWrapper<UserEntity>().allEq(map)); 根据ID批量查询 List<Long> ids=new ArrayList<Long>(); ids.add(1L); ids.add(2L); ids.add(3L); List<UserEntity> list = userDao.selectBatchIds(ids); 主键ID列表(不能为 null 以及 empty) 分页查询 Page<UserEntity> page=userDao.selectPage(new Page<>(1,5), new QueryWrapper<UserEntity>().eq("user_sex", "男")); Page<Map<String, Object>> page=userDao.selectMapsPage(new Page<>(1,5), new QueryWrapper<UserEntity>().eq("user_sex", "男")); 需先配置分页插件bean,否则分页无效。如有pagehelper需先去除,以免冲突。 new Page<>(1,5),1表示当前页,5表示页面大小。 2.4 删除 根据id删除用户 userDao.deleteById(1); 根据entity条件删除用户 userDao.delete(new QueryWrapper<UserEntity>().eq("id", 1)); 根据map条件删除用户 Map<String, Object> map=new HashMap<String, Object>(); map.put("user_name", "zwqh"); map.put("user_sex","男"); userDao.deleteByMap(map); 根据ID批量删除 List<Long> ids=new ArrayList<Long>(); ids.add(1L); ids.add(2L); ids.add(3L); userDao.deleteBatchIds(ids); 主键ID列表(不能为 null 以及 empty) 小结 本文介绍了 mybatis-plus 相关的 Mapper层 CRUD 接口实现,其还提供了 Service层 CRUD 的相关接口,有兴趣的小伙伴可以去使用下。 mybatis-plus 真正地提升了撸码效率。 其他学习要点: mybatis-plus 条件构造器 lamda 表达式 常用注解 ... 学习地址:https://mp.baomidou.com/guide/ 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十九):集成 mybatis-plus 高效开发 原文地址: https://www.zwqh.top/article/info/33 如果文章有不足的地方,欢迎提点建议,后续会完善~ 如果文章对您有帮助,请给我点个赞哦~ 关注下我的公众号,文章持续更新中...
前言 服务治理 随着业务的发展,微服务应用也随之增加,这些服务的管理和治理会越来越难,并且集群规模、服务位置、服务命名都会发生变化,手动维护的方式极易发生错误或是命名冲突等问题。而服务治理正是为了解决这个问题,服务治理是微服务架构中最为核心和基础的模块,它主要实现各个微服务实例的自动化注册和发现。 服务注册 在服务治理框架中,都会构建一个或多个服务注册中心。 每个服务模块向注册中心登记自己所提供的服务,将主机host、端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。 服务注册中心还需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。 服务发现 服务间调用不再通过指定具体实例地址来实现,而是通过向服务名发起请求调用实现。 服务调用方需要先从服务注册中心获取所有服务的实例清单,才能实现对具体服务实例的访问。 服务调用方在发起调用时,会以某种策略取出一个具体的服务实例进行服务调用(客户端负载均衡)。 在生产环境中为了考虑性能等因素,不会采用每次都向服务注册中心获取服务的方式,并且不同的应用场景在缓存和服务剔除等机制上也会采用不同的实现策略。 Spring Cloud Eureka Spring Cloud Eureka 是基于 Netflix Eureka 来实现服务注册和发现的。它主要包括两个组件: Eureka Server(服务端):服务注册中心,支持高可用配置,依托于强一致性提供良好的服务实例可用性,服务注册中心之间可以通过异步模式互相复制各自的状态。 Eureka Client(客户端):处理服务的注册与发现,客户端可以通过注解和参数配置的方式实现注册与发现,客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约,Eureka客户端从服务端查询当前注册的服务信息并把它们缓存到本地并周期性的刷新服务状态。 Eureka 基础架构 服务注册中心(Eureka Server):服务端,提供服务注册和发现功能。 服务提供者(Service Provider):提供服务的应用,将自己提供的服务注册到 Eureka Server,供其他应用发现。 服务消费者(Service Consumer):消费者应用从 Eureka Server 获取服务列表,从而调用对应的服务(ribbon或者feign)。 基础架构图 快速搭建服务注册中心(Eureka Server) 1. 创建 Spring Boot 项目,添加依赖 <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 2. @EnableEurekaServer 注解启动服务注册中心 @SpringBootApplication @EnableEurekaServer public class SpringCloudEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaServerApplication.class, args); } } 3. 配置文件 application.properties server.port=9999 #eureka eureka.instance.hostname=127.0.0.1 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ eureka.client.register-with-eureka:当前应用为服务注册中心,所以设置为false,代表不向注册中心注册自己。 eureka.client.fetch-registry:注册中心的职责主要是维护服务实例,所以设置为false,代表不去检索当前应用的服务。 eureka.client.serviceUrl.defaultZone:用于与 Eureka Server 交互的地址,注册服务和发现服务都需要依赖这个地址。 4.启动应用,访问:http://127.0.0.1:9999/ 可以看到 Eureka 的信息面板,其中 Instances currently registered with Eureka 中列表显示 No instances available,说明该注册中心还没有注册任何服务。 服务提供者(Service Provider) 1. 创建 Spring Boot 项目,添加依赖 <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 2. @EnableDiscoveryClient 注解启动 Eureka 客户端 @SpringBootApplication //@EnableEurekaClient 该注解在采用eureka作为注册中心时使用,场景较为单一 @EnableDiscoveryClient //场景更为广泛 public class SpringCloudEurekaServiceApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaServiceApplication.class, args); } } @EnableEurekaClient 和 @EnableDiscoveryClient 在当前示例中使用效果好是一样的,@EnableEurekaClient 注解在采用eureka作为注册中心时使用,场景较为单一,@EnableDiscoveryClient 场景更为广泛。 3. 配置文件 application.properties server.port=8888 spring.application.name=spring-cloud-eureka-service #info 应用信息 info.app.name=spring-cloud-eureka-service info.app.version=v1.0.0 info.app.description=spring-cloud-eureka-service #eureka eureka.instance.hostname=127.0.0.1 #每隔5s心跳一次,证明本服务还活着 eureka.instance.lease-renewal-interval-in-seconds=5 #本服务10s内没有心跳,就将该服务从服务端剔除 eureka.instance.lease-expiration-duration-in-seconds=10 eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9999/eureka/ eureka.instance.lease-renewal-interval-in-seconds:设置心跳间隔秒数 eureka.instance.lease-expiration-duration-in-seconds:设置秒数内无心跳,则剔除服务 4. 启动应用,访问:http://127.0.0.1:9999/ 在服务注册中心的控制台中我们可以看到如下输出,说明服务被注册成功了。 c.n.e.registry.AbstractInstanceRegistry : Registered instance SPRING-CLOUD-EUREKA-SERVICE/192.168.101.201:spring-cloud-eureka-service:8888 with status UP (replication=false) 而在 Eureka 的信息面板上,在 Instances currently registered with Eureka 列表中同样可以看到服务的注册信息。如下图: 高可用注册中心(集群) 上面介绍了单节点模式的服务注册中心,不过在实际生产环境中,通常不会采用这种模式。在分布式系统中,服务注册中心是非常重要的组成部分,如果是单节点模式,发生故障的话将会是毁灭性的灾害。所以为了维护服务的高可用性,通常采用集群的解决方案。 Eureka 的服务治理设计中,所有的节点既是服务提供方,也是服务消费方,服务注册中心也不例外。Eureka 通过互相注册服务的方式,以实现服务清单的互相同步,达到高可用的效果。 双节点注册中心 搭建服务注册中心 A,配置文件如下: server.port=9991 spring.application.name=eureka-server spring.profiles.active=nodea #eureka eureka.instance.hostname=nodea #设置微服务调用地址为IP优先(缺省为false) #eureka.instance.prefer-ip-address=true eureka.client.serviceUrl.defaultZone=http://nodeb:9992/eureka/ 搭建服务注册中心 B,配置文件如下: server.port=9992 spring.application.name=eureka-server spring.profiles.active=nodeb #eureka eureka.instance.hostname=nodeb #设置微服务调用地址为IP优先(缺省为false) #eureka.instance.prefer-ip-address=true eureka.client.serviceUrl.defaultZone=http://nodea:9991/eureka/ 在 /etc/hosts(windows系统路径为 C:WindowsSystem32driversetchosts) 文件中添加 nodea 和 nodeb 的转换,如下: 127.0.0.1 nodea 127.0.0.1 nodeb 启动两个项目,分别访问http://nodea:9991/和http://nodeb:9992/,我们可以看到两个节点都已经被注册,如下图所示: 搭建完多节点服务注册中心之后,服务提供者也需要做一些简单的配置,以上面的服务提供者为例,修改如下: eureka.client.serviceUrl.defaultZone=http://nodea:9991/eureka/,http://nodeb:9992/eureka/ 启动项目后,访问两个服务注册中心,我们看到服务被注册到这两个节点内。 这时我们关闭服务注册中心节点 A,我们可以看到服务注册中心节点 B 依然可以提供服务,而节点 A 从 available-replicas(可以分片) 变为 unavailable-replicas(不可用分片)。 服务消费者(Service Consumer) 使用 Ribbon 调用服务 1. pom 相关依赖配置 <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 2. 配置文件 application.properties spring.application.name=spring-cloud-ribbon-consumer server.port=8081 eureka.client.serviceUrl.defaultZone=http://nodea:9991/eureka/,http://nodeb:9992/eureka/ 3. 启动类配置 通过 @EnableDiscoveryClient 注解将应用注册为 Eureka 客户端,获得服务发现能力。 创建 RestTemplate 的 Spring Bean 实例用来调用服务。 通过 @LoadBalanced 注解来开启客户端的负载均衡。 @SpringBootApplication @EnableDiscoveryClient public class SpringCloudRibbonConsumerApplication { @Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(SpringCloudRibbonConsumerApplication.class, args); } } 4. ConsumerController 来实现服务调用 @RestController public class ConsumerController { @Autowired RestTemplate restTemplate; @RequestMapping("/test") public String test() { return restTemplate.getForEntity("http://spring-cloud-eureka-service/test", String.class).getBody(); } } spring-cloud-eureka-service 为服务注册中心的应用名称,大小写均可。 使用 Feign 调用服务 1. pom 相关依赖配置 <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 2. 配置文件 application.properties spring.application.name=spring-cloud-feign-consumer server.port=8080 eureka.client.serviceUrl.defaultZone=http://nodea:9991/eureka/,http://nodeb:9992/eureka/ 3. 启动类配置 通过 @EnableDiscoveryClient 注解将应用注册为 Eureka 客户端,获得服务发现能力。 通过 @EnableFeignClients 注解来启用feign进行远程调用。 @SpringBootApplication @EnableDiscoveryClient//启用服务注册与发现 @EnableFeignClients//启用feign进行远程调用 public class SpringCloudFeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudFeignConsumerApplication.class, args); } } 4. 实现服务调用接口 @FeignClient(name = "spring-cloud-eureka-service") public interface TestService { @RequestMapping("/test") public String test(); } spring-cloud-eureka-service 为服务注册中心的应用名称,大小写均可。 此接口中的方法和远程服务中contoller中的方法名和参数需保持一致。 5. ConsumerController 来实现服务调用 @RestController public class ConsumerController { @Autowired private TestService testService; @RequestMapping("/test") public String test() { return testService.test(); } } 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Cloud(二):Eureka 服务注册中心 原文地址: https://www.zwqh.top/article/info/28 如果文章有不足的地方,欢迎提点建议,后续会完善~ 如果文章对您有帮助,请给我点个赞哦~ 关注下我的公众号,文章持续更新中...
前言 在企业项目开发中,对系统的安全和权限控制往往是必需的,常见的安全框架有 Spring Security、Apache Shiro 等。本文主要简单介绍一下 Spring Security,再通过 Spring Boot 集成开一个简单的示例。 Spring Security 什么是 Spring Security? Spring Security 是一种基于 Spring AOP 和 Servlet 过滤器 Filter 的安全框架,它提供了全面的安全解决方案,提供在 Web 请求和方法调用级别的用户鉴权和权限控制。 Web 应用的安全性通常包括两方面:用户认证(Authentication)和用户授权(Authorization)。 用户认证指的是验证某个用户是否为系统合法用户,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证。 用户授权指的是验证某个用户是否有权限执行某个操作。 2.原理 Spring Security 功能的实现主要是靠一系列的过滤器链相互配合来完成的。以下是项目启动时打印的默认安全过滤器链(集成5.2.0): [ org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5054e546, org.springframework.security.web.context.SecurityContextPersistenceFilter@7b0c69a6, org.springframework.security.web.header.HeaderWriterFilter@4fefa770, org.springframework.security.web.csrf.CsrfFilter@6346aba8, org.springframework.security.web.authentication.logout.LogoutFilter@677ac054, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@51430781, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4203d678, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@625e20e6, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@19628fc2, org.springframework.security.web.session.SessionManagementFilter@471f8a70, org.springframework.security.web.access.ExceptionTranslationFilter@3e1eb569, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3089ab62 ] WebAsyncManagerIntegrationFilter SecurityContextPersistenceFilter HeaderWriterFilter CsrfFilter LogoutFilter UsernamePasswordAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor 详细解读可以参考:https://blog.csdn.net/dushiwodecuo/article/details/78913113 3.核心组件 SecurityContextHolder 用于存储应用程序安全上下文(Spring Context)的详细信息,如当前操作的用户对象信息、认证状态、角色权限信息等。默认情况下,SecurityContextHolder 会使用 ThreadLocal 来存储这些信息,意味着安全上下文始终可用于同一执行线程中的方法。 获取有关当前用户的信息 因为身份信息与线程是绑定的,所以可以在程序的任何地方使用静态方法获取用户信息。例如获取当前经过身份验证的用户的名称,代码如下: Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); } 其中,getAuthentication() 返回认证信息,getPrincipal() 返回身份信息,UserDetails 是对用户信息的封装类。 Authentication 认证信息接口,集成了 Principal 类。该接口中方法如下: 接口方法 功能说明 getAuthorities() 获取权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串 getCredentials() 获取用户提交的密码凭证,用户输入的密码字符窜,在认证过后通常会被移除,用于保障安全 getDetails() 获取用户详细信息,用于记录 ip、sessionid、证书序列号等值 getPrincipal() 获取用户身份信息,大部分情况下返回的是 UserDetails 接口的实现类,是框架中最常用的接口之一 AuthenticationManager 认证管理器,负责验证。认证成功后,AuthenticationManager 返回一个填充了用户认证信息(包括权限信息、身份信息、详细信息等,但密码通常会被移除)的 Authentication 实例。然后再将 Authentication 设置到 SecurityContextHolder 容器中。 AuthenticationManager 接口是认证相关的核心接口,也是发起认证的入口。但它一般不直接认证,其常用实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider> 列表,存放里多种认证方式,默认情况下,只需要通过一个 AuthenticationProvider 的认证,就可被认为是登录成功。 UserDetailsService 负责从特定的地方加载用户信息,通常是通过JdbcDaoImpl从数据库加载实现,也可以通过内存映射InMemoryDaoImpl实现。 UserDetails 该接口代表了最详细的用户信息。该接口中方法如下: 接口方法 功能说明 getAuthorities() 获取授予用户的权限 getPassword() 获取用户正确的密码,这个密码在验证时会和 Authentication 中的 getCredentials() 做比对 getUsername() 获取用于验证的用户名 isAccountNonExpired() 指示用户的帐户是否已过期,无法验证过期的用户 isAccountNonLocked() 指示用户的账号是否被锁定,无法验证被锁定的用户 isCredentialsNonExpired() 指示用户的凭据(密码)是否已过期,无法验证凭证过期的用户 isEnabled() 指示用户是否被启用,无法验证被禁用的用户 Spring Security 实战 1.系统设计 本文主要使用 Spring Security 来实现系统页面的权限控制和安全认证,本示例不做详细的数据增删改查,sql 可以在完整代码里下载,主要是基于数据库对页面 和 ajax 请求做权限控制。 1.1 技术栈 编程语言:Java 编程框架:Spring、Spring MVC、Spring Boot ORM 框架:MyBatis 视图模板引擎:Thymeleaf 安全框架:Spring Security(5.2.0) 数据库:MySQL 前端:Layui、JQuery 1.2 功能设计 实现登录、退出 实现菜单 url 跳转的权限控制 实现按钮 ajax 请求的权限控制 防止跨站请求伪造(CSRF)攻击 1.3 数据库层设计 t_user 用户表 字段 类型 长度 是否为空 说明 id int 8 否 主键,自增长 username varchar 20 否 用户名 password varchar 255 否 密码 t_role 角色表 字段 类型 长度 是否为空 说明 id int 8 否 主键,自增长 role_name varchar 20 否 角色名称 t_menu 菜单表 字段 类型 长度 是否为空 说明 id int 8 否 主键,自增长 menu_name varchar 20 否 菜单名称 menu_url varchar 50 是 菜单url(Controller 请求路径) t_user_roles 用户权限表 字段 类型 长度 是否为空 说明 id int 8 否 主键,自增长 user_id int 8 否 用户表id role_id int 8 否 角色表id t_role_menus 权限菜单表 字段 类型 长度 是否为空 说明 id int 8 否 主键,自增长 role_id int 8 否 角色表id menu_id int 8 否 菜单表id 实体类这里不详细列了。 2.代码实现 2.0 相关依赖 <dependencies> <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> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- mysql 数据库驱动. --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- alibaba fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> 2.1 继承 WebSecurityConfigurerAdapter 自定义 Spring Security 配置 /** prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..] secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用 jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用. */ @Configurable @EnableWebSecurity //开启 Spring Security 方法级安全注解 @EnableGlobalMethodSecurity @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private CustomAccessDeniedHandler customAccessDeniedHandler; @Autowired private UserDetailsService userDetailsService; /** * 静态资源设置 */ @Override public void configure(WebSecurity webSecurity) { //不拦截静态资源,所有用户均可访问的资源 webSecurity.ignoring().antMatchers( "/", "/css/**", "/js/**", "/images/**", "/layui/**" ); } /** * http请求设置 */ @Override public void configure(HttpSecurity http) throws Exception { //http.csrf().disable(); //注释就是使用 csrf 功能 http.headers().frameOptions().disable();//解决 in a frame because it set 'X-Frame-Options' to 'DENY' 问题 //http.anonymous().disable(); http.authorizeRequests() .antMatchers("/login/**","/initUserData")//不拦截登录相关方法 .permitAll() //.antMatchers("/user").hasRole("ADMIN") // user接口只有ADMIN角色的可以访问 // .anyRequest() // .authenticated()// 任何尚未匹配的URL只需要验证用户即可访问 .anyRequest() .access("@rbacPermission.hasPermission(request, authentication)")//根据账号权限访问 .and() .formLogin() .loginPage("/") .loginPage("/login") //登录请求页 .loginProcessingUrl("/login") //登录POST请求路径 .usernameParameter("username") //登录用户名参数 .passwordParameter("password") //登录密码参数 .defaultSuccessUrl("/main") //默认登录成功页面 .and() .exceptionHandling() .accessDeniedHandler(customAccessDeniedHandler) //无权限处理器 .and() .logout() .logoutSuccessUrl("/login?logout"); //退出登录成功URL } /** * 自定义获取用户信息接口 */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } /** * 密码加密算法 * @return */ @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } 2.2 自定义实现 UserDetails 接口,扩展属性 public class UserEntity implements UserDetails { /** * */ private static final long serialVersionUID = -9005214545793249372L; private Long id;// 用户id private String username;// 用户名 private String password;// 密码 private List<Role> userRoles;// 用户权限集合 private List<Menu> roleMenus;// 角色菜单集合 private Collection<? extends GrantedAuthority> authorities; public UserEntity() { } public UserEntity(String username, String password, Collection<? extends GrantedAuthority> authorities, List<Menu> roleMenus) { this.username = username; this.password = password; this.authorities = authorities; this.roleMenus = roleMenus; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<Role> getUserRoles() { return userRoles; } public void setUserRoles(List<Role> userRoles) { this.userRoles = userRoles; } public List<Menu> getRoleMenus() { return roleMenus; } public void setRoleMenus(List<Menu> roleMenus) { this.roleMenus = roleMenus; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } 2.3 自定义实现 UserDetailsService 接口 /** * 获取用户相关信息 * @author charlie * */ @Service public class UserDetailServiceImpl implements UserDetailsService { private Logger log = LoggerFactory.getLogger(UserDetailServiceImpl.class); @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Autowired private MenuDao menuDao; @Override public UserEntity loadUserByUsername(String username) throws UsernameNotFoundException { // 根据用户名查找用户 UserEntity user = userDao.getUserByUsername(username); System.out.println(user); if (user != null) { System.out.println("UserDetailsService"); //根据用户id获取用户角色 List<Role> roles = roleDao.getUserRoleByUserId(user.getId()); // 填充权限 Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getRoleName())); } //填充权限菜单 List<Menu> menus=menuDao.getRoleMenuByRoles(roles); return new UserEntity(username,user.getPassword(),authorities,menus); } else { System.out.println(username +" not found"); throw new UsernameNotFoundException(username +" not found"); } } } 2.4 自定义实现 URL 权限控制 /** * RBAC数据模型控制权限 * @author charlie * */ @Component("rbacPermission") public class RbacPermission{ private AntPathMatcher antPathMatcher = new AntPathMatcher(); public boolean hasPermission(HttpServletRequest request, Authentication authentication) { Object principal = authentication.getPrincipal(); boolean hasPermission = false; if (principal instanceof UserEntity) { // 读取用户所拥有的权限菜单 List<Menu> menus = ((UserEntity) principal).getRoleMenus(); System.out.println(menus.size()); for (Menu menu : menus) { if (antPathMatcher.match(menu.getMenuUrl(), request.getRequestURI())) { hasPermission = true; break; } } } return hasPermission; } } 2.5 实现 AccessDeniedHandler 自定义处理无权请求 /** * 处理无权请求 * @author charlie * */ @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { private Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class); @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { boolean isAjax = ControllerTools.isAjaxRequest(request); System.out.println("CustomAccessDeniedHandler handle"); if (!response.isCommitted()) { if (isAjax) { String msg = accessDeniedException.getMessage(); log.info("accessDeniedException.message:" + msg); String accessDenyMsg = "{\"code\":\"403\",\"msg\":\"没有权限\"}"; ControllerTools.print(response, accessDenyMsg); } else { request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException); response.setStatus(HttpStatus.FORBIDDEN.value()); RequestDispatcher dispatcher = request.getRequestDispatcher("/403"); dispatcher.forward(request, response); } } } public static class ControllerTools { public static boolean isAjaxRequest(HttpServletRequest request) { return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); } public static void print(HttpServletResponse response, String msg) throws IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); } } } 2.6 相关 Controller 登录/退出跳转 /** * 登录/退出跳转 * @author charlie * */ @Controller public class LoginController { @GetMapping("/login") public ModelAndView login(@RequestParam(value = "error", required = false) String error, @RequestParam(value = "logout", required = false) String logout) { ModelAndView mav = new ModelAndView(); if (error != null) { mav.addObject("error", "用户名或者密码不正确"); } if (logout != null) { mav.addObject("msg", "退出成功"); } mav.setViewName("login"); return mav; } } 登录成功跳转 @Controller public class MainController { @GetMapping("/main") public ModelAndView toMainPage() { //获取登录的用户名 Object principal= SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username=null; if(principal instanceof UserDetails) { username=((UserDetails)principal).getUsername(); }else { username=principal.toString(); } ModelAndView mav = new ModelAndView(); mav.setViewName("main"); mav.addObject("username", username); return mav; } } 用于不同权限页面访问测试 /** * 用于不同权限页面访问测试 * @author charlie * */ @Controller public class ResourceController { @GetMapping("/publicResource") public String toPublicResource() { return "resource/public"; } @GetMapping("/vipResource") public String toVipResource() { return "resource/vip"; } } 用于不同权限ajax请求测试 /** * 用于不同权限ajax请求测试 * @author charlie * */ @RestController @RequestMapping("/test") public class HttptestController { @PostMapping("/public") public JSONObject doPublicHandler(Long id) { JSONObject json = new JSONObject(); json.put("code", 200); json.put("msg", "请求成功" + id); return json; } @PostMapping("/vip") public JSONObject doVipHandler(Long id) { JSONObject json = new JSONObject(); json.put("code", 200); json.put("msg", "请求成功" + id); return json; } } 2.7 相关 html 页面 登录页面 <form class="layui-form" action="/login" method="post"> <div class="layui-input-inline"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> <input type="text" name="username" required placeholder="用户名" autocomplete="off" class="layui-input"> </div> <div class="layui-input-inline"> <input type="password" name="password" required placeholder="密码" autocomplete="off" class="layui-input"> </div> <div class="layui-input-inline login-btn"> <button id="btnLogin" lay-submit lay-filter="*" class="layui-btn">登录</button> </div> <div class="form-message"> <label th:text="${error}"></label> <label th:text="${msg}"></label> </div> </form> 防止跨站请求伪造(CSRF)攻击 退出系统 <form id="logoutForm" action="/logout" method="post" style="display: none;"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"> </form> <a href="javascript:document.getElementById('logoutForm').submit();">退出系统</a> ajax 请求页面 <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" id="hidCSRF"> <button class="layui-btn" id="btnPublic">公共权限请求按钮</button> <br> <br> <button class="layui-btn" id="btnVip">VIP权限请求按钮</button> <script type="text/javascript" th:src="@{/js/jquery-1.8.3.min.js}"></script> <script type="text/javascript" th:src="@{/layui/layui.js}"></script> <script type="text/javascript"> layui.use('form', function() { var form = layui.form; $("#btnPublic").click(function(){ $.ajax({ url:"/test/public", type:"POST", data:{id:1}, beforeSend:function(xhr){ xhr.setRequestHeader('X-CSRF-TOKEN',$("#hidCSRF").val()); }, success:function(res){ alert(res.code+":"+res.msg); } }); }); $("#btnVip").click(function(){ $.ajax({ url:"/test/vip", type:"POST", data:{id:2}, beforeSend:function(xhr){ xhr.setRequestHeader('X-CSRF-TOKEN',$("#hidCSRF").val()); }, success:function(res){ alert(res.code+":"+res.msg); } }); }); }); </script> 2.8 测试 测试提供两个账号:user 和 admin (密码与账号一样) 由于 admin 作为管理员权限,设置了全部的访问权限,这里只展示 user 的测试结果。 完整代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十八):集成 Spring Security-登录认证和权限控制 原文地址: https://www.zwqh.top/article/info/27 如果文章有不足的地方,欢迎提点,后续会完善。 如果文章对您有帮助,请给我点个赞,请扫码关注下我的公众号,文章持续更新中...
Admin 简介 Spring Boot Admin 是 Spring Boot 应用程序运行状态监控和管理的后台界面。最新UI使用vue.js重写里。 Spring Boot Admin 为已注册的应用程序提供了丰富的监控运维功能。如下: 显示健康状况 显示应用运行时的详细信息,如:JVM 和内存指标等 计数器和测量指标 数据源度量 缓存度量 跟踪和下载日志文件 查看 jvm 系统和环境属性 一键管理loglevel 管理执行 JMX-beans 查看线程转储 查看跟踪信息 Hystrix-Dashboard集成(2.X版本已删除集成) 下载 heapdump 状态更改通知(支持:电子邮件、Slack、Hipchat等) 状态更改事件日志(非永久性) 更多可以通过考文档详细了解。 Admin 使用及配置 Spring Boot Admin 服务端 项目依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- admin-server --> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.1.6</version> </dependency> 配置启动 Admin Server @SpringBootApplication @EnableAutoConfiguration @EnableAdminServer public class SpringBootAdminApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAdminApplication.class, args); } } application.properties 配置 server.port=9000 spring.application.name=Spring Boot Admin Web 测试 启动项目,通过浏览器访问 http://127.0.0.1:9000 Spring Boot Admin 客户端 这里以上面是 Spring Boot Actuator 项目为例 项目依赖 <!-- admin-client --> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.1.6</version> </dependency> application.properties 配置 #设置 Admin Server 地址 server.port=8080 spring.application.name=Spring Boot Actuator Demo spring.boot.admin.client.url=http://127.0.0.1:9000 测试 启动项目,通过浏览器访问 http://127.0.0.1:9000,我们会看到 Spring Boot Admin 的管理界面中 applications 会显示相应的客户端应用,点击应用进入详细的监控界面。 Spring Boot Admin 配置属性 Spring Boot Admin Server 配置属性详解 属性 描述 默认值 spring.boot.admin.context-path 上下文路径在应为Admin Server的静态资产和API提供服务的路径的前面加上前缀。相对于Dispatcher-Servlet / spring.boot.admin.monitor.status-interval 更新client端状态的时间间隔,单位是毫秒 10000 spring.boot.admin.monitor.status-lifetime client端状态的生命周期,该生命周期内不会更新client状态,单位是毫秒 10000 spring.boot.admin.monitor.connect-timeout 查询client端状态信息时的连接超时,单位是毫秒 2000 spring.boot.admin.monitor.read-timeout 查询client端状态信息时的读取超时时间,单位是毫秒 10000 spring.boot.admin.monitor.default-retries 失败请求的默认重试次数。Modyfing请求(PUT,POST,PATCH,DELETE)将永远不会重试 0 spring.boot.admin.monitor.retries.* 键值对,具有每个endpointId的重试次数。默认为默认重试。Modyfing请求(PUT,POST,PATCH,DELETE)将永远不会重试 spring.boot.admin.metadata-keys-to-sanitize 要被过滤掉的元数据(当与正则表达式相匹配时,这些数据会在输出的json数据中过滤掉) ".password$", ".*secret$", ".*key$", ".$token$", ".credentials.", ".*vcap_services$" spring.boot.admin.probed-endpoints 要获取的client的端点信息 "health", "env", "metrics", "httptrace:trace", "threaddump:dump", "jolokia", "info", "logfile", "refresh", "flyway", "liquibase", "heapdump", "loggers", "auditevents" spring.boot.admin.instance-proxy.ignored-headers 向client发起请求时不会被转发的headers信息 "Cookie", "Set-Cookie", "Authorization" spring.boot.admin.ui.public-url 用于在ui中构建基本href的基本URL 如果在反向代理后面运行(使用路径重写),则可用于进行正确的自我引用。如果省略了主机/端口,将从请求中推断出来 spring.boot.admin.ui.brand 导航栏中显示的品牌 <img src="assets/img/icon-spring-boot-admin.svg"><span>Spring Boot Admin</span> spring.boot.admin.ui.title 页面标题 "Spring Boot Admin" spring.boot.admin.ui.favicon 用作默认图标的图标,用于桌面通知的图标 "assets/img/favicon.png" spring.boot.admin.ui.favicon-danger 当一项或多项服务关闭并用于桌面通知时,用作网站图标 "assets/img/favicon-danger.png" Spring Boot Admin Client 配置属性详解 属性 描述 默认值 spring.boot.admin.client.enabled 启用Spring Boot Admin Client true spring.boot.admin.client.url 要注册的server端的url地址。如果要同时在多个server端口注册,则用逗号分隔各个server端的url地址 spring.boot.admin.client.api-path 管理服务器上注册端点的Http路径 "instances" spring.boot.admin.client.username 如果server端需要进行认证时,该属性用于配置用户名 spring.boot.admin.client.password 如果server端需要进行认证时,该属性用于配置密码 spring.boot.admin.client.period 重复注册的时间间隔(以毫秒为单位) 10000 spring.boot.admin.client.connect-timeout 连接注册的超时时间(以毫秒为单位) 5000 spring.boot.admin.client.read-timeout 读取注册超时(以毫秒为单位) 5000 spring.boot.admin.client.auto-registration 如果设置为true,则在应用程序准备就绪后会自动安排注册应用程序的定期任务 true spring.boot.admin.client.auto-deregistration 当上下文关闭时,切换为在Spring Boot Admin服务器上启用自动解密。如果未设置该值,并且在检测到正在运行的CloudPlatform时,该功能处于活动状态 null spring.boot.admin.client.register-once 如果设置为true,则客户端将仅向一台管理服务器注册(由定义spring.boot.admin.instance.url);如果该管理服务器出现故障,将自动向下一个管理服务器注册。如果为false,则会向所有管理服务器注册 true spring.boot.admin.client.instance.health-url 要注册的health-url地址。如果可访问URL不同(例如Docker),则可以覆盖。在注册表中必须唯一 默认该属性值与management-url 以及endpoints.health.id有关。比如工程中该值为:healthUrl=http://127.0.0.1:8080/actuator/health,其中http://127.0.0.1:8080/actuator是management-url,health是endpoints.health.id spring.boot.admin.client.instance.management-base-url 用于计算要注册的管理URL的基本URL。该路径是在运行时推断的,并附加到基本URL 默认该属性值与management.port, service-url 以及server.servlet-path有关,如工程中该值为http://127.0.0.1:8080,其中8080端口是配置的获取actuator信息的端口。127.0.0.1是设置的service-url值,如果没有设置service-url的话,则为配置的server.servlet-path值(项目的启动路径) spring.boot.admin.client.instance.management-url 要注册的management-url。如果可访问的URL不同(例如Docker),则可以覆盖 默认该属性值与management-base-url 和 management.context-path两个属性值有关,如 managementUrl=http://127.0.0.1:8080/actuator,其中http://127.0.0.1:8080为management-base-url,/actuator是management.context-path spring.boot.admin.client.instance.service-base-url 用于计算要注册的服务URL的基本URL。该路径是在运行时推断的,并附加到基本URL 默认该属性值与hostname, server.port有关,如http://127.0.0.1:8080,其中8080端口是配置的server.port。127.0.0.1是client所在服务器的hostname spring.boot.admin.client.instance.service-url 要注册的服务网址。如果可访问的URL不同(例如Docker),则可以覆盖 默认值是基于service-base-url 和 server.context-path进行赋值 spring.boot.admin.client.instance.name 要注册的名称 默认值是配置的spring.application.name的值,如果没有配置该属性的话,默认值是spring-boot-application spring.boot.admin.client.instance.prefer-ip 在猜测的网址中使用ip地址而不是主机名。如果设置了server.address/ management.address,它将被使用。否则,InetAddress.getLocalHost()将使用从返回的IP地址 false spring.boot.admin.client.instance.metadata.* 要与此实例相关联的元数据键值对 spring.boot.admin.client.instance.metadata.tags.* 标记作为要与此实例相关联的键值对 示例代码 github 码云 文档参考 https://codecentric.github.io/spring-boot-admin/2.1.6/#faqs 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十七):应用监控之 Spring Boot Admin 使用及配置 原文地址: https://www.zwqh.top/article/info/26 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
Actuator 简介 Actuator 是 Spring Boot 提供的对应用系统的自省和监控功能。通过 Actuator,可以使用数据化的指标去度量应用的运行情况,比如查看服务器的磁盘、内存、CPU等信息,系统的线程、gc、运行状态等等。 Actuator 通常通过使用 HTTP 和 JMX 来管理和监控应用,大多数情况使用 HTTP 的方式。 Actuator 端点说明 端点 描述 auditevents 获取当前应用暴露的审计事件信息 beans 获取应用中所有的 Spring Beans 的完整关系列表 caches 获取公开可以用的缓存 conditions 获取自动配置条件信息,记录哪些自动配置条件通过和没通过的原因 configprops 获取所有配置属性,包括默认配置,显示一个所有 @ConfigurationProperties 的整理列版本 env 获取所有环境变量 flyway 获取已应用的所有Flyway数据库迁移信息,需要一个或多个 Flyway Bean liquibase 获取已应用的所有Liquibase数据库迁移。需要一个或多个 Liquibase Bean health 获取应用程序健康指标(运行状况信息) httptrace 获取HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应交换)。需要 HttpTraceRepository Bean info 获取应用程序信息 integrationgraph 显示 Spring Integration 图。需要依赖 spring-integration-core loggers 显示和修改应用程序中日志的配置 logfile 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性) metrics 获取系统度量指标信息 mappings 显示所有@RequestMapping路径的整理列表 scheduledtasks 显示应用程序中的计划任务 sessions 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序 shutdown 关闭应用,要求endpoints.shutdown.enabled设置为true,默认为 false threaddump 获取系统线程转储信息 heapdump 返回hprof堆转储文件 jolokia 通过HTTP公开JMX bean(当Jolokia在类路径上时,不适用于WebFlux)。需要依赖 jolokia-core prometheus 以Prometheus服务器可以抓取的格式公开指标。需要依赖 micrometer-registry-prometheus Actuator 使用及配置 快速使用 项目依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> 配置文件 management.endpoints.enabled-by-default=true #启动所有端点 management.endpoints.web.exposure.include=* #自定义管理端点路径 #management.endpoints.web.base-path=/manage Spring Boot 2.X 中,Actuator 默认只开放 health 和 info 两个端点。 添加management.endpoints.web.exposure.include=*配置后启动应用,访问 http://127.0.0.1:8080/actuator 我们可以看到所有的 Actuator 端点列表。 如果将management.endpoints.enabled-by-default设置为false,则禁用所有端点,如需启用则如下: management.endpoints.enabled-by-default=false management.endpoint.info.enabled=true 禁用的端点将从应用程序上下文中完全删除。如果只想更改公开端点,使用include和exclude属性。使用如下: management.endpoints.web.exposure.include=* management.endpoints.web.exposure.exclude=env,beans management.endpoints.web.base-path=/manage 配置表示将 /actuator 路径重定义为 /manage。 常用端点详解 health 主要用来检测应用的运行状况,是使用最多的一个监控点。监控软件通常使用该接口实时监测应用运行状况,在系统出现故障时把报警信息推送给相关人员,如磁盘空间使用情况、数据库和缓存等的一些健康指标。默认情况下 health 端点是开放的,访问 http://127.0.0.1:8080/actuator/health 即可看到应用运行状态。 {"status":"UP"} 如果需要看到详细信息,则需要做添加配置: management.endpoint.health.show-details=always 访问返回信息如下: {"status":"UP","details":{"diskSpace":{"status":"UP","details":{"total":180002725888,"free":8687988736,"threshold":10485760}}}} info 查看应用信息是否在 application.properties 中配置。如我们在项目中配置是: info.app.name=Spring Boot Actuator Demo info.app.version=v1.0.0 info.app.description=Spring Boot Actuator Demo 启动项目,访问 http://127.0.0.1:8080/actuator/info 返回信息如下: {"app":{"name":"Spring Boot Actuator Demo","version":"v1.0.0","description":"Spring Boot Actuator Demo"}} env 通过 env 可以获取到所有关于当前 Spring Boot 应用程序的运行环境信息,如:操作系统信息(systemProperties)、环境变量信息、JDK 版本及 ClassPath 信息、当前启用的配置文件(activeProfiles)、propertySources、应用程序配置信息(applicationConfig)等。 可以通过 http://127.0.0.1:8080/actuator/env/{name} ,name表示想要查看的信息,可以独立显示。 beans 访问 http://127.0.0.1:8080/actuator/beans 返回部分信息如下: { "contexts": { "Spring Boot Actuator Demo": { "beans": { "endpointCachingOperationInvokerAdvisor": { "aliases": [ ], "scope": "singleton", "type": "org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor", "resource": "class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]", "dependencies": [ "environment" ] }, "defaultServletHandlerMapping": { "aliases": [ ], "scope": "singleton", "type": "org.springframework.web.servlet.HandlerMapping", "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]", "dependencies": [ ] }, ... } } } } 从返回的信息中我们可以看出主要展示了 bean 的别名、类型、是否单例、类的地址、依赖等信息。 conditions 通过 conditions 可以在应用运行时查看代码了某个配置在什么条件下生效,或者某个自动配置为什么没有生效。 访问 http://127.0.0.1:8080/actuator/conditions 返回部分信息如下: { "contexts": { "Spring Boot Actuator Demo": { "positiveMatches": { "SpringBootAdminClientAutoConfiguration": [ { "condition": "OnWebApplicationCondition", "message": "@ConditionalOnWebApplication (required) found 'session' scope" }, { "condition": "SpringBootAdminClientEnabledCondition", "message": "matched" } ], "SpringBootAdminClientAutoConfiguration#metadataContributor": [ { "condition": "OnBeanCondition", "message": "@ConditionalOnMissingBean (types: de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor; SearchStrategy: all) did not find any beans" } ], ... } } } } loggers 获取系统的日志信息。 访问 http://127.0.0.1:8080/actuator/loggers 返回部分信息如下: { "levels": [ "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" ], "loggers": { "ROOT": { "configuredLevel": "INFO", "effectiveLevel": "INFO" }, "cn": { "configuredLevel": null, "effectiveLevel": "INFO" }, "cn.zwqh": { "configuredLevel": null, "effectiveLevel": "INFO" }, "cn.zwqh.springboot": { "configuredLevel": null, "effectiveLevel": "INFO" }, ... } } mappings 查看所有 URL 映射,即所有 @RequestMapping 路径的整理列表。 访问 http://127.0.0.1:8080/actuator/mappings 返回部分信息如下: { "contexts": { "Spring Boot Actuator Demo": { "mappings": { "dispatcherServlets": { "dispatcherServlet": [ { "handler": "ResourceHttpRequestHandler [class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/], class path resource []]", "predicate": "/**/favicon.ico", "details": null }, ... ] } } } } } heapdump 访问:http://127.0.0.1:8080/actuator/heapdump会自动生成一个 GZip 压缩的 Jvm 的堆文件 heapdump,我们可以使用 JDK 自带的 Jvm 监控工具 VisualVM 打开此文件查看。如图:VisualVM下载:https://visualvm.github.io/download.html threaddump 获取系统线程的转储信息,主要展示了线程名、线程ID、线程的状态、是否等待锁资源等信息。在工作中,我们可以通过查看线程的情况来排查相关问题。 访问 http://127.0.0.1:8080/actuator/threaddump 返回部分信息如下: { "threads": [ { "threadName": "DestroyJavaVM", "threadId": 40, "blockedTime": -1, "blockedCount": 0, "waitedTime": -1, "waitedCount": 0, "lockName": null, "lockOwnerId": -1, "lockOwnerName": null, "inNative": false, "suspended": false, "threadState": "RUNNABLE", "stackTrace": [ ], "lockedMonitors": [ ], "lockedSynchronizers": [ ], "lockInfo": null }, ... ] } shutdown 开启可以接口关闭 Spring Boot 应用,要使用这个功能需要做如下配置: management.endpoint.shutdown.enabled=true 可以通过 post(仅支持 post) 请求访问 http://127.0.0.1:8080/actuator/shutdown 关闭应用。 metrics 访问 http://127.0.0.1:8080/actuator/metrics 可以获取系统度量指标信息项如下: { "names": [ "jvm.memory.max", "jvm.threads.states", "jvm.gc.pause", "http.server.requests", "process.files.max", "jvm.gc.memory.promoted", "system.load.average.1m", "jvm.memory.used", "jvm.gc.max.data.size", "jvm.memory.committed", "system.cpu.count", "logback.events", "tomcat.global.sent", "jvm.buffer.memory.used", "tomcat.sessions.created", "jvm.threads.daemon", "system.cpu.usage", "jvm.gc.memory.allocated", "tomcat.global.request.max", "tomcat.global.request", "tomcat.sessions.expired", "jvm.threads.live", "jvm.threads.peak", "tomcat.global.received", "process.uptime", "tomcat.sessions.rejected", "process.cpu.usage", "tomcat.threads.config.max", "jvm.classes.loaded", "jvm.classes.unloaded", "tomcat.global.error", "tomcat.sessions.active.current", "tomcat.sessions.alive.max", "jvm.gc.live.data.size", "tomcat.threads.current", "process.files.open", "jvm.buffer.count", "jvm.buffer.total.capacity", "tomcat.sessions.active.max", "tomcat.threads.busy", "process.start.time" ] } 对应访问 names 中的指标,可以查看具体的指标信息。如访问 http://127.0.0.1:8080/actuator/metrics/jvm.memory.used 返回信息如下: { "name": "jvm.memory.used", "description": "The amount of used memory", "baseUnit": "bytes", "measurements": [ { "statistic": "VALUE", "value": 1.16828136E8 } ], "availableTags": [ { "tag": "area", "values": [ "heap", "nonheap" ] }, { "tag": "id", "values": [ "Compressed Class Space", "PS Survivor Space", "PS Old Gen", "Metaspace", "PS Eden Space", "Code Cache" ] } ] } 示例代码 github 码云 参考文档 https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/html/production-ready-features.html 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十六):应用监控之 Spring Boot Actuator 使用及配置 原文地址: https://www.zwqh.top/article/info/25 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
前言 相信很多后端开发在项目中都会碰到要写 api 文档,不管是给前端、移动端等提供更好的对接,还是以后为了以后交接方便,都会要求写 api 文档。 而手写 api 文档的话有诸多痛点: 文档更新的时候,需要再次发送给对接人 接口太对,手写文档很难管理 接口返回的结果不明确 不能直接在线测试接口,通常需要使用工具,如 postman 等 Swagger 就很好的解决了这个问题。 Swagger 简介 Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。 官网:https://swagger.io Swagger 使用 1.相关依赖 <!--swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> 2.Swagger 配置类 @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket buildDocket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(buildApiInf()) //将api的元信息设置为包含在json resourcelisting响应中 //.host("127.0.0.1:8080") //设置ip和端口,或者域名 .select() //启动用于api选择的生成器 //.apis(RequestHandlerSelectors.any()) .apis(RequestHandlerSelectors.basePackage("cn.zwqh.springboot.controller"))//指定controller路径 .paths(PathSelectors.any()).build(); } private ApiInfo buildApiInf() { Contact contact=new Contact("朝雾轻寒","https://www.zwqh.top/","zwqh@clover1314.com"); return new ApiInfoBuilder() .title("Swagger Demo Restful API Docs")//文档标题 .description("Swagger 示例 Restful Api 文档")//文档描述 .contact(contact)//联系人 .version("1.0")//版本号 //.license("")//更新此API的许可证信息 //.licenseUrl("")//更新此API的许可证Url //.termsOfServiceUrl("")//更新服务条款URL .build(); } } 3.Spring MVC 相关配置 @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 静态资源配置(默认) */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");// 静态资源路径 registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } } 如果不添加此静态资源配置会报错,找不到相关路径 4.Model 中使用 Swagger 注解 @ApiModel(value = "UserEntity", description = "用户对象") public class UserEntity implements Serializable{ /** * */ private static final long serialVersionUID = 5237730257103305078L; @ApiModelProperty(value ="用户id",name="id",dataType="Long",required = false,example = "1",hidden = false ) private Long id; @ApiModelProperty(value ="用户名",name="userName",dataType="String",required = false,example = "关羽" ) private String userName; @ApiModelProperty(value ="用户性别",name="userSex",dataType="String",required = false,example = "男" ) private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } 5. Controller 中使用 Swagger 注解 @RestController @RequestMapping("/api") @Api(tags = { "接口分组1", "接口分组2" }) public class ApiController { @Autowired private UserDao userDao; @GetMapping("/getAllUser") @ApiOperation(value = "获取所有用户", notes = "", httpMethod = "GET", tags = "接口分组3") public List<UserEntity> getAll() { return userDao.getAll(); } @GetMapping("/getUserById") @ApiOperation(value = "根据id获取用户", notes = "id必传", httpMethod = "GET") @ApiImplicitParam(name = "id", value = "用户id",example = "1", required = true, dataType = "long", paramType = "query") public UserEntity getOne(Long id) { return userDao.getOne(id); } @PostMapping("/getUserByNameAndSex") @ApiOperation(value = "根据name和sex获取用户", notes = "", httpMethod = "POST") @ApiImplicitParams({ @ApiImplicitParam(name = "userName", value = "用户名", example = "关羽", required = true, dataType = "string", paramType = "query"), @ApiImplicitParam(name = "userSex", value = "用户性别", example = "男", required = true, dataType = "string", paramType = "query") }) public UserEntity getUserByNameAndSex(String userName, String userSex) { return userDao.getUserByNameAndSex(userName, userSex); } @PostMapping("/insertUser") @ApiOperation(value = "新增用户", notes = "传json,数据放body", httpMethod = "POST") @ApiImplicitParams({ @ApiImplicitParam(name = "body", value = "用户对象json", example = "{userName:'朝雾轻寒',userSex:'男'}", required = true) }) public String insertUser(@RequestBody String body) { System.out.println(body); UserEntity user = JSON.parseObject(body, UserEntity.class); userDao.insertUser(user); return "{code:0,msg:'success'}"; } @PostMapping("/updateUser") @ApiOperation(value = "修改用户", notes = "传json,数据放body", httpMethod = "POST") @ApiImplicitParams({ @ApiImplicitParam(name = "body", value = "用户对象json", example = "{id:23,userName:'朝雾轻寒',userSex:'女'}", required = true) }) public String updateUser(@RequestBody String body) { System.out.println(body); UserEntity user = JSON.parseObject(body, UserEntity.class); userDao.updateUser(user); return "{code:0,msg:'success'}"; } @PostMapping("/deleteUser") @ApiOperation(value = "删除用户", notes = "id必传", httpMethod = "POST") public String deleteUser(@ApiParam(name = "id", value = "用户id", required = true) Long id) { userDao.deleteUser(id); return "{code:0,msg:'success'}"; } } 5.测试 访问 http://127.0.0.1:8080/swagger-ui.html 进行接口在线测试 Swagger 常用注解 1.@Api 用于类,表示标识这个类是swagger的资源。属性如下: tags 表示说明,tags如果有多个值,会生成多个列表 value 表示说明,可以使用tags替代 2.@ApiOperation 用于方法,表示一个http请求的操作。属性如下: value 用于方法描述 notes 用于提示内容 tags 用于API文档控制的标记列表,视情况而用,可以进行独立分组 3.@ApiParam 用于方法、参数、字段说明;表示对参数的添加元数据。 name 参数名 value 参数说明 required 是否必填 4.@ApiModel 用于类,表示对类进行说明,用于参数用实体类接受。 value 对象名 description 描述 5.@ApiModelProperty 用于方法、字段,表示对model属性的说明或者数据操作更改。 value 字段说明 name 重写属性名 dataType 重写属性数据类型 required 是否必填 example 举例说明 hidden 隐藏 6.@ApiIgnore 用于类、方法、方法参数,表示这个方法或者类被忽略,不在swagger-ui.html上显示。 7.@ApiImplicitParam 用于方法,表示单独的请求参数。 name 参数名 value 参数说明 dataType 数据类型 paramType 参数类型 example 举例说明 8.@ApiImplicitParams 用于方法,包含多个 @ApiImplicitParam。 9.@ApiResponses @ApiResponse 用于类或者方法,描述操作的可能响应。 code 响应的HTTP状态代码 message 响应附带的可读消息 10.@ResponseHeader 用于方法,响应头设置。 name 响应头名称 description 头描述 response 默认响应类 void responseContainer 参考ApiOperation中配置 Swagger 导出离线 api 文档 1.导出 AsciiDocs、Markdown、Confluence 格式文档 添加依赖 <!-- swagger2markup 相关依赖 --> <dependency> <groupId>io.github.swagger2markup</groupId> <artifactId>swagger2markup</artifactId> <version>1.3.3</version> </dependency> 转换工具类 public class SwaggerUtils { private static final String url = "http://127.0.0.1:8080/v2/api-docs"; /** * 生成AsciiDocs格式文档 * @throws MalformedURLException */ public static void generateAsciiDocs() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.ASCIIDOC) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema().build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFolder(Paths.get("./docs/asciidoc/generated")); } /** * 生成AsciiDocs格式文档,并汇总成一个文件 * @throws MalformedURLException */ public static void generateAsciiDocsToFile() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.ASCIIDOC) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema() .build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFile(Paths.get("./docs/asciidoc/generated/all")); } /** * 生成Markdown格式文档 * @throws MalformedURLException */ public static void generateMarkdownDocs() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.MARKDOWN) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema() .build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFolder(Paths.get("./docs/markdown/generated")); } /** * 生成Markdown格式文档,并汇总成一个文件 * @throws MalformedURLException */ public static void generateMarkdownDocsToFile() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.MARKDOWN) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema() .build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFile(Paths.get("./docs/markdown/generated/all")); } /** * 生成Confluence格式文档 * @throws MalformedURLException */ public static void generateConfluenceDocs() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.CONFLUENCE_MARKUP) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema() .build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFolder(Paths.get("./docs/confluence/generated")); } /** * 生成Confluence格式文档,并汇总成一个文件 * @throws MalformedURLException */ public static void generateConfluenceDocsToFile() throws MalformedURLException { Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder() .withMarkupLanguage(MarkupLanguage.CONFLUENCE_MARKUP) .withOutputLanguage(Language.ZH) .withPathsGroupedBy(GroupBy.TAGS) .withGeneratedExamples() .withoutInlineSchema() .build(); Swagger2MarkupConverter.from(new URL(url)) .withConfig(config) .build() .toFile(Paths.get("./docs/confluence/generated/all")); } } 使用测试 Controller @RestController @RequestMapping("/export") @ApiIgnore public class ExportController { @RequestMapping("/ascii") public String exportAscii() throws MalformedURLException{ SwaggerUtils.generateAsciiDocs(); return "success"; } @RequestMapping("/asciiToFile") public String asciiToFile() throws MalformedURLException{ SwaggerUtils.generateAsciiDocsToFile(); return "success"; } @RequestMapping("/markdown") public String exportMarkdown() throws MalformedURLException{ SwaggerUtils.generateMarkdownDocs(); return "success"; } @RequestMapping("/markdownToFile") public String exportMarkdownToFile() throws MalformedURLException{ SwaggerUtils.generateMarkdownDocsToFile(); return "success"; } @RequestMapping("/confluence") public String confluence() throws MalformedURLException{ SwaggerUtils.generateConfluenceDocs(); return "success"; } @RequestMapping("/confluenceToFile") public String confluenceToFile() throws MalformedURLException{ SwaggerUtils.generateConfluenceDocsToFile(); return "success"; } } 2.导出 html、pdf、xml 格式 添加依赖 <!--离线文档 --> <dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <scope>test</scope> </dependency> <!--springfox-staticdocs 生成静态文档 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-staticdocs</artifactId> <version>2.6.1</version> </dependency> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>io.github.swagger2markup</groupId> <artifactId>swagger2markup-maven-plugin</artifactId> <version>1.3.1</version> <configuration> <swaggerInput>http://127.0.0.1:8080/v2/api-docs</swaggerInput> <outputDir>./docs/asciidoc/generated</outputDir> <config> <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage> </config> </configuration> </plugin> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.3</version> <!-- <version>2.0.0-RC.1</version> --> <!-- Include Asciidoctor PDF for pdf generation --> <dependencies> <dependency> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctorj-pdf</artifactId> <version>1.5.0-alpha.10.1</version> </dependency> <dependency> <groupId>org.jruby</groupId> <artifactId>jruby-complete</artifactId> <version>1.7.21</version> </dependency> </dependencies> <configuration> <sourceDirectory>./docs/asciidoc/generated</sourceDirectory> <outputDirectory>./docs/asciidoc/html</outputDirectory> <backend>html</backend> <!-- <outputDirectory>./docs/asciidoc/pdf</outputDirectory> <backend>pdf</backend> --> <headerFooter>true</headerFooter> <doctype>book</doctype> <sourceHighlighter>coderay</sourceHighlighter> <attributes> <!-- 菜单栏在左边 --> <toc>left</toc> <!-- 多标题排列 --> <toclevels>3</toclevels> <!-- 自动打数字序号 --> <sectnums>true</sectnums> </attributes> </configuration> </plugin> </plugins> </pluginManagement> </build> 可以修改此处 html 和 pdf,通过 mvn asciidoctor:process-asciidoc 可以导出相应格式文件 <outputDirectory>./docs/asciidoc/html</outputDirectory> <backend>html</backend> 执行 mvn asciidoctor:process-asciidoc 后再执行 mvn generate-resources,可在 targt/generated-docs 目录下生成 xml 格式文件。 完整代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十五):集成 Swagger2 开发 API 文档(在线+离线) 原文地址: https://www.zwqh.top/article/info/24
Logback 简介 Logback 是由 SLF4J 作者开发的新一代日志框架,用于替代 log4j。 主要特点是效率更高,架构设计够通用,适用于不同的环境。 Logback 分为三个模块:logback-core,logback-classic和logback-access。 logback-core 模块是其他两个模块的基础。 logback-classic 模块是 core 的扩展,是log4j的改进版。logback-classic 本身实现了 SLF4J API,因此可以很容易的在 logback 和其他日志框架之间来回切换,例如 log4j、java.util.logging(JUL)。 logback-access 模块集成了 Servlet 容器,提供了 HTTP 访问日志的功能。 官网:http://logback.qos.ch 中文网:http://www.logback.cn github:https://github.com/qos-ch/logback Logback 使用 1.使用 logback-spring.xml 配置 <?xml version="1.0" encoding="UTF-8"?> <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="true" scanPeriod="60 seconds" debug="false"> <!-- 定义日志根目录 --> <property name="LOG_PATH" value="/usr/local/log/" /> <!-- 定义应用名称 --> <property name="APP_NAME" value="springboot-logback" /> <!-- 应用名称 --> <contextName>${APP_NAME}</contextName> <!-- 引用 Spring Boot 中默认的 logback 配置 --> <!-- <include resource="org/springframework/boot/logging/logback/base.xml" /> --> <!-- 可以动态修改日志输出等级 --> <jmxConfigurator /> <!--输出到控制台--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>debug</level> </filter> <encoder> <pattern>%d [%t] %5p %c:%L - %m%n</pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!-- 时间滚动输出 level为 INFO 日志 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}${APP_NAME}-info.log</File> <encoder> <pattern>%d [%t] %5p %c:%L - %m%n</pattern> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录,日志按天分类压缩保存--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <FileNamePattern>${LOG_PATH}${APP_NAME}/info/%d{yyyy-MM-dd}-%i.log.gz </FileNamePattern> <!--日志文件保留天数--> <MaxHistory>30</MaxHistory> </rollingPolicy> </appender> <!-- 时间滚动输出 level为 ERROR 日志 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}${APP_NAME}-error.log</File> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>error</level> </filter> <encoder> <pattern>%d [%t] %5p %c:%L - %m%n</pattern> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录,日志按天分类压缩保存--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>256MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <FileNamePattern>${LOG_PATH}${APP_NAME}/error/%d{yyyy-MM-dd}-%i.log.gz </FileNamePattern> <!--日志文件保留天数--> <MaxHistory>30</MaxHistory> </rollingPolicy> </appender> <!-- 设置需要打印日志的包及输出级别 --> <logger name="org.springframework.web" level="INFO" /> <logger name="cn.zwqh.springboot.controller" level="TRACE" /> <!-- 使用mybatis的时候,sql语句只有在 debug 级别下才会打印 --> <logger name="cn.zwqh.springboot.dao" level="debug" /> <!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 不能设置为INHERITED或者同义词NULL。默认是DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 --> <root level="info"> <appender-ref ref="CONSOLE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </configuration> 2.使用 logback.groovy 配置 使用 groovy 配置需要添加依赖 <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.17</version> </dependency> import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.filter.ThresholdFilter import ch.qos.logback.core.ConsoleAppender import ch.qos.logback.core.rolling.RollingFileAppender import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP import ch.qos.logback.core.rolling.TimeBasedRollingPolicy import java.nio.charset.Charset import static ch.qos.logback.classic.Level.DEBUG import static ch.qos.logback.classic.Level.ERROR import static ch.qos.logback.classic.Level.INFO import static ch.qos.logback.classic.Level.TRACE scan("60 seconds") def LOG_PATH = "/usr/local/log/" def APP_NAME = "springboot-logback" context.name = "${APP_NAME}" jmxConfigurator() appender("CONSOLE", ConsoleAppender) { filter(ThresholdFilter) { level = DEBUG } encoder(PatternLayoutEncoder) { pattern = "%d [%t] %5p %c:%L - %m%n" charset = Charset.forName("UTF-8") } } appender("INFO_FILE", RollingFileAppender) { file = "${LOG_PATH}${APP_NAME}-info.log" encoder(PatternLayoutEncoder) { pattern = "%d [%t] %5p %c:%L - %m%n" charset = Charset.forName("UTF-8") } rollingPolicy(TimeBasedRollingPolicy) { timeBasedFileNamingAndTriggeringPolicy(SizeAndTimeBasedFNATP) { maxFileSize = "100MB" } fileNamePattern = "${LOG_PATH}${APP_NAME}/info/%d{yyyy-MM-dd}-%i.log.gz" maxHistory = 30 } } appender("ERROR_FILE", RollingFileAppender) { file = "${LOG_PATH}${APP_NAME}-error.log" filter(ThresholdFilter) { level = ERROR } encoder(PatternLayoutEncoder) { pattern = "%d [%t] %5p %c:%L - %m%n" charset = Charset.forName("UTF-8") } rollingPolicy(TimeBasedRollingPolicy) { timeBasedFileNamingAndTriggeringPolicy(SizeAndTimeBasedFNATP) { maxFileSize = "256MB" } fileNamePattern = "${LOG_PATH}${APP_NAME}/error/%d{yyyy-MM-dd}-%i.log.gz" maxHistory = 30 } } logger("org.springframework.web", INFO) logger("cn.zwqh.springboot.controller", TRACE) logger("cn.zwqh.springboot.dao", DEBUG) root(INFO, ["CONSOLE", "INFO_FILE", "ERROR_FILE"]) 详细的 logback.groovy 语法可以参考 http://logback.qos.ch/manual/groovy.html 。logback 也提供了 logback.xml 转 logback.groovy 的在线工具,地址:http://logback.qos.ch/translator/asGroovy.html (include 标签未作解析,所以转换前把该标签去除,否则会报错) logback.xml 配置说明 configuration 配置文件的根节点,主要包含以下三个属性: scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 contextName 设置日志上下文名称,后面输出格式中可以通过定义 %contextName 来打印日志上下文名称。 property 配置文件的变量定义,name 代表变量的名称,value 代表变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${name}”来使用变量。 jmxConfigurator 开启 JMX 的功能,可以从默认配置文件,指定文件或URL重新配置登录,列出记录器并修改记录器级别。 JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。相关文档:http://logback.qos.ch/manual/jmxConfig.html appender 日志输出组件,主要负责日志的输出以及格式化日志。常用的属性有name和class。 name:appender组件的名称,后面给logger指定appender使用。 class:appender的具体实现类。常用的有 ConsoleAppender、FileAppender、RollingFileAppender。 appender的具体实现类: ConsoleAppender:向控制台输出日志内容的组件,只要定义好encoder节点就可以使用。 FileAppender:向文件输出日志内容的组件,用法也很简单,不过由于没有日志滚动策略,一般很少使用。 RollingFileAppender:向文件输出日志内容的组件,同时可以配置日志文件滚动策略,在日志达到一定条件后生成一个新的日志文件。 Threshold filter Logback 定义的日志打印级别的过滤器。可以过滤掉指定级别以下的日志不输出到文件。 encoder charset 表示对日志进行编码。 encoder pattern %d{HH:mm:ss.SSS} —— 日志输出时间。 %thread —— 输出日志的进程名称,用方括号括起来。这个信息在 Web 应用以及异步任务处理中很有用。 %-5level —— 日志级别,使用5个字符靠左对齐。 %logger{36} —— 日志输出者的名字。 %msg —— 日志消息。 %n —— 换行符。 rollingPolicy 日志记录器的滚动策略。 FileNamePattern:定义日志的切分方式,本文把每一天的日志归档到一个文件。 MaxHistory:表示日志保留的天数,本文设置为30天。 logger 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特殊值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。 使用mybatis的时候,sql语句只有在 debug 级别下才会打印 root 必选节点,用来指定最基础的日志输出级别,只有一个level属性level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是 DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十四):日志功能 Logback 原文地址: https://www.zwqh.top/article/info/23
前言 邮件服务在开发中非常常见,比如用邮件注册账号、邮件作为找回密码的途径、用于订阅内容定期邮件推送等等,下面就简单的介绍下邮件实现方式。 邮件服务实现 1.添加依赖 <!-- 启用邮箱 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> 2.配置文件 spring.mail.host=smtp.qq.com spring.mail.username=zwqh@clover1314.com spring.mail.password=***** spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true 3.实现示例 @Service public class MailTool { @Value("${spring.mail.username}") private String from; @Autowired private JavaMailSender mailSender; /** * 发送邮件 * * @return */ public boolean send() { try { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from);//发送者 message.setTo("zwqh@clover1314.com");//接受者 message.setCc("sohuniuer@sina.com");// 抄送 message.setSubject("邮件主题"); //邮件主题 message.setText("这里是邮件内容");//邮件内容 mailSender.send(message); System.out.println("邮件发送成功"); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 发送富文本邮件 * * @return */ public boolean sendHtml() { // 使用 JavaMail 的 MimeMessage,支持更多复杂的邮件格式和内容 MimeMessage mimeMessage = mailSender.createMimeMessage(); try { // 创建 MimeMessageHelper 对象,处理 MimeMessage 辅助类 MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); // 使用辅助类 MimeMessage 设定参数 helper.setFrom(from); helper.setTo("zwqh@clover1314.com"); helper.setBcc("sohuniuer@sina.com");//密送 helper.setSubject("富文本邮件主题"); helper.setText("<h1>这是富文本邮件内容标题</h1><p style='color:red;'>这里是段落一</p><p style='color:orange;'>这里是段落二</p>", true); mailSender.send(mimeMessage); System.out.println("邮件发送成功"); return true; } catch (MessagingException e) { e.printStackTrace(); return false; } } /** * 发送富文本带附件的邮件 * @return */ public boolean sendHtmlWithAttach() { // 使用 JavaMail 的 MimeMessage,支持更多复杂的邮件格式和内容 MimeMessage mimeMessage = mailSender.createMimeMessage(); try { // 创建 MimeMessageHelper 对象,处理 MimeMessage 辅助类 MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setFrom(from); helper.setTo("zwqh@clover1314.com"); helper.setSubject("富文本带附件的邮件主题"); helper.setText("<h1>这是富文本邮件内容标题</h1><p style='color:red;'>这里是段落一</p><p style='color:orange;'>这里是段落二</p>", true); //加载文件资源作为附件 ClassPathResource file=new ClassPathResource("static/avatar2.jpg"); //添加附件,并重命名 helper.addAttachment("附件.jpg", file); mailSender.send(mimeMessage); System.out.println("邮件发送成功"); return true; } catch (MessagingException e) { e.printStackTrace(); return false; } } } 4.Controller 用于测试 @RestController public class MailController { @Autowired private MailTool mailTool; @RequestMapping("/send") public String send() { mailTool.send(); return "send success"; } @RequestMapping("/sendHtml") public String sendHtml() { mailTool.sendHtml(); return "sendHtml success"; } @RequestMapping("/sendHtmlWithAttach") public String sendHtmlWithAttach() { mailTool.sendHtmlWithAttach(); return "sendHtmlWithAttach success"; } } 5.测试效果 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十三):邮件服务 原文地址: https://www.zwqh.top/article/info/22 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
简介 定时任务是后端开发中常见的需求,主要应用场景有定期数据报表、定时消息通知、异步的后台业务逻辑处理、日志分析处理、垃圾数据清理、定时更新缓存等等。 Spring Boot 集成了一整套的定时任务工具,让我们专注于完成逻辑,剩下的基础调度工作将自动完成。 通用实现方式 实现方式 描述 java.util.Timer Timer 提供了一个 java.util.TimerTask 任务支持任务调度。该方式只能按指定频率执行,不能在指定时间运行。由于功能过于单一,使用较少。 Quartz Quartz 是一个功能比较强大的调度器,支持在指定时间运行,也可以按照指定频率执行。缺点是使用起来相对麻烦。 Spring 框架自带的 Schedule 模块 可以看做是轻量级的 Quartz 静态定时任务 @Component @EnableScheduling public class StaticScheduleJob { /** * 上次开始执行时间点后5秒再次执行 */ @Scheduled(fixedRate = 5000) public void job3() { System.out.println("执行任务job3:"+DateUtil.formatDateTime(new Date())); } /** * 上次执行完毕时间点后3秒再次执行 */ @Scheduled(fixedDelay = 3000) public void job2() { System.out.println("执行任务job2:"+DateUtil.formatDateTime(new Date())); } /** * 每隔10秒执行一次(按照 corn 表达式规则执行) */ @Scheduled(cron = "0/10 * * * * ?") public void job1() { System.out.println("执行任务job1:"+DateUtil.formatDateTime(new Date())); } } @EnableScheduling 注解启用定时调动功能 @Scheduled 参数说明: @Scheduled(fixedRate = 5000):上次开始执行时间点后5秒再次执行 @Scheduled(fixedDelay = 3000):上次执行完毕时间点后3秒再次执行 @Scheduled(cron = "0/10 ?"):每隔10秒执行一次(按照 corn 表达式规则执行) Cron 表达式 1.Cron表达式格式 {秒} {分} {时} {日} {月} {周} {年(可选)} 2.Cron 表达式字段取值范围及说明 字段 取值范围 允许的特殊字符 Seconds(秒) 0 ~ 59 , - * / Minutes(分) 0 ~ 59 , - * / Hours(时) 0 ~ 23 , - * / Day-of-Month(天) 可以用数字 1 ~ 31 中的任意一个值,但要注意特殊月份 , - * ? / L W C Month(月) 可以用 0 ~ 11 或者字符串 “JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC” 表示 , - * / Day-of-Week(每周) 可以用数字 1 ~ 7 表示(1=星期日)或者用字符串 “SUN、MON、TUE、WED、THU、FRI、SAT” 表示 , - * ? / L C # Year(年) 取值范围(1970-2099),允许为空值 , - * / 3.Cron 表达式中特殊字符的意义 | 表示可以匹配该域的所有值?| 主要用于日和星期,可以匹配域的任意值,但实际不会。当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为? / | 表示为每隔一段时间。如 0 0/10 * ? 其中的 0/10表示从0分钟开始,每隔10分钟执行一次 | 表示范围。如 0 0-5 14 ? 表示在每天14:00到14:05期间每1分钟执行一次, | 表示枚举多个值,这些值之间是"或"的关系。如 0 10,30 14 * 3 ? 表示每个星期二14点10分或者14点30分执行一次 L | 表示每月或者每周的最后一天。如 0 0 0 L ? 表示每月的最后一天执行W | 表示最近工作日。如 0 0 0 15W ? 表示每月15号最近的那个工作日执行 | 用来指定具体的周数,"#"前面代表星期,"#"后面代表本月的第几周。如"2#1"表示本月第二周的星期日 4.Cron 在线生成工具 http://www.bejson.com/othertools/cron/ 动态定时任务 1.实现 SchedulingConfigurer 接口 @Configuration public class CustomScheduleConfig implements SchedulingConfigurer { @Autowired private CronTriggerDao cronTriggerDao; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 上次开始执行时间点后5秒再执行 taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig执行任务job1=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 5000); // 上次执行完毕时间点后3秒再执行 taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig执行任务job2=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 3000); // 添加一个配合数据库动态执行的定时任务 TriggerTask triggerTask = new TriggerTask( // 任务内容.拉姆达表达式 () -> { // 1)添加任务 Runnable System.out.println("CustomScheduleConfig执行动态任务job3=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()); // 2)设置执行周期 }, triggerContext -> { // 3)从数据库获取执行周期 String cron = cronTriggerDao.getCronTriggerById(1L); if (cron != null) { // 4)返回执行周期(Date) return new CronTrigger(cron).nextExecutionTime(triggerContext); } else { return null; } }); taskRegistrar.addTriggerTask(triggerTask); } } 2.数据库中初始化数据 这边为了测试简单初始化两行数据: SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_cron_trigger -- ---------------------------- DROP TABLE IF EXISTS `t_cron_trigger`; CREATE TABLE `t_cron_trigger` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '任务id', `cron` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'cron表达式', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `is_deleted` int(1) DEFAULT '0' COMMENT '作废状态 0-否 1-是', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; -- ---------------------------- -- Records of t_cron_trigger -- ---------------------------- BEGIN; INSERT INTO `t_cron_trigger` VALUES (1, '0/10 * * * * ?', '2019-10-30 13:40:14', NULL, 0); INSERT INTO `t_cron_trigger` VALUES (2, '0/7 * * * * ?', '2019-10-30 13:40:34', NULL, 0); COMMIT; SET FOREIGN_KEY_CHECKS = 1; 3.从数据库中读取 cron 表达式值 CronTrigger 实体类 public class CronTrigger implements Serializable{ /** * */ private static final long serialVersionUID = 880141459783509786L; private Long id;//任务id private String cron;//cron表达式 private String createTime;//创建时间 private String updateTime;//更新时间 private boolean isDeleted=false;//删除状态 public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getCron() { return cron; } public void setCron(String cron) { this.cron = cron; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } public boolean isDeleted() { return isDeleted; } public void setDeleted(boolean isDeleted) { this.isDeleted = isDeleted; } } CronTriggerDao public interface CronTriggerDao { /** * 根据id获取cron表达式 * @param id * @return */ String getCronTriggerById(Long id); } CronTriggerMapper.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.CronTriggerDao"> <resultMap type="cn.zwqh.springboot.model.CronTrigger" id="cronTrigger"> <id property="id" column="id"/> <result property="cron" column="cron"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> <result property="isDeleted" column="is_deleted"/> </resultMap> <!-- 根据id获取cron表达式 --> <select id="getCronTriggerById" resultType="String"> select cron from t_cron_trigger where is_deleted=0 and id=#{id} </select> </mapper> 4.测试 启动前记得在启动类上加上 @EnableScheduling 打印日志如下: 多线程定时任务 通过上面的日志我们可以看到任务执行都是单线程的。如果要实现多线程执行任务,我们可以通过在 SchedulingConfigurer 接口的 configureTasks方法中添加线程池即可。 1.CustomScheduleConfig @Configuration public class CustomScheduleConfig implements SchedulingConfigurer { @Autowired private CronTriggerDao cronTriggerDao; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //设定一个长度为10的定时任务线程池 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); // 上次开始执行时间点后5秒再执行 taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig执行任务job1=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 5000); // 上次执行完毕时间点后3秒再执行 taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig执行任务job2=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 3000); // 添加第一个配合数据库动态执行的定时任务 TriggerTask triggerTask = new TriggerTask( // 任务内容.拉姆达表达式 () -> { // 1)添加任务 Runnable System.out.println("CustomScheduleConfig执行动态任务job3=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()); // 2)设置执行周期 }, triggerContext -> { // 3)从数据库获取执行周期 String cron = cronTriggerDao.getCronTriggerById(1L); if (cron != null) { // 4)返回执行周期(Date) return new CronTrigger(cron).nextExecutionTime(triggerContext); } else { return null; } }); taskRegistrar.addTriggerTask(triggerTask); // 添加第二个配合数据库动态执行的定时任务 TriggerTask triggerTask2 = new TriggerTask( // 任务内容.拉姆达表达式 () -> { // 1)添加任务 Runnable System.out.println("CustomScheduleConfig执行动态任务job4=>" + DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()); // 2)设置执行周期 }, triggerContext -> { // 3)从数据库获取执行周期 String cron = cronTriggerDao.getCronTriggerById(2L); if (cron != null) { // 4)返回执行周期(Date) return new CronTrigger(cron).nextExecutionTime(triggerContext); } else { return null; } }); taskRegistrar.addTriggerTask(triggerTask2); } } 2.测试 打印日志如下: 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十二):定时任务 原文地址: https://www.zwqh.top/article/info/21 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
前言 在 Java Web 系统开发中,不管是 Controller 层、Service 层还是 Dao 层,都有可能抛出异常。如果在每个方法中加上各种 try catch 的异常处理代码,那样会使代码非常繁琐。在Spring MVC 中,我们可以将所有类型的异常处理从各个单独的方法中解耦出来,进行异常信息的统一处理和维护。 在 Spring MVC 中全局异常捕获处理的解决方案通常有两种方式: 1.使用 @ControllerAdvice + @ExceptionHandler 注解进行全局的 Controller 层异常处理。 2.实现 org.springframework.webb.servlet.HandlerExceptionResolver 接口中的 resolveException 方法。 使用 @ControllerAdvice + @ExceptionHandler 注解 1.定义统一异常处理类 @ControllerAdvice public class GlobalExceptionHandler { private Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) { log.error("ExceptionHandler ===>" + e.getMessage()); e.printStackTrace(); // 这里可根据不同异常引起的类做不同处理方式 String exceptionName = ClassUtils.getShortName(e.getClass()); log.error("ExceptionHandler ===>" + exceptionName); ModelAndView mav = new ModelAndView(); mav.addObject("stackTrace", e.getStackTrace()); mav.addObject("errorMessage", e.getMessage()); mav.addObject("url", req.getRequestURL()); mav.setViewName("forward:/error/500"); return mav; } } 其中 @ExceptionHandler(value = Exception.class) 中的捕获异常 value 可以自定义,如下: 类型 描述 NullPointerException 当应用程序试图访问空对象时,则抛出该异常 SQLException 提供关于数据库访问错误或其他错误信息的异常 IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出 NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 FileNotFoundException 当试图打开指定路径名表示的文件失败时,抛出此异常 IOException 当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类 ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常 ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常 IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数 ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例 NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常 NoSuchMethodException 无法找到某一特定方法时,抛出该异常 SecurityException 由安全管理器抛出的异常,指示存在安全侵犯 UnsupportedOperationException 当不支持请求的操作时,抛出该异常 RuntimeException 是那些可能在Java虚拟机正常运行期间抛出的异常的超类 当捕获到响应的异常类型时,会进入 defaultErrorHandler() 方法中的逻辑:把异常信息放入 model,跳转至 /error/500 请求URL。 2.异常信息展现 视图控制器配置 @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 视图控制器配置 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("/index");//设置默认跳转视图为 /index registry.addViewController("/error/500").setViewName("/error/500"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); super.addViewControllers(registry); } } 视图模板 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>Exception</h1> <h3 th:text="${url}"></h3> <h3 th:text="${errorMessage}"></h3> <p th:each="line : ${stackTrace}" th:text="${line}"> </p> </body> </html> 3.测试异常类 @Controller public class TestController { @GetMapping("/index") public String hello() { int x = 1 / 0; return "hello"; } } 4.运行测试 浏览器访问:http://127.0.0.1:8080/index @ControllerAdvice 还能结合 @ModelAttribute 、@InitBinder 注解一起使用,实现全局数据绑定和全局数据预处理等功能。 实现 HandlerExceptionResolver 接口 1.定义统一异常处理类 @Component public class GlobalHandlerExceptionResolver implements HandlerExceptionResolver { private Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Exception e = new Exception(); //处理 UndeclaredThrowableException if (ex instanceof UndeclaredThrowableException) { e = (Exception) ((UndeclaredThrowableException) ex).getUndeclaredThrowable(); } else { e = ex; } e.printStackTrace(); //这里可以根据不同异常引起的类做不同处理方式 String exceptionName = ClassUtils.getShortName(e.getClass()); if(exceptionName.equals("ArrayIndexOutOfBoundsException")) { log.error("GlobalHandlerExceptionResolver resolveException ===>" + exceptionName); ModelAndView mav = new ModelAndView(); mav.addObject("stackTrace", e.getStackTrace()); mav.addObject("exceptionName", exceptionName); mav.addObject("errorMessage", e.getMessage()); mav.addObject("url", request.getRequestURL()); mav.setViewName("forward:/error/500"); return mav; } return null; } } UndeclaredThrowableException 异常通常是在 RPC 接口调用场景或者使用 JDK 动态代理的场景时发生。如果不预先处理转换,测试捕获到的异常则为 UndeclaredThrowableException,而不是真实的异常对象。 2.异常信息展现 同上 3.测试异常类 @Controller public class TestController { @GetMapping("/test") public String test() { String[] ss = new String[] { "1", "2" }; System.out.print(ss[2]); return "hello"; } } 4.测试运行 测试前先把 @ControllerAdvice 注释了。浏览器访问:http://127.0.0.1:8080/test 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十一):全局异常处理 原文地址: https://www.zwqh.top/article/info/20 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
前言 在 Spring Boot 中已经移除了 web.xml 文件,如果需要注册添加 Servlet、Filter、Listener 为 Spring Bean,在 Spring Boot 中有两种方式: 使用 Servlet 3.0 API 的注解 @WebServlet、@WebFilter、@Listener 用来配置。 Spring Boot JavaConfig 注解配置 Bean 的方式来进行配置。 注册之前 在使用 Servlet 时,需要在 Spring Boot 入口类添加 @ServletComponentScan 注解,告诉 Spring Boot 去扫描使用下面注册的 Servlet、Filter、Listener。 @SpringBootApplication @ServletComponentScan public class SpringBootServletApplication { public static void main(String[] args) { SpringApplication.run(SpringBootServletApplication.class, args); } } 注册 Servlet 1.@WebServlet 属性 属性 类型 描述 name String 指定Servlet名称,等价于 value String[] 等同于 urlPatterns 属性,两者不应该同时使用 urlPatterns String[] 指定一组 Servlet 的 URL 匹配模式。等价于标签 loadOnStartup int 指定 Servlet 的加载顺序,等价于 标签 initParams WebInitParam[] 指定一组 Servlet 初始化参数,等价于标签 asyncSupported boolean 声明 Servlet 是否支持异步操作模式,等价于 标签 smallIcon String 此 Servlet 的小图标 largeIcon String 此 Servlet 的大图标 description String 该 Servlet 的描述信息,等价于 标签 displayName String 该 Servlet 的显示名,通常配合工具使用,等价于 标签 2.示例 @WebServlet(urlPatterns = "/TestServlet") public class TestServlet extends HttpServlet { /** * */ private static final long serialVersionUID = -3325041776508043481L; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { doPost(req, resp); } /* * 实现请求uri和header打印,另外返回一个json */ @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { System.out.println("RequestURI:" + req.getRequestURI()); System.out.println("Request Headers:"); StringBuilder sb = new StringBuilder(); Enumeration<?> names = req.getHeaderNames(); while (names.hasMoreElements()) { String name = names.nextElement().toString(); Enumeration<?> hs = req.getHeaders(name); sb.append(name).append(":"); while (hs.hasMoreElements()) { sb.append(hs.nextElement()).append(";"); } } System.out.println(sb); ObjectMapper om=new ObjectMapper(); UserEntity user=new UserEntity(); user.setId(1L); user.setUserName("zwqh"); user.setUserSex("男"); user.setHeaders(sb.toString()); String resultJson=om.writeValueAsString(user); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().print(resultJson); } } 其中@WebServlet(urlPatterns = "/TestServlet")等价于以下代码: <servlet> <!-- 类名 --> <servlet-name> TestServlet </servlet-name> <!-- 所在的包 --> <servlet-class> cn.zwqh.springbboot.servlet.TestServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name> TestServlet </servlet-name> <!-- 访问的url路径地址 --> <url-pattern> /TestServlet </url-pattern> </servlet-mapping> 3.测试 浏览器访问 http://127.0.0.1:8080/TestServlet日志输出: 注册 Filter 1.@WebFilter 属性 属性 类型 描述 filterName String 指定Filter名称,等价于 value String[] 等同于 urlPatterns 属性,两者不应该同时使用 urlPatterns String[] 指定一组 Filter 的 URL 匹配模式。等价于标签 servletNames String[] 指定过滤器将应用于哪些 Servlet。取值于 @WebServlet 中的 name 属性,或者是 web.xml 中 的值 initParams WebInitParam[] 指定一组 Filter 初始化参数,等价于标签 dispatcherTypes DispatcherType[] 指定 Filter 的转发模式,包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST asyncSupported boolean 声明 Filter 是否支持异步操作模式,等价于 标签 smallIcon String 此 Filter 的小图标 largeIcon String 此 Filter 的大图标 description String 该 Filter 的描述信息,等价于 标签 displayName String 该 Filter 的显示名,通常配合工具使用,等价于 标签 2.示例 @WebFilter(urlPatterns = { "/TestServlet" }) // 注册拦截器,并添加拦截路径‘/TestServlet’ public class TestFilter implements Filter { /** * 初始化,只在项目启动的时候执行一次 */ @Override public void init(FilterConfig filterConfig) { System.out.println("===> TestFilter init"); } /** * 用于存放过滤器的业务逻辑实现代码 */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response);// 处理请求和响应的分界线 System.out.println("===> chain.doFilter 后执行处理 response 的相关方法"); // 在response header里设置一个token setToken(response); } private void setToken(ServletResponse response) { HttpServletResponse res = (HttpServletResponse) response; String token = UUID.randomUUID().toString(); res.setHeader("Token", token); System.out.println("===> 设置了token:" + token); } /** * 销毁,在项目关闭,Servlet 容器销毁前调用 */ @Override public void destroy() { System.out.println("===> TestFilter destroy"); } } 3.测试 浏览器访问 http://127.0.0.1:8080/TestServlet :日志打印: 4.Filter 主要使用场景 禁用浏览器的缓存(缓存的处理) 解决中文乱码问题 登录鉴权及权限管理 用户授权,负责检查用户的请求,根据请求过滤用户非法请求 日志记录,详细记录某些特殊的用户请求 其他场景 注册 Listener 1.@Listener 属性 属性 类型 描述 value String 侦听器Listener的描述 2.示例 与 ServletContext 相关的监听 Servlet 的监听器 Listener 是实现了 javax.servlet.ServletContextListener 接口的服务器端程序,随着 Web 应用启动而启动,只初始化一次,也随着 Web 应用停止而销毁。其主要作用是做一些初始化的内容添加工作,如参数和对象等。 @WebListener public class ContextListener implements ServletContextListener, ServletContextAttributeListener{ public static final String INITIAL_CONTENT = "Content created in servlet Context"; /** * ServletContext创建 */ @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("===> context initialized"); ServletContext servletContext = sce.getServletContext(); servletContext.setAttribute("content", INITIAL_CONTENT); } /** * ServletContext销毁 */ @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("===> context destroyed"); } /** * context属性新增 */ @Override public void attributeAdded(ServletContextAttributeEvent scae) { System.out.println("===> context attribute added"); } /** * context属性移除 */ @Override public void attributeRemoved(ServletContextAttributeEvent scae) { System.out.println("===> context attribute removed"); } /** * context属性替换 */ @Override public void attributeReplaced(ServletContextAttributeEvent scae) { System.out.println("===> context attribute replaced"); } } 与 HttpSession 相关的监听 @WebListener public class SessionListener implements HttpSessionListener, HttpSessionIdListener, HttpSessionAttributeListener, HttpSessionActivationListener { /** * session被创建时 */ @Override public void sessionCreated(HttpSessionEvent se) { System.out.println("===> session created"); } /** * session被销毁时 */ @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("===> session destroyed"); } /** * sessionId改变 */ @Override public void sessionIdChanged(HttpSessionEvent se, String oldSessionId) { System.out.println("===> session id changed"); } /** * session属性新增 */ @Override public void attributeAdded(HttpSessionBindingEvent se) { System.out.println("===> session attribute added"); } /** * session属性移除 */ @Override public void attributeRemoved(HttpSessionBindingEvent se) { System.out.println("===> session attribute removed"); } /** * session属性替换 */ @Override public void attributeReplaced(HttpSessionBindingEvent se) { System.out.println("===> session attribute replaced"); } /** * session的钝化,内存的数据写入到硬盘上的过程。 */ @Override public void sessionWillPassivate(HttpSessionEvent se) { System.out.println("===> session will passivate"); } /** * session的活化,将硬盘的数据恢复到内存中。 */ @Override public void sessionDidActivate(HttpSessionEvent se) { System.out.println("===> session did activate"); } } 与 ServletRequest 相关的监听 @WebListener public class RequestListener implements ServletRequestListener,ServletRequestAttributeListener { /** * 请求即将进入Web应用程序的范围/请求初始化时 */ @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("===> request initialized"); } /** * 请求即将进入Web应用程序的范围/请求销毁时 */ @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("===> request destroyed"); } /** * request属性新增 */ @Override public void attributeAdded(ServletRequestAttributeEvent srae) { System.out.println("===> request attribute added"); } /** * request属性移除 */ @Override public void attributeRemoved(ServletRequestAttributeEvent srae) { System.out.println("===> request attribute removed"); } /** * request属性替换 */ @Override public void attributeReplaced(ServletRequestAttributeEvent srae) { System.out.println("===> request attribute replaced"); } } 3.项目相关日志输入(启动和停止) 先执行 contextInitialzed 方法在执行 TestFilter 类的 init 方法,contextDestroyed 方法在 TestFilter 类 destroy 方法执行后执行。 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(十):自定义注册 Servlet、Filter、Listener 原文地址: https://www.zwqh.top/article/info/17 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
拦截器 1.简介 Spring MVC 中的拦截器(Interceptor)类似与 Servlet 开发中的过滤器 Filter,它主要用于拦截用户请求并作相应的处理,它也是 AOP 编程思想的体现,底层通过动态代理模式完成。 2.定义实现类 拦截器有两种实现方式:1.实现 HandlerInterceptor 接口2.继承 HandlerInterceptorAdapter 抽象类(看源码最底层也是通过 HandlerInterceptor 接口 实现) 3.HandlerInterceptor方法介绍 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //进行逻辑判断,如果ok就返回true,不行就返回false,返回false就不会处理请求 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。afterCompletion:在 DispatcherServlet 完全处理完请求后被调用,可用于清理资源等。 4.应用场景 1.日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等;2.登录鉴权:如登录检测,进入处理器检测检测是否登录; 3.性能监控:检测方法的执行时间;4.其他通用行为。 5.与 Filter 过滤器的区别 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调。 2.拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。3.拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。4.拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。5.在Controller的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。6.拦截器可以获取IOC容器中的各个bean,而过滤器不行。这点很重要,在拦截器里注入一个service,可以调用业务逻辑。 具体实现 单个拦截器 1.新建拦截器 public class Test1Interceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行preHandle方法-->01"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行postHandle方法-->02"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行afterCompletion方法-->03"); } } 2.配置拦截器 @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /* * 拦截器配置 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 注册自定义拦截器,添加拦截路径和排除拦截路径 registry.addInterceptor(new Test1Interceptor()) // 添加拦截器 .addPathPatterns("/**") // 添加拦截路径 .excludePathPatterns(// 添加排除拦截路径 "/hello").order(0);//执行顺序 super.addInterceptors(registry); } } 3.测试拦截器 @RestController public class TestController { @RequestMapping("/hello") public String getHello() { System.out.println("这里是Hello"); return "hello world"; } @RequestMapping("/test1") public String getTest1() { System.out.println("这里是Test1"); return "test1 content"; } @RequestMapping("/test2") public String getTest2() { System.out.println("这里是Test2"); return "test2 content"; } } 4.单个拦截器的执行流程 通过浏览器测试:http://127.0.0.1:8080/hello结果: 这里是Hello http://127.0.0.1:8080/test1 、http://127.0.0.1:8080/test2 结果: 执行preHandle方法-->01 这里是Test1 执行postHandle方法-->02 执行afterCompletion方法-->03 多个拦截器 1.新建两个拦截器 Test1Interceptor public class Test1Interceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行Test1Interceptor preHandle方法-->01"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行Test1Interceptor postHandle方法-->02"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行Test1Interceptor afterCompletion方法-->03"); } } Test2Interceptor public class Test2Interceptor extends HandlerInterceptorAdapter{ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行Test2Interceptor preHandle方法-->01"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行Test2Interceptor postHandle方法-->02"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行Test2Interceptor afterCompletion方法-->03"); } } 2.配置拦截器 @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /* * 拦截器配置 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 注册自定义拦截器,添加拦截路径和排除拦截路径 registry.addInterceptor(new Test1Interceptor()) // 添加拦截器1 .addPathPatterns("/**") // 添加拦截路径 .excludePathPatterns(// 添加排除拦截路径 "/hello") .order(0); registry.addInterceptor(new Test2Interceptor()) // 添加拦截器2 .addPathPatterns("/**") // 添加拦截路径 .excludePathPatterns(// 添加排除拦截路径 "/test1") .order(1); super.addInterceptors(registry); } } 3.测试拦截器 @RestController public class TestController { @RequestMapping("/hello") public String getHello() { System.out.println("这里是Hello"); return "hello world"; } @RequestMapping("/test1") public String getTest1() { System.out.println("这里是Test1"); return "test1 content"; } @RequestMapping("/test2") public String getTest2() { System.out.println("这里是Test2"); return "test2 content"; } } 4.多个拦截器的执行流程 通过浏览器测试:http://127.0.0.1:8080/test2结果: 执行Test1Interceptor preHandle方法-->01 执行Test2Interceptor preHandle方法-->01 这里是Test2 执行Test2Interceptor postHandle方法-->02 执行Test1Interceptor postHandle方法-->02 执行Test2Interceptor afterCompletion方法-->03 执行Test1Interceptor afterCompletion方法-->03 通过示例,简单的说多个拦截器执行流程就是先进后出。 简单的 token 判断示例 1.拦截器 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行Test1Interceptor preHandle方法-->01"); String token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); response.getWriter().println("token不存在"); return false; } return true; } 2.测试及结果 未传token: 执行Test1Interceptor preHandle方法-->01 传token: 执行Test1Interceptor preHandle方法-->01 页码:1 页码大小:10 执行Test1Interceptor postHandle方法-->02 执行Test1Interceptor afterCompletion方法-->03 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(九):Spring MVC - 拦截器(Interceptor) 原文地址: https://www.zwqh.top/article/info/18 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
AOP 1.什么是 AOP ? AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一种技术。AOP 是面向对象编程(OOP)的补充和扩展。利用 AOP 可以对业务逻辑各部分进行隔离,从而达到降低模块之间的耦合度,并将那些影响多个类的公共行为封装到一个可重用模块,从而到达提高程序的复用性,同时提高了开发效率,提高了系统的可操作性和可维护性。 2.为什么要用 AOP ? 在实际的 Web 项目开发中,我们常常需要对各个层面实现日志记录,性能统计,安全控制,事务处理,异常处理等等功能。如果我们对每个层面的每个类都独立编写这部分代码,那久而久之代码将变得很难维护,所以我们把这些功能从业务逻辑代码中分离出来,聚合在一起维护,而且我们能灵活地选择何处需要使用这些代码。 3.AOP 的核心概念 名词 概念 理解 通知(Advice) 拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类 我们要实现的功能,如日志记录,性能统计,安全控制,事务处理,异常处理等等,说明什么时候要干什么 连接点(Joint Point) 被拦截到的点,如被拦截的方法、对类成员的访问以及异常处理程序块的执行等等,自身还能嵌套其他的 Joint Point Spring 允许你用通知的地方,方法有关的前前后后(包括抛出异常) 切入点(Pointcut) 对连接点进行拦截的定义 指定通知到哪个方法,说明在哪干 切面(Aspect) 切面类的定义,里面包含了切入点(Pointcut)和通知(Advice)的定义 切面就是通知和切入点的结合 目标对象(Target Object) 切入点选择的对象,也就是需要被通知的对象;由于 Spring AOP 通过代理模式实现,所以该对象永远是被代理对象 业务逻辑本身 织入(Weaving) 把切面应用到目标对象从而创建出 AOP 代理对象的过程。织入可以在编译期、类装载期、运行期进行,而 Spring 采用在运行期完成 切点定义了哪些连接点会得到通知 引入(Introduction ) 可以在运行期为类动态添加方法和字段,Spring 允许引入新的接口到所有目标对象 引入就是在一个接口/类的基础上引入新的接口增强功能 AOP 代理(AOP Proxy ) Spring AOP 可以使用 JDK 动态代理或者 CGLIB 代理,前者基于接口,后者基于类 通过代理来对目标对象应用切面 Spring AOP 1.简介 AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。 2.相关注解 注解 说明 @Aspect 将一个 java 类定义为切面类 @Pointcut 定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等 @Before 在切入点开始处切入内容 @After 在切入点结尾处切入内容 @AfterReturning 在切入点 return 内容之后处理逻辑 @Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内容 @AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑 @Order(100) AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行 其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通知(Advice)。 利用 AOP 实现 Web 日志处理 1.构建项目 2.添加依赖 <dependencies> <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> <scope>test</scope> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- Spring AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> 3.Web 日志注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ControllerWebLog { String name();//所调用接口的名称 boolean intoDb() default false;//标识该条操作日志是否需要持久化存储 } 4.实现切面逻辑 @Aspect @Component @Order(100) public class WebLogAspect { private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>(); /** * 横切点 */ @Pointcut("execution(public * cn.zwqh.springboot.controller..*.*(..))") public void webLog() { } /** * 接收请求,并记录数据 * @param joinPoint * @param controllerWebLog */ @Before(value = "webLog()&& @annotation(controllerWebLog)") public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) { // 接收到请求 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 记录请求内容,threadInfo存储所有内容 Map<String, Object> threadInfo = new HashMap<>(); logger.info("URL : " + request.getRequestURL()); threadInfo.put("url", request.getRequestURL()); logger.info("URI : " + request.getRequestURI()); threadInfo.put("uri", request.getRequestURI()); logger.info("HTTP_METHOD : " + request.getMethod()); threadInfo.put("httpMethod", request.getMethod()); logger.info("REMOTE_ADDR : " + request.getRemoteAddr()); threadInfo.put("ip", request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); threadInfo.put("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); threadInfo.put("args", Arrays.toString(joinPoint.getArgs())); logger.info("USER_AGENT"+request.getHeader("User-Agent")); threadInfo.put("userAgent", request.getHeader("User-Agent")); logger.info("执行方法:" + controllerWebLog.name()); threadInfo.put("methodName", controllerWebLog.name()); threadLocal.set(threadInfo); } /** * 执行成功后处理 * @param controllerWebLog * @param ret * @throws Throwable */ @AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret") public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable { Map<String, Object> threadInfo = threadLocal.get(); threadInfo.put("result", ret); if (controllerWebLog.intoDb()) { //插入数据库操作 //insertResult(threadInfo); } // 处理完请求,返回内容 logger.info("RESPONSE : " + ret); } /** * 获取执行时间 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around(value = "webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object ob = proceedingJoinPoint.proceed(); Map<String, Object> threadInfo = threadLocal.get(); Long takeTime = System.currentTimeMillis() - startTime; threadInfo.put("takeTime", takeTime); logger.info("耗时:" + takeTime); threadLocal.set(threadInfo); return ob; } /** * 异常处理 * @param throwable */ @AfterThrowing(value = "webLog()", throwing = "throwable") public void doAfterThrowing(Throwable throwable) { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 异常信息 logger.error("{}接口调用异常,异常信息{}", request.getRequestURI(), throwable); } } 5.测试接口 @RestController @RequestMapping("/user") public class UserController { @GetMapping("/getOne") @ControllerWebLog(name = "查询", intoDb = true) public String getOne(Long id, String name) { return "1234"; } } 6.运行测试 浏览器请求:http://127.0.0.1:8080/user/getOne?id=1&name=zwqh ,可以看到后台日志输出: 小结 日志记录只是一个简单的示例,而 Spring AOP 的应用让整个系统变的更加有条不紊,在其他场景应用也很强大。它帮助我们降低模块间耦合度,提高程序复用性,提高开发效率,提高系统可做性和可维护性。 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(八):Spring AOP 实现简单的日志切面 原文地址: https://www.zwqh.top/article/info/14 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
Spring Cache 简介 在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 两个接口来统一不同的缓存技术。Cache 接口包含缓存的常用操作:增加、删除、读取等。CacheManager 是 Spring 各种缓存的抽象接口。Spring 支持的常用 CacheManager 如下: CacheManager 描述 SimpleCacheManager 使用简单的 Collection 来存储缓存 ConcurrentMapCacheManager 使用 java.util.ConcurrentHashMap 来实现缓存 NoOpCacheManager 仅测试用,不会实际存储缓存 EhCacheCacheManger 使用EhCache作为缓存技术。EhCache 是一个纯 Java 的进程内缓存框架,特点快速、精干,是 Hibernate 中默认的 CacheProvider,也是 Java 领域应用最为广泛的缓存 JCacheCacheManager 支持JCache(JSR-107)标准的实现作为缓存技术 CaffeineCacheManager 使用 Caffeine 作为缓存技术。用于取代 Guava 缓存技术。 RedisCacheManager 使用Redis作为缓存技术 HazelcastCacheManager 使用Hazelcast作为缓存技术 CompositeCacheManager 用于组合 CacheManager,可以从多个 CacheManager 中轮询得到相应的缓存 Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等注解,在方法上使用。通过注解 Cache 可以实现类似事务一样、缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码。核心思想:当我们调用一个方法时会把该方法的参数和返回结果最为一个键值对存放在缓存中,等下次利用同样的参数来调用该方法时将不会再执行,而是直接从缓存中获取结果进行返回。 Cache注解 1.@EnableCaching 开启缓存功能,一般放在启动类上。 2.@CacheConfig 当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {"cacheName"})注解在 class 之上来统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。 3.@Cacheable 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key keyGenerator key的生成器。key/keyGenerator二选一使用 cacheManager 指定缓存管理器 cacheResolver 指定获取解析器 condition 条件符合则缓存 unless 条件符合则不缓存 sync 是否使用异步模式,默认为false 4.@CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key keyGenerator key的生成器。key/keyGenerator二选一使用 cacheManager 指定缓存管理器 cacheResolver 指定获取解析器 condition 条件符合则缓存 unless 条件符合则不缓存 5.@CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key keyGenerator key的生成器。key/keyGenerator二选一使用 cacheManager 指定缓存管理器 cacheResolver 指定获取解析器 condition 条件符合则缓存 allEntries 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 6.@Caching 该注解可以实现同一个方法上同时使用多种注解。可从其源码看出: public @interface Caching { Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; } Spring Cache 使用 1.构建项目,添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zwqh</groupId> <artifactId>spring-boot-cache</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-cache</name> <description>spring-boot-cache</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> <!-- Spring Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- mysql 数据库驱动. --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 2.application.properties 配置文件 #datasource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=true spring.datasource.username=root spring.datasource.password=123456 #mybatis mybatis.mapper-locations=classpath:/mapper/*Mapper.xml 3.实体类 public class UserEntity implements Serializable{ /** * */ private static final long serialVersionUID = 5237730257103305078L; private Long id; private String userName; private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } 4.数据层 dao 和 mapper.xml public interface UserDao { //mapper.xml方式 /** * 获取所有用户 * @return */ List<UserEntity> getAll(); /** * 根据id获取用户 * @return */ UserEntity getOne(Long id); /** * 新增用户 * @param user */ void insertUser(UserEntity user); /** * 修改用户 * @param user */ void updateUser(UserEntity user); /** * 删除用户 * @param id */ void deleteUser(Long id); } <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.UserDao"> <resultMap type="cn.zwqh.springboot.model.UserEntity" id="user"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="userSex" column="user_sex"/> </resultMap> <!-- 获取所有用户 --> <select id="getAll" resultMap="user"> select * from t_user </select> <!-- 根据用户ID获取用户 --> <select id="getOne" resultMap="user"> select * from t_user where id=#{id} </select> <!-- 新增用户 --> <insert id="insertUser" parameterType="cn.zwqh.springboot.model.UserEntity"> insert into t_user (user_name,user_sex) values(#{userName},#{userSex}) </insert> <!-- 修改用户 --> <update id="updateUser" parameterType="cn.zwqh.springboot.model.UserEntity"> update t_user set user_name=#{userName},user_sex=#{userSex} where id=#{id} </update> <!-- 删除用户 --> <delete id="deleteUser" parameterType="Long"> delete from t_user where id=#{id} </delete> </mapper> 5.业务代码层接口 Service 和实现类 ServiceImpl public interface UserService { /** * 查找所有 * @return */ List<UserEntity> getAll(); /** * 根据id获取用户 * @param id * @return */ UserEntity getOne(Long id); /** * 新增用户 * @param user */ void insertUser(UserEntity user); /** * 修改用户 * @param user */ void updateUser(UserEntity user); void deleteAll1(); void deleteAll12(); } @Service @CacheConfig(cacheNames = {"userCache"}) public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Cacheable("userList") // 标志读取缓存操作,如果缓存不存在,则调用目标方法,并将结果放入缓存 public List<UserEntity> getAll() { System.out.println("缓存不存在,执行方法"); return userDao.getAll(); } @Override @Cacheable(cacheNames = { "user" }, key = "#id")//如果缓存存在,直接读取缓存值;如果缓存不存在,则调用目标方法,并将结果放入缓存 public UserEntity getOne(Long id) { System.out.println("缓存不存在,执行方法"); return userDao.getOne(id); } @Override @CachePut(cacheNames = { "user" }, key = "#user.id")//写入缓存,key为user.id,一般该注解标注在新增方法上 public void insertUser(UserEntity user) { System.out.println("写入缓存"); userDao.insertUser(user); } @Override @CacheEvict(cacheNames = { "user" }, key = "#user.id")//根据key清除缓存,一般该注解标注在修改和删除方法上 public void updateUser(UserEntity user) { System.out.println("清除缓存"); userDao.updateUser(user); } @Override @CacheEvict(value="userCache",allEntries=true)//方法调用后清空所有缓存 public void deleteAll1() { } @Override @CacheEvict(value="userCache",beforeInvocation=true)//方法调用前清空所有缓存 public void deleteAll2() { } } 6.测试 Controller @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * 查找所有 * @return */ @RequestMapping("/getAll") public List<UserEntity> getAll(){ return userService.getAll(); } /** * 根据id获取用户 * @return */ @RequestMapping("/getOne") public UserEntity getOne(Long id){ return userService.getOne(id); } /** * 新增用户 * @param user * @return */ @RequestMapping("/insertUser") public String insertUser(UserEntity user) { userService.insertUser(user); return "insert success"; } /** * 修改用户 * @param user * @return */ @RequestMapping("/updateUser") public String updateUser(UserEntity user) { userService.updateUser(user); return "update success"; } } 7.启动 Cache 功能 @SpringBootApplication @MapperScan("cn.zwqh.springboot.dao") @EnableCaching //启动 Cache 功能 public class SpringBootCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCacheApplication.class, args); } } 8.数据库及测试数据 数据库和测试数据仍旧用之前的。 9.测试 编写单元测试,或者通过访问 http://127.0.0.1:8080/user/ 加上对应路径和参数。 文档 org.springframework.cache 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(七):Spring Cache 使用 原文地址: https://www.zwqh.top/article/info/13 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
Redis 简介 什么是 Redis Redis 是目前使用的非常广泛的免费开源内存数据库,是一个高性能的 key-value 数据库。 Redis 与其他 key-value 缓存(如 Memcached )相比有以下三个特点: 1.Redis 支持数据的持久化,它可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。2.Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。 3.Redis 支持数据的备份,即 master-slave 模式的数据备份。 Redis 优势如下: 1.性能极高。Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s。 2.丰富的数据类型。Redis 支持二进制案例的 Strings,Lists,Sets 及 Ordered Sets 数据类型操作。3.原子性。Redis 所有的操作都是原子性的,意思是要么成功执行要么失败完全不执行。单个操作是原子性的,多个操作也是,通过 MULTI 和 EXEC 指令抱起来。4.丰富的特性。Redis 还支持 publish/subscribe,通知,key 过期等特性。 Spring Boot 集成 Redis 1.在项目中添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>cn.zwqh</groupId> <artifactId>spring-boot-redis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-redis</name> <description>spring-boot-redis</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 查看 jar 包时发现,Spring Data Redis 下 org.springframework.data.redis.connection 包路径下面默认有两个包 jedis 和 lettuce,这说明 Spring Boot 已经默认包装适配了这两个 Redis 客户端。 在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,springboot 2.x版本中默认客户端是用 lettuce实现的。 Lettuce 与 Jedis 比较 Lettuce 和 Jedis 的都是连接 Redis Server的客户端。 Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加物理连接。 Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。 下面我们分别使用 Lettuce 和 Jedis 来集成 Redis 服务 2. Lettuce 集成 Redis 服务 导入依赖 由于 Spring Boot 2.X 默认集成了 Lettuce ,所以无需导入。 application.properties配置文件 ################ Redis 基础配置 ############## # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password=zwqh # 链接超时时间 单位 ms(毫秒) spring.redis.timeout=3000 ################ Redis 线程池设置 ############## # 连接池最大连接数(使用负值表示没有限制) 默认 8 spring.redis.lettuce.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 spring.redis.lettuce.pool.max-wait=-1 # 连接池中的最大空闲连接 默认 8 spring.redis.lettuce.pool.max-idle=8 # 连接池中的最小空闲连接 默认 0 spring.redis.lettuce.pool.min-idle=0 自定义 RedisTemplate 默认情况下的模板只能支持 RedisTemplate<String,String>,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。如下所示: @Configuration public class LettuceRedisConfig { @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) { RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } } 序列化实体类 public class UserEntity implements Serializable { /** * */ private static final long serialVersionUID = 5237730257103305078L; private Long id; private String userName; private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } 单元测试 @RunWith(SpringRunner.class) @SpringBootTest public class SpringBootRedisApplicationTests { @Autowired private RedisTemplate<String, String> strRedisTemplate; @Autowired private RedisTemplate<String, Serializable> serializableRedisTemplate; @Test public void testString() { strRedisTemplate.opsForValue().set("strKey", "zwqh"); System.out.println(strRedisTemplate.opsForValue().get("strKey")); } @Test public void testSerializable() { UserEntity user=new UserEntity(); user.setId(1L); user.setUserName("朝雾轻寒"); user.setUserSex("男"); serializableRedisTemplate.opsForValue().set("user", user); UserEntity user2 = (UserEntity) serializableRedisTemplate.opsForValue().get("user"); System.out.println("user:"+user2.getId()+","+user2.getUserName()+","+user2.getUserSex()); } } 执行结果如下:得到我们预期的结果。 3.Jedis 集成 Redis 服务 pom 文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>cn.zwqh</groupId> <artifactId>spring-boot-redis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-redis</name> <description>spring-boot-redis</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <!-- 排除lettuce包 --> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加jedis客户端 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> application.properties配置文件 ################ Redis 基础配置 ############## # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password=zwqh # 链接超时时间 单位 ms(毫秒) spring.redis.timeout=3000 ################ Redis 线程池设置 ############## # 连接池最大连接数(使用负值表示没有限制) 默认 8 spring.redis.jedis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 spring.redis.jedis.pool.max-wait=-1 # 连接池中的最大空闲连接 默认 8 spring.redis.jedis.pool.max-idle=8 # 连接池中的最小空闲连接 默认 0 spring.redis.jedis.pool.min-idle=0 JedisRedisConfig @Configuration public class JedisRedisConfig { @Value("${spring.redis.database}") private int database; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; @Value("${spring.redis.jedis.pool.max-active}") private int maxActive; @Value("${spring.redis.jedis.pool.max-wait}") private long maxWaitMillis; @Value("${spring.redis.jedis.pool.max-idle}") private int maxIdle; @Value("${spring.redis.jedis.pool.min-idle}") private int minIdle; /** * 连接池配置信息 */ @Bean public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 最大连接数 jedisPoolConfig.setMaxTotal(maxActive); // 当池内没有可用连接时,最大等待时间 jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); // 最大空闲连接数 jedisPoolConfig.setMinIdle(maxIdle); // 最小空闲连接数 jedisPoolConfig.setMinIdle(minIdle); // 其他属性可以自行添加 return jedisPoolConfig; } /** * Jedis 连接 * * @param jedisPoolConfig * @return */ @Bean public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) { JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling() .poolConfig(jedisPoolConfig).and().readTimeout(Duration.ofMillis(timeout)).build(); RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(port); redisStandaloneConfiguration.setPassword(RedisPassword.of(password)); return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); } /** * 缓存管理器 * * @param connectionFactory * @return */ @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { return RedisCacheManager.create(connectionFactory); } @Bean public RedisTemplate<String, Serializable> redisTemplate(JedisConnectionFactory connectionFactory) { RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(jedisConnectionFactory(jedisPoolConfig())); return redisTemplate; } } 单元测试同上 出现预期结果。 总结 上面介绍了 Spring Boot 2.X 如何通过 Lettuce 和 Jedis 来集成 Redis 服务,按项目需求,我们也可以自定义操作类来实现数据操作。 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(六):Spring Boot 集成Redis 原文地址: https://www.zwqh.top/article/info/11 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
前言 MyBatis 多数据源配置,最近在项目建设中,需要在原有系统上扩展一个新的业务模块,特意将数据库分库,以便减少复杂度。本文直接以简单的代码示例,如何对 MyBatis 多数据源配置。 准备 创建数据库db_test SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT 'ID', `user_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户姓名', `user_sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户性别', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user -- ---------------------------- BEGIN; INSERT INTO `t_user` VALUES (1, '刘备', '男'); INSERT INTO `t_user` VALUES (2, '孙尚香', '女'); INSERT INTO `t_user` VALUES (3, '周瑜', '男'); INSERT INTO `t_user` VALUES (4, '小乔', '女'); INSERT INTO `t_user` VALUES (5, '诸葛亮', '男'); INSERT INTO `t_user` VALUES (6, '黄月英', '女'); INSERT INTO `t_user` VALUES (7, '关羽', '男'); INSERT INTO `t_user` VALUES (8, '张飞', '男'); INSERT INTO `t_user` VALUES (9, '赵云', '男'); INSERT INTO `t_user` VALUES (10, '黄总', '男'); INSERT INTO `t_user` VALUES (11, '曹操', '男'); INSERT INTO `t_user` VALUES (12, '司马懿', '男'); INSERT INTO `t_user` VALUES (13, '貂蝉', '女'); INSERT INTO `t_user` VALUES (14, '吕布', '男'); INSERT INTO `t_user` VALUES (15, '马超', '男'); INSERT INTO `t_user` VALUES (16, '魏延', '男'); INSERT INTO `t_user` VALUES (17, '孟获', '男'); INSERT INTO `t_user` VALUES (18, '大乔', '女'); INSERT INTO `t_user` VALUES (19, '刘婵', '男'); INSERT INTO `t_user` VALUES (20, '姜维', '男'); INSERT INTO `t_user` VALUES (21, '廖化', '男'); INSERT INTO `t_user` VALUES (22, '关平', '男'); COMMIT; SET FOREIGN_KEY_CHECKS = 1; dbb_test2 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_hero -- ---------------------------- DROP TABLE IF EXISTS `t_hero`; CREATE TABLE `t_hero` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `hero_code` varchar(32) DEFAULT NULL COMMENT '英雄编码', `hero_name` varchar(20) DEFAULT NULL COMMENT '英雄名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_hero -- ---------------------------- BEGIN; INSERT INTO `t_hero` VALUES (1, '001', '德玛西亚'); COMMIT; SET FOREIGN_KEY_CHECKS = 1; 构建项目,项目目录结构 pom 文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zwqh</groupId> <artifactId>spring-boot-mybatis-mulidatasource</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-mybatis-mulidatasource</name> <description>spring-boot-mybatis-mulidatasource</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- mysql 数据库驱动. --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- alibaba的druid数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 这里使用了alibaba的druid数据库连接池,Druid 能够提供强大的监控和扩展功能。这里我们暂时只做简单的应用。 配置文件 #master 数据源配置 master.datasource.driver-class-name=com.mysql.cj.jdbc.Driver master.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=true master.datasource.username=root master.datasource.password=zwqh@0258 #slave 数据源配置 slave.datasource.driver-class-name=com.mysql.cj.jdbc.Driver slave.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test2?useUnicode=true&characterEncoding=UTF-8&useSSL=true slave.datasource.username=root slave.datasource.password=zwqh@0258 #mybatis mybatis.mapper-locations=classpath:/mapper/**/*Mapper.xml 数据源配置 MasterDataSourceConfig 对应数据库 db_test @Configuration @MapperScan(basePackages = "cn.zwqh.springboot.dao.master", sqlSessionFactoryRef = "masterSqlSessionFactory") public class MasterDataSourceConfig { @Value("${master.datasource.driver-class-name}") private String driverClassName; @Value("${master.datasource.url}") private String url; @Value("${master.datasource.username}") private String username; @Value("${master.datasource.password}") private String password; @Bean(name = "masterDataSource") @Primary public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(this.driverClassName); dataSource.setUrl(this.url); dataSource.setUsername(this.username); dataSource.setPassword(this.password); return dataSource; } @Bean(name = "masterSqlSessionFactory") @Primary public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/master/*Mapper.xml")); return bean.getObject(); } @Bean(name = "masterTransactionManager") @Primary public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "masterSqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } SlaveDataSourceConfig 对应数据库 db_test2 @Configuration @MapperScan(basePackages = "cn.zwqh.springboot.dao.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory") public class SlaveDataSourceConfig { @Value("${slave.datasource.driver-class-name}") private String driverClassName; @Value("${slave.datasource.url}") private String url; @Value("${slave.datasource.username}") private String username; @Value("${slave.datasource.password}") private String password; @Bean(name = "slaveDataSource") public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(this.driverClassName); dataSource.setUrl(this.url); dataSource.setUsername(this.username); dataSource.setPassword(this.password); return dataSource; } @Bean(name = "slaveSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/slave/*Mapper.xml")); return bean.getObject(); } @Bean(name = "slaveTransactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("slaveDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "slaveSqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } 多个数据源在使用的过程中必须指定主库,不然会报错。@MapperScan(basePackages = "cn.zwqh.springboot.dao.slave") 指定对应 Dao 层的扫描路径。 dao 层和 xml 层 db_test 数据库的 dao 层在 cn.zwqh.springboot.dao.master 包下,db_test2 数据库的 dao 层在 cn.zwqh.springboot.dao.slave 包下。 UserDao public interface UserDao { List<UserEntity> getAll(); } HeroDao public interface HeroDao { List<Hero> getAllHero(); } db_test 数据库的 xml 层在 /mapper/master/ 文件路径下,db_test2 数据库的 xml 层在 /mapper/slave/ 文件路径下。 UserMapper.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.master.UserDao"> <resultMap type="cn.zwqh.springboot.model.UserEntity" id="user"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="userSex" column="user_sex"/> </resultMap> <!-- 获取所有用户 --> <select id="getAll" resultMap="user"> select * from t_user </select> </mapper> HeroMapper.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.slave.HeroDao"> <resultMap type="cn.zwqh.springboot.model.Hero" id="hero"> <id property="id" column="id"/> <result property="heroCode" column="hero_code"/> <result property="heroName" column="hero_name"/> </resultMap> <!-- 获取所有用户 --> <select id="getAllHero" resultMap="hero"> select * from t_hero </select> </mapper> 测试 测试可以使用 SpringBootTest,也可以放到 Controller中,个人习惯用 Controller。 @RestController @RequestMapping("/test") public class TestController { @Autowired private UserDao userDao; @Autowired private HeroDao heroDao; /** * 查找所有用户 * @return */ @RequestMapping("/getAllUser") public List<UserEntity> getAllUser(){ return userDao.getAll(); } /** * 查找所有英雄 * @return */ @RequestMapping("/getAllHero") public List<Hero> getAllHero(){ return heroDao.getAllHero(); } } 浏览器直接访问:http://127.0.0.1:8080/test/ 加上相关测试路径即可。 总结 多数据源一般用于主从模式或者按业务分库。 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(五):MyBatis 多数据源配置 原文地址: https://www.zwqh.top/article/info/12 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
0.准备 Spring Boot 不仅提供了相当简单使用的自动配置功能,而且开放了非常自由灵活的配置类。Spring MVC 为我们提供了 WebMvcConfigurationSupport 类和一个注解 @EnableWebMvc 以帮助我们减少配置 Bean 的声明。本文简单说明如何自定义 Web MVC 配置。首先需要使用 @Configuration 将 WebMvcConfig 类标注为 Spring 配置类,示例代码如下: @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { //通过重写配置方法覆盖 } 并在启动类上添加 @EnableWebMvc,代码如下: @SpringBootApplication @MapperScan("cn.zwqh.springboot.dao") @EnableWebMvc //启用 Spring MVC 配置 public class SpringBootSsmThymeleafApplication { public static void main(String[] args) { SpringApplication.run(SpringBootSsmThymeleafApplication.class, args); } } 1.静态资源配置 Spring Boot 中默认的静态资源配置,是把类路径下的/static、/public、/resources 和 /METAINF/resources 目录或者 ServletContext 的根目录中的静态文件直接映射为 /**。它使用来自 Spring MVC 的ResourceHttpRequestHandler,以便您可以通过添加自己的WebMvcConfigurer并覆盖addResourceHandlers方法来修改该行为。示例代码如下: @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");//静态资源路径 css,js,img等 registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");//视图 registry.addResourceHandler("/mapper/**").addResourceLocations("classpath:/mapper/");//mapper.xml super.addResourceHandlers(registry); } 2.拦截器配置 通过重写 addInterceptors() 方法,使用 InterceptorRegistry 注册类来添加拦截器 HandlerInterceptor。示例代码如下: @Autowired private BeforMethodInteceptor beforMethodInteceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //注册自定义拦截器,添加拦截路径和排除拦截路径 registry.addInterceptor(beforMethodInteceptor) //添加拦截器 .addPathPatterns("/**") //添加拦截路径 .excludePathPatterns(//添加排除拦截路径 "/index", "/login", "/doLogin", "/logout", "/register", "/doRegister", "/**/*.css", "/**/*.png", "/**/*.jpeg", "/**/*.jpg", "/**/*.ico", "/**/*.js", "/swagger-resources/**" ); super.addInterceptors(registry); } 3.跨域配置 通过重写 addCorsMappings 方法实现跨域配置的支持,使用 CorsRegistry 注册类添加路径映射。示例代码如下: @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**")//配置允许跨域的路径 .allowedOrigins("*")//配置允许访问的跨域资源的请求域名 .allowedMethods("PUT,POST,GET,DELETE,OPTIONS")//配置允许访问该跨域资源服务器的请求方法,如:POST、GET、PUT、DELETE等 .allowedHeaders("*"); //配置允许请求header的访问,如 :X-TOKEN super.addCorsMappings(registry); } 4.视图控制器配置 通过重写 addViewControllers 方法,使用 ViewControllerRegistry 注册类来实现视图控制器配置。示例代码如下: @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("/index");//默认视图跳转 registry.addViewController("/index").setViewName("/index"); registry.addViewController("/article").setViewName("/article"); registry.addViewController("/error/404").setViewName("/error/404"); registry.addViewController("/error/500").setViewName("/error/500"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); super.addViewControllers(registry); } 上面的代码等同于如下代码: @RequestMapping(value = { "/", "/index" }) public String index() { return "index"; } @RequestMapping(value = "article") public String article() { return "article"; } @RequestMapping(value = "/error/404") public String error_404() { return "/error/404"; } @RequestMapping(value = "/error/500") public String error_500() { return "/error/500"; } 5.消息转换器配置 通过覆盖重写 configureMessageConverters 方法来配置消息转换器。示例代码如下:自定义字符串转换器: @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8")); converters.add(converter); } 自定义fastjson转换器: @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); //1.需要定义一个convert转换消息的对象; FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); //2.添加fastJson的配置信息,比如:是否要格式化返回的json数据; FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //3处理中文乱码问题 List<MediaType> fastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); //4.在convert中添加配置信息. fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); //5.将convert添加到converters当中. converters.add(fastJsonHttpMessageConverter); } 6.数据格式化器配置 通过重写 addFormatters 方法来添加数据格式化器。Spring MVC 接受 HTTP 请求会把参数自动绑定映射到 Controller 请求参数上。Spring 中没有默认配置将字符串转换为日期类型。这时可以通过添加一个 DateFormatter 类来实现自动转换。示例代码如下: @Override public void addFormatters(FormatterRegistry registry) { super.addFormatters(registry); registry.addFormatter(new DateFormatter("yyyy-MM-dd")); } 7.视图解析器配置 通过覆盖重写 configureViewResolvers() 方法来配置视图解析器。代码示例如下: @Override public void configureViewResolvers(ViewResolverRegistry registry) { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix(""); viewResolver.setSuffix(".html"); viewResolver.setCache(false); viewResolver.setContentType("text/html;charset=UTF-8"); viewResolver.setOrder(0); registry.viewResolver(viewResolver); super.configureViewResolvers(registry); } 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(四):Spring Boot 自定义 Web MVC 配置 原文地址: https://www.zwqh.top/article/info/9 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
前言 Spring MVC 是构建在 Servlet API 上的原生框架,并从一开始就包含在 Spring 框架中。本文主要通过简述 Spring MVC 的架构及分析,并用 Spring Boot + Spring MVC + MyBatis (SSM)+ Thymeleaf(模板引擎) 框架来简单快速构建一个 Web 项目。 Web MVC 架构及分析 MVC 三层架构如图所示,红色字体代表核心模块。其中 MVC 各分层分别为: Model (模型层)处理核心业务(数据)逻辑,模型对象负责在数据库中存取数据。这里的“数据”不仅限于数据本身,还包括处理数据的逻辑。 View(视图层)用于展示数据,通常数据依据模型数据创建。 Controller(控制器层)用于处理用户输入请求和响应输出,从试图读取数据,控制用户输入,并向模型发送数据。Controller 是在 Model 和 View 之间双向传递数据的中间协调者。 Spring MVC 架构及分析 Spring MVC 处理一个 HTTP 请求的流程,如图所示:整个过程详细介绍:1.用户发送请求至前端控制器 DispatcherServlet。2.DispatcherServlet 收到请求调用处理器映射器 HandlerMapping。3.处理器映射器根据请求 URL 找到具体的 Controller 处理器返回给 DispatcherServlet。4.DispatcherServlet 通过处理器适配器 HandlerAdapter 调用 Controller 处理请求。5.执行 Controller 处理器的方法。6.Controller 执行完成返回 ModelAndView。7.HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet。8.DispatcherServlet 将 ModelAndView 的 ViewName 传给视图解析器 ViewReslover。9.ViewReslover 解析后返回具体的视图 View。10.DispatcherServlet 传递 Model 数据给 View,对 View 进行渲染(即将模型数据填充至视图中)。11-12.DispatcherServlet 响应用户。 Spring Boot + Spring MVC + MyBatis + Thymeleaf 本段我们主要通过构建项目,实现一个分页查询。 1.项目构建 项目结构如图所示: 1.1 pom 引入相关依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zwqh</groupId> <artifactId>spring-boot-ssm-thymeleaf</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-ssm-thymeleaf</name> <description>spring-boot-ssm-thymeleaf</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- mysql 数据库驱动. --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.12</version> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 1.2 WebMvcConfig 配置 package cn.zwqh.springboot.config; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 静态资源配置 */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");//静态资源路径 css,js,img等 registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");//视图 registry.addResourceHandler("/mapper/**").addResourceLocations("classpath:/mapper/");//mapper.xml super.addResourceHandlers(registry); } /** * 视图控制器配置 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("/index");//设置默认跳转视图为 /index registry.setOrder(Ordered.HIGHEST_PRECEDENCE); super.addViewControllers(registry); } /** * 视图解析器配置 控制controller String返回的页面 视图跳转控制 */ @Override public void configureViewResolvers(ViewResolverRegistry registry) { // registry.viewResolver(new InternalResourceViewResolver("/jsp/", ".jsp")); super.configureViewResolvers(registry); } } 1.3 application.properties 配置 #thymeleaf spring.thymeleaf.cache=false #datasource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=true spring.datasource.username=root spring.datasource.password=root #mybatis mybatis.mapper-locations=classpath:/mapper/*.xml #logging logging.path=/user/local/log logging.level.cn.zwqh=debug logging.level.org.springframework.web=info logging.level.org.mybatis=error 1.4 Controller @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/list") public ModelAndView showUserList(int pageNum, int pageSize) { PageInfo<UserEntity> pageInfo = userService.getUserList(pageNum, pageSize); ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("index"); modelAndView.addObject("pageInfo",pageInfo); return modelAndView; } } 1.5 Service 及 ServiceImpl UserService public interface UserService { PageInfo<UserEntity> getUserList(int pageNum, int pageSize); } UserServiceImpl @Service public class UserServiceImpl implements UserService{ @Autowired private UserDao userDao; @Override public PageInfo<UserEntity> getUserList(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<UserEntity> list=userDao.getAll(); PageInfo<UserEntity> pageData= new PageInfo<UserEntity>(list); System.out.println("当前页:"+pageData.getPageNum()); System.out.println("页面大小:"+pageData.getPageSize()); System.out.println("总数:"+pageData.getTotal()); System.out.println("总页数:"+pageData.getPages()); return pageData; } } 1.6 Dao public interface UserDao { /** * 获取所有用户 * @return */ List<UserEntity> getAll(); } 记得在启动类里加上@MapperScan @SpringBootApplication @MapperScan("cn.zwqh.springboot.dao") public class SpringBootSsmThymeleafApplication { public static void main(String[] args) { SpringApplication.run(SpringBootSsmThymeleafApplication.class, args); } } 1.7 Mapper.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.UserDao"> <resultMap type="cn.zwqh.springboot.model.UserEntity" id="user"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="userSex" column="user_sex"/> </resultMap> <!-- 获取所有用户 --> <select id="getAll" resultMap="user"> select * from t_user </select> </mapper> 1.8 实体 UserEntity public class UserEntity { private Long id; private String userName; private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } 1.9 html 页面 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <p>Thymeleaf是一个用于Web和独立环境的现代服务器端Java模板引擎。SpringBoot推荐使用Thymeleaf。</p> <p>下面是表格示例:</p> <table border="1"> <thead> <tr> <th width="100">ID</th> <th width="100">姓名</th> <th width="100">性别</th> </tr> </thead> <tbody> <tr th:each="user:${pageInfo.list}"> <td th:text="${user.id}"></td> <td th:text="${user.userName}"></td> <td th:text="${user.userSex}"></td> </tr> </tbody> </table> <p> <a th:href="${'/user/list?pageNum='+(pageInfo.pageNum-1>=1?pageInfo.pageNum-1:1)+'&pageSize=10'}">上一页</a> <a th:href="${'/user/list?pageNum='+(pageInfo.pageNum+1<=pageInfo.pages?pageInfo.pageNum+1:pageInfo.pages)+'&pageSize=10'}">下一页</a> 总数:<span th:text="${pageInfo.total}"></span> </p> </body> </html> 测试 通过浏览器访问:http://127.0.0.1:8080/user/list?pageNum=1&pageSize=10 进行测试。效果如图: 示例代码 github 码云 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题: Spring Boot 2.X(三):使用 Spring MVC + MyBatis + Thymeleaf 开发 web 应用 原文地址: https://www.zwqh.top/article/info/2 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
MyBatis 简介 概述 MyBatis 是一款优秀的持久层框架,支持定制化 SQL、存储过程以及高级映射。它采用面向对象编程的方式对数据库进行 CRUD 的操作,使程序中对关系数据库的操作更方便简单。它支持 XML 描述符配置文件和注解两种方式执行 SQL 语句。“简单灵活”是它在对象关系映射工具上的最大优势。 mybatis-spring-boot-starter 过去使用 MyBatis 开发,需要各种配置文件、实体类、Dao 层映射关联、还有一大推其它配置。经过进行不断的优化后,终于他来了,mybatis-spring-boot-starter 可以做到无需配置只用注解开发,也可以使用简单的配置轻松上手。当然两种方式都需要在 POM 文件引入mybatis-spring-boot-starter: <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> 集成 MyBatis 准备工作 1.构建一个 Spring Boot项目 2.建立 MySQL 数据库(db_test),创建表(t_user)及添加部分测试数据 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT 'ID', `user_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户姓名', `user_sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户性别', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user -- ---------------------------- BEGIN; INSERT INTO `t_user` VALUES (1, '刘备', '男'); INSERT INTO `t_user` VALUES (2, '孙尚香', '女'); INSERT INTO `t_user` VALUES (3, '周瑜', '男'); INSERT INTO `t_user` VALUES (4, '小乔', '女'); INSERT INTO `t_user` VALUES (5, '诸葛亮', '男'); INSERT INTO `t_user` VALUES (6, '黄月英', '女'); INSERT INTO `t_user` VALUES (7, '关羽', '男'); INSERT INTO `t_user` VALUES (8, '张飞', '男'); INSERT INTO `t_user` VALUES (9, '赵云', '男'); INSERT INTO `t_user` VALUES (10, '黄总', '男'); INSERT INTO `t_user` VALUES (11, '曹操', '男'); INSERT INTO `t_user` VALUES (12, '司马懿', '男'); INSERT INTO `t_user` VALUES (13, '貂蝉', '女'); INSERT INTO `t_user` VALUES (14, '吕布', '男'); INSERT INTO `t_user` VALUES (15, '马超', '男'); INSERT INTO `t_user` VALUES (16, '魏延', '男'); INSERT INTO `t_user` VALUES (17, '孟获', '男'); INSERT INTO `t_user` VALUES (18, '大乔', '女'); INSERT INTO `t_user` VALUES (19, '刘婵', '男'); INSERT INTO `t_user` VALUES (20, '姜维', '男'); INSERT INTO `t_user` VALUES (21, '廖化', '男'); INSERT INTO `t_user` VALUES (22, '关平', '男'); COMMIT; SET FOREIGN_KEY_CHECKS = 1; 3.新建用户实体类 UserEntity.java public class UserEntity { private Long id; private String userName; private String userSex; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } } 注解方式 1.添加相关 Maven 依赖 <dependencies> <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> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 热部署模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> </dependency> <!-- mysql 数据库驱动. --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies> 2.application.properties 添加相关配置 #datasource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=true spring.datasource.username=root spring.datasource.password=root 在启动类中添加对 mapper 包扫描@MapperScan @SpringBootApplication @MapperScan("cn.zwqh.springboot.dao") public class SpringBootMybatisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMybatisApplication.class, args); } } 或者直接在 Mapper 类上面添加注解@Mapper,建议使用上面那种,不然每个 mapper 加个注解也挺麻烦的 3.Mapper 开发 public interface UserDao { //使用注解方式 /** * 获取所有用户 * @return */ @Select("select * from t_user") @Results({ @Result(property = "userName",column = "user_name"), @Result(property = "userSex",column = "user_sex") }) List<UserEntity> getAll2(); /** * 根据id获取用户 * @param id * @return */ @Select("select * from t_user where id=#{id}") @Results({ @Result(property = "userName",column = "user_name"), @Result(property = "userSex",column = "user_sex") }) List<UserEntity> getOne2(Long id); /** * 新增用户 * @param user */ @Insert("insert into t_user (user_name,user_sex) values(#{userName},#{userSex})") void insertUser2(UserEntity user); /** * 修改用户 * @param user */ @Update("update t_user set user_name=#{userName},user_sex=#{userSex} where id=#{id}") void updateUser2(UserEntity user); /** * 删除用户 * @param id */ @Delete("delete from t_user where id=#{id}") void deleteUser2(Long id); } 注解:@Select 是查询类的注解,所有的查询均使用这个@Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。@Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值@Update 负责修改,也可以直接传入对象@delete 负责删除 4. restful 接口测试 UserController @RestController @RequestMapping("/user") public class UserController { @Autowired private UserDao userDao; //使用注解方式 /** * 获取所有用户 * @return */ @RequestMapping("/getAll2") public List<UserEntity> getAll2(){ return userDao.getAll2(); } /** * 根据id获取用户 * @return */ @RequestMapping("/getOne2") public List<UserEntity> getOne2(Long id){ return userDao.getOne2(id); } /** * 新增用户 * @param user * @return */ @RequestMapping("/insertUser2") public String insertUser2(UserEntity user) { userDao.insertUser2(user); return "insert success"; } /** * 修改用户 * @param user * @return */ @RequestMapping("/updateUser2") public String updateUser2(UserEntity user) { userDao.updateUser2(user); return "update success"; } /** * 删除用户 * @param user * @return */ @RequestMapping("/deleteUser2") public String deleteUser2(Long id) { userDao.deleteUser2(id); return "delete success"; } } 启动项目后可以通过浏览器访问 http://127.0.0.1:8080/user/getOne2?id=1 进行测试,其他雷同。也可以编写单元测试进行测试。 XML 方式 1.pom 文件如上 2.application.properties 添加相关配置 #datasource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=UTF-8&useSSL=true spring.datasource.username=root spring.datasource.password=root #mybatis mybatis.mapper-locations=classpath:/mapper/*.xml 3.Mapper 层开发 public interface UserDao { //mapper.xml方式 /** * 获取所有用户 * @return */ List<UserEntity> getAll(); /** * 根据id获取用户 * @return */ List<UserEntity> getOne(Long id); /** * 新增用户 * @param user */ void insertUser(UserEntity user); /** * 修改用户 * @param user */ void updateUser(UserEntity user); /** * 删除用户 * @param id */ void deleteUser(Long id); } 4.xml 映射文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zwqh.springboot.dao.UserDao"> <resultMap type="cn.zwqh.springboot.model.UserEntity" id="user"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="userSex" column="user_sex"/> </resultMap> <!-- 获取所有用户 --> <select id="getAll" resultMap="user"> select * from t_user </select> <!-- 根据用户ID获取用户 --> <select id="getOne" resultMap="user"> select * from t_user where id=#{id} </select> <!-- 新增用户 --> <insert id="insertUser" parameterType="cn.zwqh.springboot.model.UserEntity"> insert into t_user (user_name,user_sex) values(#{userName},#{userSex}) </insert> <!-- 修改用户 --> <update id="updateUser" parameterType="cn.zwqh.springboot.model.UserEntity"> update t_user set user_name=#{userName},user_sex=#{userSex} where id=#{id} </update> <!-- 删除用户 --> <delete id="deleteUser" parameterType="Long"> delete from t_user where id=#{id} </delete> </mapper> 如何选择使用 个人觉得,注解方式适合轻量级的项目,现在的微服务项目比较适合这种模式;对于大型项目,复杂的多表联合查询sql用 xml 更适合。 扩展: 使用 MyBatis 分页插件 pagehelper 1. pom.xml 添加依赖 <!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.12</version> </dependency> 2. pagehelper 使用 @RestController @RequestMapping("/user") public class UserController { @Autowired private UserDao userDao; /** * 使用pagehelper分页插件 * @param pageNum * @param pageSize * @return */ @RequestMapping("/pagehelperTest") public List<UserEntity> pagehelperTest(int pageNum,int pageSize){ PageHelper.startPage(pageNum, pageSize); return userDao.getAll(); //直接使用上面的 mapper } } 3. 测试 浏览器直接访问 http://127.0.0.1:8080/user/pagehelperTest?pageNum=1&pageSize=10 ,改变参数试试。 示例代码 码云 github 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题: Spring Boot 2.X(二):集成 MyBatis 数据层开发 原文地址: https://www.zwqh.top/article/info/3 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
什么是 Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架遵循”约定优于配置“的思想,清除了原先使用Spring框架的那些样板化的配置,继承了原有Spring框架的优秀基因,从而帮助开发者快速开发应用。 SpringBoot的特性 总的来说就是简单、快速、方便。 SpringBoot的核心模块 创建SpringBoot项目 本文使用开发工具为eclipse 官网Maven构建项目 1、访问 https://start.spring.io/ 2、选择构建工具中Maven Project、Java、Spring Boot版本2.1.8以及一些项目的基本信息,可参考下图所示: 3、点击 Generate Project 下载项目压缩包 4、Import —> Existing Maven Projects —> Next —> 选择解压后的文件夹 —> Finsh Eclipse构建项目 1、首先安装SpringBoot插件,Help —> Eclipse Marketplace —> 搜索'Spring' —> 安装Spring Tools 4 - for Spring Boot··· —> Install,直至完成restart 2、File —> New —> Project,弹出新建项目的框 3、搜索‘Spring’,找到选择Spring Boot子目录下的Spring Starter Project,点击Next 4、填写相关项目信息后,点击Next,选择需要的依赖包,再点击Next,确认无误后Finish,完成创建。 HelloWorld 我们根据上面构建了一个helloworld项目,基于它我们来实现简单的web示例以及测试示例 1.项目目录结构介绍 如上图所示,Spring Boot 的基础结构共三个大块: • src/main/java Java源代码目录,主程序入口 HelloworldApplication,可以通过直接运行该类来启动 Spring Boot 应用 • src/main/resources 资源文件目录,该目录用来存放应用的一些配置以及静态资源。application.properties为配置文件,可以配置应用名、服务器端口、数据库链接等等。由于引入了Web模块,因此产生了 static 目录与 templates 目录,static 用于存放静态资源,如图片、CSS、JavaScript等,templates 用于存放 Web 页面的模板文件。 • src/test/java 单元测试目录,生成的 HelloworldApplicationTests 通过 JUint 4 实现,可以直接用运行 Spring Boot 应用的测试。 2.Maven 配置分析 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>helloworld</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>helloworld</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Spring Boot 版本:2.1.8.RELEASE 打包形式: jar (Spring Boot 默认的打包形式) pom.xml 文件中项目依赖 dependencies 默认有两个模块: • spring-boot-starter-web 全栈Web开发模块,包含嵌入式Tomcat、Spring MVC。 • spring-boot-starter-test 通用测试模块,包含JUnit、Hamcrest、Mockito。项目构建的 build 部分:引入了 Spring Boot 的 Maven 插件。 3.实现一个简单的应用 • 新建 package,命名为 com.example.demo.controller ,可以根据实际的构建情况修改自己的路径。 • 新建 HelloController 类,代码如下: @RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World"; } } • 启动应用,通过浏览器访问 http://localhost:8080/hello ,我们可以看到返回了预期的结果:Hello World 。 4.单元测试 • 打开 src/test/java 下的测试入口 HelloApplicationTests ,编写一个简单的单元测试来模拟 HTTP 请求。代码如下: @RunWith(SpringJUnit4ClassRunner.class)//引入Spring对JUnit4的支持 @SpringBootTest public class HelloApplicationTests { private MockMvc mvc;//用于模拟调用 Controller 的接口发起请求, @Before //预加载内容,用来初始化对 HelloController 的模拟 public void setUp() throws Exception{ mvc=MockMvcBuilders.standaloneSetup(new HelloController()).build(); } @Test public void hello() throws Exception{ mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Hello World"))); } } • 注意需要引入下面的静态引用,让 status 、content 、 equalTo 函数可用: import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; • 开发环境中调试引入热部署依赖,修改代码后就无须手动重启了。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> </dependencies> 总结 使用 Spring Boot 可以非常方便、快速搭建项目,使我们不用关心框架之间的兼容性,适用版本等各种问题,我们想使用任何东西,仅仅添加一个配置就可以,所以使用 Spring Boot 非常适合构建微服务。 示例代码 码云 github 非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处. 原文标题:Spring Boot 2.X(一):入门篇 原文地址: https://www.zwqh.top/article/info/1 如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...