Spring Boot实现Web的常用功能
通常在Web开发中,会涉及静态资源的访问支持、视图解析器的配置、转换器和格式化器的定制、文件上传下载等功能,甚至还需要考虑到与Web服务器相关联的Servlet相关组件的定制。Spring Boot框架支持整合一些常用Web框架,从而实现Web开发,并默认支持Web开发中的一些通用功能。
Spring MVC的整合支持
为了实现并简化Web开发,Spring Boot为一些常用的Web开发框架提供了整合支持,例如Spring MVC、Spring WebFlux等框架。使用Spring Boot进行Web开发时,只需要在项目中引入对应的Web开发框架的依赖启动器即可。
Spring MVC自动配置介绍
在Spring Boot项目中,一旦引入了Web依赖启动器spring-boot-starter-web,那么Spring Boot整合Spring MVC框架默认实现的一些xxxAutoConfiguration自动配置类就会自动生效,几乎可以在无任何额外配置的情况下进行Web开发。Spring Boot为整合Spring MVC框架实现Web开发,主要提供了以下自动化配置的功能特性。 1)内置了两个视图解析器:ContentNegotiatingViewResolver和BeanNameViewResolver。 2)支持静态资源以及WebJars、 3)自动注册了转换器和格式化器。 4)支持Http消息转换器 5)自动注册了消息代码解析器。 6)支持静态项目首页index.html 7)支持定制应用图标favicon.ico 8)自动初始化Web数据绑定器ConfigurableWebBindingInitializer Spring Boot整合Spring MVC进行Web开发时提供了很多默认配置,而且大多数时候使用默认配置即可满足开发需求。例如,Spring Boot整合Spring MVC进行Web开发时,不需要额外配置视图解析器。
Spring MVC功能拓展实现
Spring Boot整合Spring MVC进行Web开发时提供了很多的自动化配置,但在实际开发中还需要开发者对一些功能进行扩展实现。 1、项目基础环境搭建 使用Spring Initializr方式创建名称为chapter05的Spring Boot项目,并在Dependencies依赖选择中选择Web模块下的Web依赖启动器和Template Engines模块下的Thymeleaf依赖启动器。 复制chapter04项目下的所有文件至chapter05 功能扩展实现 接下里使用Spring Boot整合Spring MVC进行Web开发,实现简单的页面跳转功能,这里我们将使用Spring Boot提供的WebMvcConfigurer接口编写自定义配置,并对Web功能进行适当扩展。 1)注册视图管理器。在chapter05项目的com.example.chapter05.config包下创建一个实现WebMvcConfigurer接口的配置类MyMVCconfig,用于对MVC框架功能进行扩展。
MyMVCconfig.java
package com.example.chapter05.config;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class MyMVCconfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/toLoginPage").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
}
MyMVCconfig实现了接口WebMvcConfigurer的addViewControllers(View ControllerRegistry registry)方法。在addViewControllers()方法内部,使用ViewControllerRegistry的addViewController()方法分别定义了"/toLoginPage"和"/login.html"的请求控制,并使用setViewName(“login”)方法将路径映射为login.html页面。 定制完MVC的视图管理功能后,就可以进行效果测试了。为了演示这种定制效果,我们将之前的用户登录控制器LoginController代码全部注释。重启chapter05项目,项目启动后两种方式访问。 2)注册自定义拦截器。WebMvcConfigurer接口提供了许多MVC开发相关方法,例如,添加拦截器方法addInterceptors()、添加格式化器方法addFormatters()等。 在项目com.example.chapter05.config包下创建一个自定义拦截类MyInterceptor,并编写简单的拦截业务代码。 MyInterceptor.java
package com.example.chapter05.config;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url=request.getRequestURI();
Object loginUser=request.getSession().getAttribute("loginUser");
if(url.startsWith("/admin")&&null==loginUser){
response.sendRedirect("/toLoginPage");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception{
request.setAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception{
}
}
自定义拦截器类MyInterceptor实现了HamdlerInterceptor接口。在preHandle()方法中,如果用户请求以"/admin"开头,则判断用户是否登录,如果没有登录,则重定向到"/toLoginPage"请求对应的登录页面。在postHandle()方法中,使用request对象向前端页面传递表示年份的currentYear数据。 然后在自定义配置类MyMVCconfig中,重写addInterceptors()方法注册自定义的拦截器
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login.html");
}
上述代码中,先使用@Autowired注解引入自定义的MyInterceptor拦截器组件,然后重写其中的addInterceptors()方法注册自定义的拦截器。在注册自定义拦截器时,使用addPathPatterns("/**")方法拦截所有路径请求,excludePathPatterns("/login.html")方法对/login.html路径的请求进行了放行处理。 重启chapter05项目,浏览器访问http://localhost:8080/admin 访问后自动跳转到了用户登录页面,同时在页面中动态显示出了当前年份,这说明此次定制的自定义拦截器生效了。 需要说明的是,Spring Boot在整合Spring MVC过程中提供了许多默认自动化配置和特性,开发者可以通过Spring Boot提供的WebMvcConfigurer接口对MVC功能进行定制和扩展。如果开发者不想使用Spring Boot整合MVC时提供的一些默认配置,而是想使用绝对的自定义管理,那么可以编写@Configuration注解配置类,同时添加@EnableWebMvc注解关闭Spring Boot提供的所有关于MVC功能的默认配置。
Spring Boot整合Servlet三大组件
进行Servlet开发时,通常首先自定义Servlet、Filter、Listener三大组件,然后在文件web.xml中进行配置,而Spring Boot使用的是内嵌式Servlet容器,没有提供外部配置文件web.xml,下面学习Spring Boot如何整合Servlet的相关组件。
组件注册整合Servlet三大组件
在Spring Boot中,使用组件注册方式整合内嵌Servlet容器的Servlet、Filter、Listener三大组件时,只需将这些自定义组件通过ServletRegistrationBean、FilterRegistrationBean、ServletListener RegistrationBean类注册到容器中即可。 使用组件注册方式整合Servlet 创建自定义Servlet类。在chapter05项目中创建名为com.exmaple.chapter05.servletComponent的包,并在包下创建一个继承了HttpServlet的类MyServlet。
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class MyServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello MyServlet");
}
}
使用@Component注解将MyServlet类作为组件注入Spring容器。MyServlet类继承自HttpServlet,通过HttpServletResponse对象向页面输出"hello MyServlet"。 2)创建Servlet组件配置类。在项目com.exmaple.chapter05.config包下创建一个Servlet组件配置类ServletConfig,用来对Servlet相关组件进行注册。 ServletConfig.java
package com.example.chapter05.config;
import com.example.chapter05.servletComponent.MyServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean getServlet(MyServlet myServlet){
ServletRegistrationBean registrationBean=new ServletRegistrationBean(myServlet,"/myServet");
return registrationBean;
}
}
使用@Configuration注解将ServletConfig标注为配置类,ServletConfig类内部的getServlet()方法用于注册自定义的MyServlet,并返回ServletRegistrationBean类型Bean对象。 启动项目进行测试。浏览器访问。 使用组件注册方式整合Filter 1)创建自定义Filter类。在com.example.servletComponent包下创建一个类MyFilter MyFilter.java
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("hello MyFilter");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
使用@Component注解当前MyFilter类作为组件注入到Spring容器中。MyFilter类实现了Filter接口,并重写了init()、doFilter()、destroy()方法,在doFilter()方法中向控制台打印了"hello MyFilter"字符串。 2)向Servlet组件配置类注册自定义Filter类。打开之前创建的Servlet组件配置类ServletConfig,将该自定义Filter类使用组件注册方式进行注册
@Bean
public FilterRegistrationBean<MyFilter> getFilter(MyFilter filter){
FilterRegistrationBean<MyFilter> registrationBean=new FilterRegistrationBean<MyFilter>(filter);
registrationBean.setUrlPatterns(Arrays.asList("toLoginpage","/myFilter"));
return registrationBean;
}
上述代码中,使用组件注册方式注册自定义的MyFilter类。在getFilter(MyFilter filter)方法中,使用setUrlPatterns(Arrays.asList("/toLoginPage","/myFilter"))方法定义了过滤的请求路径为"/toLoginPage"和"/myFilter",同时使用@Bean注解将当前组装好的FilterRegistrationBean对象作为Bean组件返回。 完成Filter的自定义配置后启动项目,项目启动成功后,在浏览器上访问"http://localhost:8080/myFilter"查看控制台打印效果。 使用组件注册方法整合Listener 1)创建自定义Listener类。在com.example.servletComponent包下创建一个类MyListener
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@Component
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ...");
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ...");
}
}
使用@Component注解将MyListener类作为组件注册到Spring容器中。MyListener类实现了ServletContextListener接口,并重写了contextInitialized()和contextDestroyed()方法。 需要说明的是,Servlet容器提供了很多Listener接口,例如ServletRequestListener、HttpSessionListener、ServletContextListener等,我们在自定义Listener类时要根据自身需要选择实现对应接口即可。 2)向Servlet组件配置类注册自定义Listener类。打开之前创建的Servlet组件配置类ServletConfig,将该自定义Listener类使用组件注册方式进行注册。
@Bean
public ServletListenerRegistrationBean<MyListener> getServletListener(MyListener myListener){
return new ServletListenerRegistrationBean<MyListener>(myListener);
}
完成自定义Listener组件后启动项目,项目启动成功后查看控制台打印效果。 程序启动成功后,控制台会打印出自定义Listener组件中定义的输出语句"contextInitialized…" 将自定义的Servlet组件配置类ServletConfig全部注释并重启项目后,自定义的Servlet、Filter、Listener组件仍然可以生效。出现这种情况的主要原因是:嵌入式Servlet容器对Servlet、Filiter、Listener组件进行了自动化识别和配置,而自定义的Servlet、Filter、Listener都继承/实现了对应的类/接口,同时自定义的Servlet、Filter、Listener组件都使用了@Component注解,这些组件会被自动扫描为Spring组件。 使用ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean组件组装配置的根本目的是对一些请求路径和参数进行初始化设置和组装。假设没有组件注册类,那么自定义Servlet虽然生效,但无法确定是哪个访问路径生效。自定义的Filter会对所有的请求都进行过滤,不会出现选择性过滤的情况。而自定义的Listener则没有太大影响,因为定制组件基本不需要设置什么参数。
路径扫描整合Servlet三大组件
在Spring Boot中,使用路径扫描的方式整合内嵌式servlet容器的Servlet、Filter、Listener三大组件时,首先需要在自定义组件上分别添加@WebServlet、@WebFilter和@WebListener注解进行声明,并配置相关注解属性,然后在项目主程序启动类上使用@ServletComponentScan注解开启组件扫描即可。 使用路径扫描方式整合Servlet、Filter、Listener 将之前自定义的Serlet组件配置类ServletConfig全部注释掉,同时注解掉自定义Servlet、Filter、Listener三大组件类上的@Component注解。 MyServlet.java
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/annotationServlet")
public class MyServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello MyServlet");
}
}
MyFilter.java
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(value = {"/antionLogin","/antionMyFilter"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("hello MyFilter");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
MyListener.java
package com.example.chapter05.servletComponent;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ...");
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ...");
}
}
Chapter05Application.java
package com.example.chapter05;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class Chapter05Application {
public static void main(String[] args) {
SpringApplication.run(Chapter05Application.class, args);
}
}
|