整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SpringBootWeb容器配置:JAX-RS和Jersey框架、内嵌容器的配置

pring Boot Web容器

Web应用开发是企业开发的重要领域,Spring Boot 1.X的Web容器管理方式基于Servlet容器技术栈。Servlet容器主要基于同步阻塞I/O架构,HTTP请求和线程是一对一的关系,主要是TPR模型,即一个请求对应一个线程。主要的业务逻辑也是基于命令式的编程模式。以Spring MVC框架为主,Web容器方面以Tomcat为主,也可以通过自动配置功能改为Jetty/UnderTow容器。

Spring Boot 2.X主要基于异步非阻塞I/O架构,HTTP请求基于收敛的线程模型,网络层使用基于Reactor的I/O多路复用模式,业务逻辑基于函数式编程模式,以Spring WebFlux为主要框架。在Web容器方面可以基于Servlet 3.0的异步模式,默认情况下使用Netty作为容器。本节我们主要以Spring Boot 1.X讲解嵌入式Web容器的启动和加载原理,在进阶篇的响应式编程中将介绍Spring 5及Spring Boot 2.X的响应式框架WebFlux对Web应用服务的支持。

Spring Boot Web容器配置

Spring Boot对Web项目的支持主要是Spring Boot对Spring MVC框架的继承。Spring MVC框架是一个基于Servlet容器标准的Web容器框架实现,Spring Boot向Spring MVC提供开箱即用的Starter:springboot-starter-web。

Spring Boot应用中利用自动配置功能,只需要在pom.xml文件中加入下面的Web依赖,就可以直接启动一个Web服务:

Spring Web MVC 框 架 使 用 特 定 的 @Controller 或 者@RestController 注 解 的 Bean 作 为 处 理 HTTP 请 求 的 端 点 , 通 过@RequestMapping注解将控制器中的方法与HTTP请求进行映射,示例如下:

Spring Boot为Spring MVC提供了自动配置功能,包含如下主要配置特性。

● 自动配置ViewResolver引入ContentNegotiatingViewResolver组件功能。示例:在应用中添加ViewResolver组件用来匹配HTML静态页面,如果没有匹配成功,则返回false,由其他ViewResolver继续尝试匹配。ContentNegotiatingViewResolver会组合所有的视图解析器,代码如下。

● 自动注册Converter、GenericConverter、Formatter Bean。

示例:将页面提交数据转化为后台数据,实现格式化,代码如下。

● 对HttpMessageConverters的支持。

示例:Spring Boot可以为HttpMessageConverters类添加自定义转换类,通过这种方式可以将所有的HttpMessageConverters的Bean添加到Converter列表,覆盖默认的转换器列表,代码如下。

● 自动注册MessageCodeResolver。

● 自动使用ConfigurableWebBindingInitializer Bean。

● 使用WebMvcConfigurerAdapter类型的Bean来定制化配置。

默认情况下,Spring Boot会以/src/main/resources/static作为查找静态资源的文件路径,如果想自定义静态资源映射目录,需要重写addResourceHandlers来添加指定路径,重写addResourceLocations来指定静态资源路径。

总之,我们可以根据自己的意愿,对默认的Spring MVC的组件配置加以修改,方法也很简单,通过在IoC容器中注册新的同类型Bean来替换即可。如果你希望完全接管Spring MVC的所有相关配置,可以添加自己的@Configuration,并使用@EnableWebMvc注解实现定制化配置。

JAX-RS和Jersey框架

如果你喜欢JAX-RS和REST风格的编程模型,可以使用下面的Starter替代Spring MVC框架,Spring支持Jersey 1.X和Jersey 2.X等技术框架。这里我们只介绍Spring Boot对Jersey 2.X的支持,在pom.xml文件中加入下面的依赖:

Spring Boot对Jersey的配置有三种主要方式。在开始不同的配置方式前,我们注册一个端点对象资源,示例代码如下:

● 第一种方式,创建一个自定义的ResourceConfig:

● 第二种方式,返回一个ResourceConfig类型的@Bean:

● 第三种方式,配置一组ResourceConfigCustomizer对象。

Spring Boot提供了ResourceConfigCustomizer接口,让我们更灵活地对ResourceConfig对象进行配置。要使用该接口,我们需要先注释掉前面两节中提到的相关代码,然后创建一个类:

默 认 情 况 下 , Jersey 将 以 Servlet 的 形 式 注 册 一 个

ServletRegistrationBean 类 型 的 @Bean 。 它 的 名 字 为

jerseyServletRegistration,该Servlet默认会延迟初始化。

你可以通过spring.jersey.servlet.load-on-startup自定义配置

Jersey组件。通过创建相同名字的Bean,可以禁用或覆盖框架默认的

Bean。设置spring.jersey.type=filter可以使用Filter的形式代替

Servlet , 相 应 的 @Bean 类 型 变 为 jerseyFilter-Registration , 该

Filter有一个@Order属性,你可以通过spring.jersey.filter.order

设 置 该 属 性 。 Servlet 和 Filter 在 注 册 时 都 可 以 使 用

spring.jersey.init.*定义一个属性集合并将其传递给init参数进行

初始化。

内嵌容器的配置

Spring Boot 的 另 一 大 特 性 就 是 支 持 内 嵌 的 Web 容 器 , 包 括Tomcat、Jetty和UnderTow服务器,大多数开发者只需要使用合适的Starter来获取一个完全配置好的实例即可,内嵌服务器默认监听8080端口的HTTP请求。spring-boot-starter-web默认使用Tomcat作为Web容器,你可以在pom.xml中去除spring-boot-starter-tomcat依赖,然后 引 入 spring-boot-starter-jetty 或 者 spring-boot-starterundertow模块作为替代Web容器方案。Starter还提供了以“server.”为前缀的配置项对嵌入式容器配置进行修改。配置项的加载和定制化钩子加载过程如下。

1.自动化配置嵌入式容器

2.初始化TomcatEmbeddedServletContainerFactory的Bean对象

3.定制化Bean扩展逻辑

EmbeddedServletContainerCustomizerBeanPostProcessor在加载Bean后开始初始化配置项PostProcessor的处理逻辑:

4.配置文件加载

从配置文件中,你可以加载配置文件对象的配置值。如果配置文件中没有相关配置项,将使用默认代码设定配置。

5.Web容器定制化

如果你需要对Web容器进行更深入的定制,可以使用对应的Factory自动化配置Tomcat容器,它是初始化的关键流程和步骤,代码示例如下:

下图是Spring Boot启动过程中Tomcat容器完成自动配置的类图结构。我们在最新的Spring Boot下查看Tomcat的相关配置,发现有两个自动装配类,分别包含了三个定制器,还有一个工厂类。

本文给大家讲解的内容是SpringBootWeb容器配置:JAX-RS和Jersey框架、内嵌容器的配置

  1. 下篇文章给大家讲解的是Spring Boot嵌入式Web容器原理
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

开发Web应用程序时,经常需要进行一些全局性的配置,例如添加拦截器、设置消息转换器、配置跨域资源共享(CORS)等。在Spring WebFlux中,我们可以通过实现WebFluxConfigurer接口来进行这些全局配置。本文将详细介绍如何使用WebFluxConfigurer进行全局配置,以及常见的配置项和实现方式。

全局配置接口

WebFluxConfigurer是Spring WebFlux框架中用于配置WebFlux全局特性的接口,它提供了多个方法用于配置不同的功能。

public interface WebFluxConfigurer {

    default void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {}

    default void addFormatters(FormatterRegistry registry) {}

    default void configureViewResolvers(ViewResolverRegistry registry) {}

    default void addArgumentResolvers(ArgumentResolverConfigurer configurer) {}

    default void addReturnValueHandlers(ReturnValueHandlerConfigurer configurer) {}

    default void configureHandlerExceptionResolvers(HandlerExceptionResolverConfigurer configurer) {}

    default void addInterceptors(InterceptorRegistry registry) {}

    default void addCorsMappings(CorsRegistry registry) {}

    default void configurePathMatching(PathMatchConfigurer configurer) {}

    default void configureWebSocketTransport(WebSocketTransportConfigurer configurer) {}

    default void configureWebSocketHandlerMapping(WebSocketHandlerMappingConfigurer configurer) {}

    default void configureHttpMessageReader(ServerHttpMessageReaderConfigurer configurer) {}

    default void configureHttpMessageWriter(ServerHttpMessageWriterConfigurer configurer) {}

    default void configureClientHttpRequestFactory(ClientHttpConnectorConfigurer configurer) {}

    default void addResourceHandlers(ResourceHandlerRegistry registry) {}

    default void configureServerSentEvent(ServerSentEventHttpMessageWriterConfigurer configurer) {}
}

在上述接口中,每个方法对应一个特定的全局配置功能。接下来,我们将详细介绍每个方法的作用和实现方式。

配置消息编解码器

在WebFlux中,消息编解码器(Message Codec)负责将请求和响应的数据转换为对象,并进行序列化和反序列化。可以通过configureHttpMessageCodecs方法来配置消息编解码器。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.customCodecs().encoder(new MyEncoder());
        configurer.customCodecs().decoder(new MyDecoder());
    }
}

在上述示例中,我们通过configureHttpMessageCodecs方法添加了自定义的编码器和解码器,实现了对特定数据格式的处理。

配置格式化器

格式化器(Formatter)用于将字符串类型的数据转换为特定类型的对象,例如将字符串转换为日期对象。可以通过addFormatters方法来配置格式化器。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
    }
}

在上述示例中,我们通过addFormatters方法添加了日期格式化器,将日期字符串转换为yyyy-MM-dd格式的日期对象。

配置视图解析器

视图解析器(View Resolver)用于将逻辑视图名称解析为实际的视图对象,例如将Thymeleaf模板文件解析为HTML视图。可以通过configureViewResolvers方法来配置视图解析器。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(new ThymeleafViewResolver());
    }
}

在上述示例中,我们通过configureViewResolvers方法添加了Thymeleaf视图解析器,用于解析Thymeleaf模板文件。

配置参数解析器和返回值处理器

在WebFlux中,参数解析器(Argument Resolver)用于将请求中的参数解析为控制器方法的参数,而返回值处理器(Return Value Handler)用于将控制器方法的返回值处理为响应数据。可以通过addArgumentResolversaddReturnValueHandlers方法来配置参数解析器和返回值处理器。

配置参数解析器

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addArgumentResolvers(ArgumentResolverConfigurer configurer) {
        configurer.addCustomResolver(new MyArgumentResolver());
    }
}

在上述示例中,我们通过addArgumentResolvers方法添加了自定义的参数解析器MyArgumentResolver

配置返回值处理器

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addReturnValueHandlers(ReturnValueHandlerConfigurer configurer) {
        configurer.addCustomHandler(new MyReturnValueHandler());
    }
}

在上述示例中,我们通过addReturnValueHandlers方法添加了自定义的返回值处理器MyReturnValueHandler

配置异常处理器

在Web应用程序开发中,异常处理器(Exception Resolver)用于处理控制器方法中抛出的异常,并返回适当的响应。可以通过configureHandlerExceptionResolvers方法来配置异常处理器。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHandlerExceptionResolvers(HandlerExceptionResolverConfigurer configurer) {
        configurer.addExceptionHandler(new MyExceptionHandler());
    }
}

在上述示例中,我们通过configureHandlerExceptionResolvers方法添加了自定义的异常处理器MyExceptionHandler

配置拦截器

拦截器(Interceptor)用于在处理请求之前或之后执行一些额外的逻辑,例如记录请求日志、权限验证等。可以通过addInterceptors方法来配置拦截器。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/api/**");
    }
}

在上述示例中,我们通过addInterceptors方法添加了自定义的拦截器MyInterceptor,并指定了拦截路径为/api/**

配置跨域资源共享(CORS)

跨域资源共享(Cross-Origin Resource Sharing,CORS)是一种用于解决跨域访问问题的机制,可以通过addCorsMappings方法来配置CORS。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

在上述示例中,我们通过addCorsMappings方法配置了跨域访问的规则,允许所有来源的请求访问/api/**路径,并指定了允许的方法、是否允许携带凭证以及最大缓存时间。

配置路径匹配规则

路径匹配规则(Path Matching)用于指定URL路径与请求处理器的映射关系。可以通过configurePathMatching方法来配置路径匹配规则。

@Configuration
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configurePathMatching(PathMatchConfigurer configurer) {
        configurer.setUseSuffixPatternMatch(false)
                  .setUseTrailingSlashMatch(false);
    }
}

在上述示例中,我们通过configurePathMatching方法配置了路径匹配规则,禁用了后缀模式匹配和尾部斜杠匹配。

总 结

通过本文的详细介绍,读者应该对如何使用WebFluxConfigurer进行全局配置有了更深入的了解。合理地进行全局配置可以提高开发效率,并确保应用程序的稳定性和可维护性。希望本文能够帮助读者更好地理解和应用Spring WebFlux中的全局配置功能,为开发高效、灵活的Web应用程序提供参考和指导。


公众号:九极客

来源:blog.csdn.net/Gaowumao?type=blog


难度分析

虽然但是听到这个消息的时候,内心还是挺震惊的,毕竟是一个完整的管理系统,功能界面还不能太过简陋。而且从数据库设计到整个系统的交付全由自己一人完成,挑战效果直接拉满!但是冷静下来思考一下,其实也并不是很难,整体的项目流程即为:设计——>文档——>编码——>交付。整体的流程划清之后,就开始一步步从无到有的实现,没想到到最后一步的时候,我竟然才用一天半的时间!!后面又用了半天的时间对整体的项目做了一个优化处理!

项目回顾

最终效果演示:

技术选型:

  • SpringBoot
  • Thymeleaf
  • Mybatis-Plus
  • MySQL
  • PageHelper
  • Lombok
  • Redis(后期页面优化使用)

项目业务流程简介

登录模块、用户模块管理以及对用户的角色分配,新闻公告模块的管理、商品模块(包括对商品、商品分类、订单)的管理、角色模块的管理;对于前端某资源是否有权限操作该资源,使用的是thymeleaf模板语法进行判断鉴别以及文件上传等基本功能。

项目搭建(使用模板引擎)

1. 首先创建Maven项目

引入相应的依赖,构建所需文件目录

2. 编写yaml配置文件

server:
port: 8080

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/supplier?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
# thymeleaf 配置
thymeleaf:
# 关闭缓存
cache: false
prefix: classpath:/templates/

mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml

3. 项目初期基本搭建

在搭建一个项目的初期,为了让系统显得更规范化,我一般会提前做好基础的配置和声明,一个项目从开始设想时所涉及到技术以及这些技术对应的一些基础配置,都要提前规划清楚(个人习惯)。比如:异常处理、拦截器、过滤器、常量类等等。

①异常处理

@ControllerAdvice
public class ExceptionHandler {

private final org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());

@org.springframework.web.bind.annotation.ExceptionHandler(Exception.class)
public ModelAndView exception(HttpServletRequest request, Exception e ) throws Exception {
logger.error("Request URL:{},Exception:{}",request.getRequestURL(),e);

if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class )!= ){
throw e;
}

ModelAndView mv = new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception",e);
mv.setViewName("error/error");

return mv;
}
}

② 拦截器

拦截器主要是对一些资源做的处理,类似于某些资源需要用户登录后才能访问的,某些是不需要的,比如:登录功能就不需要有所拦截,而对用户的各种管理就需要添加拦截操作,这样才能使系统的安全性有所提高。

登录拦截

public class LoginInterceptor extends HandlerInterceptorAdapter {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("user") == ){
response.sendRedirect("/api");
return false;
}
return true;
}
}

资源放行

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api","/api/doLogin");
}
}

4. 编写Controller前端控制器代码

首先创建一个FileController类

① 跳转文件上传的页面

//跳转文件上传的页面
@RequestMapping("/file-upload")
public String userList(){
return "file-upload";
}

② 实现文件上传的功能

@RequestMapping("/doAddForUser")
public String doAdd(User user, @RequestParam("file") MultipartFile files, HttpServletRequest request) throws IOException {
//String path = ;
if (files != && !files.isEmpty()){
String name = UUID.randomUUID().toString().replace("-","");
//获取文件的扩展名
String ext = FilenameUtils.getExtension(files.getOriginalFilename());
//设置文件上传的路径
String url =request.getSession().getServletContext().getRealPath("/upload/");

File file = new File(url);
if (!file.exists()){
file.mkdir();
}
//测试路径
System.out.println(request.getServletPath()+ "/upload");
System.out.println(request.getContextPath() + "/upload/");
//以绝对路径保存重命名后的文件
files.transferTo(new File(url+"/"+name+"."+ext));
user.setAvatar(request.getContextPath() + "/upload/"+name+"."+ext);
}

user.setId(UUID.randomUUID().toString());
String salt = PasswordUtils.getSalt();
String password = user.getPassword();
String encode = PasswordUtils.encode(password, salt);
user.setSalt(salt) ;
user.setPassword(encode);
user.setCreateTime(new Date());
userService.save(user);
return "redirect:/api/users";
}

注:如何想要实现多文件上传需要更改的地方如下:

③ 实现多文件上传功能

在这个项目中并未实现多文件上传功能

private void commons(Object obj, @RequestParam("file") CommonsMultipartFile[] files, HttpServletRequest request) throws IOException {
//String path = ;
for (int i = 0; i < files.length; i++) {

if (files[i] != && !files[i].isEmpty()){
String name = UUID.randomUUID().toString().replace("-","");
//获取文件的扩展名
String ext = FilenameUtils.getExtension(files[i].getOriginalFilename());
//设置文件上传的路径
String url =request.getSession().getServletContext().getRealPath("/upload/");

File file = new File(url);
if (!file.exists()){
file.mkdir();
}
//测试路径
System.out.println(request.getServletPath()+ "/upload");
System.out.println(request.getContextPath() + "/upload/");
//以绝对路径保存重命名后的文件
files[i].transferTo(new File(url+"/"+name+"."+ext));

if (i == 0){
obj.setUrl1(request.getContextPath() + "/upload/"+name+"."+ext);
}
if (i == 1){
obj.setUrl2(request.getContextPath() + "/upload/"+name+"."+ext);
}
if (i == 2){
obj.setUrl3(request.getContextPath() + "/upload/"+name+"."+ext);
}
if (i == 3){
obj.setUrl4(request.getContextPath() + "/upload/"+name+"."+ext);
}
if (i == 4){
obj.setUrl5(request.getContextPath() + "/upload/"+name+"."+ext);
}
}
}
}

5. 项目优化

对于前后端不分离的项目,多数使用的是页面缓存优化,当系统某一瞬间遭受巨大流量时,当第一个用户进行页面访问时可以将该页面数据进行缓存,这样,后来的用户访问到的页面都是从缓存中获取的,这样就减少了 对数据库的操作,减轻了数据库的压力,从而达到优化的处理。

① 导入依赖

<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pools2 对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

② yaml配置

## Redis配置
redis:
# 服务器地址
host: localhost
# 端口
port: 6379
# 数据库
database: 0
# 超时时间
connect-timeout: 10000ms
lettuce:
pool:
# 最大连接数
max-active: 8
# 最大连接阻塞等待时间 默认 -1
max-wait: 10000ms
# 最大空闲时间 默认8
max-idle: 200
# 最小空闲连接 默认8
min-idle: 5

④ Redis序列化处理

@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//key序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//hash类型key的序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//hash类型value的序列化
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}

③ 优化处理

 @Autowired
private NewsService newsService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ThymeleafViewResolver viewResolver;

@RequestMapping(value = "/news",produces = "text/html;charset=utf-8")
@ResponseBody
public String roles(Model model, @RequestParam(value = "pageNo",defaultValue = "1")Integer pageNo
, @RequestParam(value = "pageSize",defaultValue = "10")Integer pageSize
, HttpServletRequest request, HttpServletResponse response){
//Redis中获取页面,如果不为空,则直接返回页面
ValueOperations valueOperations = redisTemplate.opsForValue();
String html = (String) valueOperations.get("news-list");
if (!StringUtils.isEmpty(html)){
return html;
}
PageHelper.startPage(pageNo,pageSize);
List<News> list = newsService.list();
PageInfo<News> pageInfo = new PageInfo<>(list);
model.addAttribute("news",list);
model.addAttribute("pageInfo",pageInfo);
//如果为空,手动渲染,存入Redis中并返回
WebContext context = new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());
html = viewResolver.getTemplateEngine().process("news-list", context);
if (!StringUtils.isEmpty(html)){
//给缓存设置过期时间
valueOperations.set("news-list",html,60, TimeUnit.SECONDS);
}
return html;
}

④ Redis查看

6. 注意事项

注意@Controller和@RestController的区别,本项目使用的是模板渲染页面,而@Controller就是用来响应页面的;而@RestController是用来返回Json

在项目优化阶段需要在方法上添加注解@ResponseBody,因为我们是将整个页面进行缓存 ,所以要将页面转换成JSON进行存储。

注入Thymeleaf解析器,将具体的 页面进行解析成Json字符串进行存储

将存入Redis中的数据加上过期时间,因为页面中的数据要和数据库保持一致,如果用户看到是几十秒之前或一分钟之前的数据还是勉强可以接受的。

目前代码已经同步到Gitee:

https://gitee.com/gao-wumao/supplier

如果有需要的自行前去仓库拉取