说明:本文参考http://blog.csdn.net/catoop/article/details/50501664/,在此基础上进行验证与总结。
相当简单,pom.xml文件中配置:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.3.1.RELEASEversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
SpringBootSampleApplication类如下:
@SpringBootApplication
public class SpringBootSampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSampleApplication.class,args);
}
}
然后就是controller中的rest接口了:
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping
public String hello() {
return "Hello Spring-Boot";
}
@RequestMapping("/info")
public Map<String, String> getInfo(@RequestParam String name) {
Map<String, String> map = new HashMap<>();
map.put("name", name);
return map;
}
@RequestMapping("/list")
public List<Map<String, String>> getList() {
List<Map<String, String>> list = new ArrayList<>();
Map<String, String> map = null;
for (int i = 1; i <= 5; i++) {
map = new HashMap<>();
map.put("name", "Shanhy-" + i);
list.add(map);
}
return list;
}
}
运行main函数,直接通过内置的tomcat就发布了web程序。通过8080端口可以访问:
http://localhost:8080/hello。几分钟内就搭建了一个web应用,省去了配置web.xml,springmvc.xml等文件的麻烦,大大节省了时间。需要注意的一点是,SpringBootSampleApplication要放在顶层包中,这就是约定大于配置的规则。可以看到,controller中的注解不是用的@Controller,而是@RestController,该注解式sprig4.0引入的,包含了@Controller 和 @ResponseBody,是两者的合二为一,因此也不需要加@ResponseBody注解,就可以直接响应json结果。
SpringBoot不推荐使用jsp,如果要使用,需要做一些配置:在pom中添加
<dependency>
<groupId>org.apache.tomcat.embedgroupId>
<artifactId>tomcat-embed-jasperartifactId>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
dependency>
在resources目录下创建application.properties:
# 页面默认前缀目录
spring.mvc.view.prefix=/WEB-INF/jsp/
# 响应页面默认后缀
spring.mvc.view.suffix=.jsp
# 自定义属性,可以在Controller中读取
application.hello=Hello World
在 src/main 下面创建 webapp/WEB-INF/jsp 目录用来存放jsp页面index.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<html>
<head>
<title>Spring Boot Sampletitle>
head>
<body>
Time: ${time}<br>
Message: ${message}
body>
html>
另外控制器是用@Controller而不要用@RestController,否则会因为@RestController中包含@ResponseBody,从而将index当做json字符串输出。
@Controller
public class PageController {
// 从 application.properties 中读取配置,如取不到默认值为Hello
@Value("${application.hell:Hello Shanhy}")
private String hello = "Hello";
/**
* 默认页
* @RequestMapping("/") 和 @RequestMapping 是有区别的
* 如果不写参数,则为全局默认页,加入输入404页面,也会自动访问到这个页面。
* 如果加了参数“/”,则只认为是根页面。
*/
@RequestMapping(value = {"/","/index"})
public String index(Map model){
model.put("time", new Date());
model.put("message", this.hello);
return "index";
}
}
使用Spring-Boot时,嵌入式servlet容器通过扫描注解的方式注册Servlet、Filter和Listener。Spring Boot的主Servlet为DispatcherServley,默认的url-pattern为“/”。
在Spting Boot中添加自己的servlet/filter/linstener有两种方法:代码注册和注解自动注册。代码注册通过ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean获得控制,也可以通过ServletContextInitializer接口直接注册。注解方式注册是在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener 可以直接通过@WebServlet,@WebFilter和@WebListener注解自动注册。
代码方式注册。在SpringBootSampleApplication中添加:
/**
* 使用代码方式注册servlet
*/
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new MyServlet(), "/xs/*");
}
注册的Servlet:
public class MyServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doGet()<<<<<<<<<<<");
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("");
out.println("");
out.println("Hello World ");
out.println("");
out.println("");
out.println("大家好,我的名字叫Servlet
");
out.println("");
out.println("");
}
}
该servlet拦截/xs/的请求,并通过out对象输出信息。
使用注解方式注册。在SpringBootSampleApplication上添加@ServletComponentScan注解,在MyServlet类上加上@WebServlet(urlPatterns = “/xs”)注解即可。filter和listener注册同理。
刚刚说过,在DispatcherServlet中默认拦截“/”,但是可以修改拦截路径。在SpringBootSampleApplication中添加代码:
@Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
registration.getUrlMappings().clear();
registration.addUrlMappings("*.do");
registration.addUrlMappings("*.json");
return registration;
}
修改之后访问地址必须同时满足requestMapping映射的url和后缀为do或者json,才能返回对应视图。
过滤器和监听器的注册跟servlet类似,也是通过代码注册和注解自动注册。代码注册就不说了,注解自动注册先在SpringBootSampleApplication上添加@ServletComponentScan注解,并在过滤器类上添加@WebFilter注解:
@WebFilter
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行过滤操作");
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("过滤器初始化");
}
}
在监听器类上添加@WebListener注解:
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContex初始化");
System.out.println(sce.getServletContext().getServerInfo());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContex销毁");
}
}
另一个监听器:
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session 被创建");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session销毁");
}
}
在启动时可以看到输出:
ServletContex初始化
Apache Tomcat/8.0.30
过滤器初始化
访问页面输出:
执行过滤操作
Session 被创建
这里顺便提一下监听器和过滤器的启动顺序,在系统启动时,监听器最先初始化,因此先调用MyServletContextListener的contextInitialized方法。另一个监听器是创建session用的,因此在访问页面时调用。过滤器在监听器之后初始化,初始化时调用init方法,在访问页面时调用doFilter方法。而销毁时则相反,后初始化的先销毁。
拦截器作用和过滤器有点类似,但区别也是很明显的。过滤器是在Servlet中定义的,而拦截器是在spring中定义的。过滤器在web容器初始化过程中初始化,在web请求到达服务器之前进行过滤。而拦截器是随着spring容器初始化而进行初始化的,在请求到达DispatcherServlet之后进行拦截。拦截器中有三个方法,preHandle在request请求被响应之前执行,postHandle在request请求被响应之后,视图渲染之前执行,afterCompletion在视图渲染之后执行。在SpringBoot中实现自定义拦截器只需要3步:
(1) 创建自己的拦截器实现类,并实现HandlerInterceptor接口。
(2) 创建一个Java类继承WebMvcConfigurerAdapter,并重写addInterceptor方法。
(3) 将自定义拦截器添加到拦截器链中。
示例如下:
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>在请求处理之前进行调用(Controller方法调用之前)");
return true;// 只有返回true才会继续向下执行,返回false取消当前请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)");
}
}
注册拦截器:
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
/*registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");*/
super.addInterceptors(registry);
}
}
注册第二个过滤器,执行后结果如下:
>>>MyInterceptor1>>>>>>>在请求处理之前进行调用(Controller方法调用之前)
>>>MyInterceptor2>>>>>>>在请求处理之前进行调用(Controller方法调用之前)
>>>MyInterceptor2>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
>>>MyInterceptor1>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
Session 被创建
>>>MyInterceptor2>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
>>>MyInterceptor1>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
可以看到两个过滤器的不同方法的执行顺序。
只有经过DispatcherServlet 的请求,才会走拦截器链,我们自定义的Servlet 请求是不会被拦截的,比如我们自定义的myServlet 是不会被拦截器拦截的。并且不管是属于哪个Servlet,只要符合过滤器的过滤规则,过滤器都会拦截。
最后说明下,我们上面用到的 WebMvcConfigurerAdapter 并非只是注册添加拦截器使用,其顾名思义是做Web配置用的,它还可以有很多其他作用
SpringBoot默认提供了静态资源处理,使用WebMvcAutoConfiguration中的参数配置各种属性。建议使用默认的配置方式,如果需要特殊处理的再通过配置进行修改(如果要想完成自主控制WebMVC,就需要在@Configuration注解的配置类上增加@EnableWebMvc,增加该注解后WebMvcAutoConfiguration中的配置就不会生效,然后配置自定义信息)。
在应用程序启动时,可在控制台中看到:
o.s.w.s.handler.SimpleUrlHandlerMapping: Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
o.s.w.s.handler.SimpleUrlHandlerMapping: Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
默认配置的/**映射到/static(或/public、/resources、/META-INF/resources)(static、public、resources 等目录都在 classpath下面,如 src/main/resources/static)。默认配置的/webjars/**映射到classpath:/META-INF/resources/webjars/。
假如访问地址http://localhost:8080/xxx.jpg,则会自动映射到/static、/public、/resources、/META-INF/resources目录,优先级顺序为:META/resources > resources > static > public 。
自定义静态资源映射
一般资源随着发布包一起打包,但有时候有些资源需要在管理系统中动态维护,资源目录是指定的。这时候就要自定义资源映射了。下面看具体代码:
实现类继承 WebMvcConfigurerAdapter 并重写方法 addResourceHandlers (上面说过,WebMvcConfigurerAdapter并不只有注册拦截器的方法,还有很多其他用于配置web的方法):
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
super.addResourceHandlers(registry);
}
访问myres 文件夹中的xxx.jpg 图片的地址为 http://localhost:8080/myres/xxx.jpg ,这样使用代码的方式自定义目录映射,并不影响Spring Boot的默认映射,可以同时使用。
如果我们将/myres/* 修改为 /* 与默认的相同时,则会覆盖系统的配置,可以多次使用 addResourceLocations 添加目录,优先级先添加的高于后添加的。
如果要指定一个绝对路径的文件夹,如H:/myimgs/ ,则只需要使用addResourceLocations 指定
// 可以直接使用addResourceLocations 指定磁盘绝对路径,同样可以配置多个位置,注意路径写法需要加上file:
registry.addResourceHandler("/myimgs/**").addResourceLocations("file:H:/myimgs/");
使用代码来定义静态资源的映射,其实Spring Boot也为我们提供了可以直接在 application.properties(或.yml)中配置的方法。配置方法如下:
# 默认值为 :/**
spring.mvc.static-path-pattern=
# 默认值为 :classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
spring.resources.static-locations=这里设置要指向的路径,多个使用英文逗号隔开
使用 spring.mvc.static-path-pattern 可以重新定义pattern,如修改为 /myres/** ,则访问static 等目录下的fengjing.jpg文件应该为 http://localhost:8080/myres/fengjing.jpg ,修改之前为 http://localhost:8080/fengjing.jpg
使用 spring.resources.static-locations 可以重新定义 pattern 所指向的路径,支持 classpath: 和 file: (上面已经做过说明)
注意 spring.mvc.static-path-pattern 只可以定义一个,目前不支持多个逗号分割的方式。
在页面中像平时一样使用即可:
<body>
<img alt="读取默认配置中的图片" src="${pageContext.request.contextPath }/pic.jpg">
<br/>
<img alt="读取自定义配置myres中的图片" src="${pageContext.request.contextPath }/myres/fengjing.jpg">
body>
Webjars就是前端需要的资源如js,css等放到classpath:/META-INF/resources/webjars/中,然后打包成jar发布到maven仓库中。
举个栗子,以js文件为例,假设系统前端要使用的js文件资源存放路径如下:
META-INF/resources/webjars/jquery/2.1.4/jquery.js
META-INF/resources/webjars/jquery/2.1.4/jquery.min.js
META-INF/resources/webjars/jquery/2.1.4/jquery.min.map
META-INF/resources/webjars/jquery/2.1.4/webjars-requirejs.js
前面说过,Spring Boot默认将/webjars/**映射到classpath:/META-INF/resources/webjars/ ,因此在jsp页面中引入jquery.js的方法是:
<script type="text/javascript" src="${pageContext.request.contextPath }/webjars/jquery/2.1.4/jquery.js">script>
在pom.xml中引入:
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>2.1.4version>
dependency>
但是我们实际开发中,可能会遇到升级版本号的情况,如果我们有100多个页面,几乎每个页面上都有按上面引入jquery.js 那么我们要把版本号更换为3.0.0,一个一个替换显然不是最好的办法。如何来解决?
首先在pom.xml 中添加依赖:
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>webjars-locatorartifactId>
dependency>
然后增加一个WebJarsController:
@Controller
public class WebJarsController {
private final WebJarAssetLocator assetLocator = new WebJarAssetLocator();
@ResponseBody
@RequestMapping("/webjarslocator/{webjar}/**")
public ResponseEntity
最后在页面中使用的方式:
<script type="text/javascript" src="${pageContext.request.contextPath}/webjarslocator/jquery/jquery.js">script>
Spring 默认提供了静态资源版本映射的支持。 当我们的资源内容发生改变时,由于浏览器缓存,用户本地的资源还是旧资源,为了防止这种情况发生导致的问题。我们可能会选择在资源文件后面加上参数“版本号”或其他方式。使用版本号参数,如:
<script type="text/javascript" src="${pageContext.request.contextPath }/js/common.js?v=1.0.1">script>
使用这种方式,当我们文件修改后,手工修改版本号来达到URL文件不被浏览器缓存的目的。同样也存在很多文件都需要修改的问题。
然而Spring在解决这种问题方面,提供了2种解决方式。
资源名称md5方式
1. 修改 application.properties 配置文件:
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
所有 /** 请求的静态资源都会被处理。
创建 ResourceUrlProviderController 文件:
@ControllerAdvice
public class ResourceUrlProviderController {
@Autowired
private ResourceUrlProvider resourceUrlProvider;
@ModelAttribute("urls")
public ResourceUrlProvider urls() {
return this.resourceUrlProvider;
}
}
在页面中使用的写法
<script type="text/javascript" src="${pageContext.request.contextPath }${urls.getForLookupPath('/js/common.js') }">script>
当我们访问页面后,HTML中实际生成的代码为:
<script type="text/javascript" src="/项目名/js/common-c6b7da8fffc9be141b48c073e39c7340.js">script>
资源版本号方式
这是对所有资源的统一版本控制,不像上面一个md5是针对文件的。除了在 application.properties中的配置有所区别,页面使用和md5的一样。
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/**,/v1.0.0/**
spring.resources.chain.strategy.fixed.version=v1.0.0
这样配置后,以上面 common.js 为例,实际页面中生成的HTML代码为:
<script type="text/javascript" src="/项目名/v1.0.0/js/common.js">script>
处理原理:
页面中首先会调用urls.getForLookupPath方法,返回一个/v1.0.0/js/common.js或/css/common-c6b7da8fffc9be141b48c073e39c7340.js ,然后浏览器发起请求。
当请求的地址为md5方式时,会尝试url中的文件名中是否包含-,如果包含会去掉后面这部分,然后去映射的目录(如/static/)查找/js/common.js文件,如果能找到就返回。
当请求的地址为版本号方式时,会在url中判断是否存在/v1.0.0 ,如果存在,则先从URL中把 /v1.0.0 去掉,然后再去映射目录查找对应文件,找到就返回。
总结:
1. 我们使用第三方的库时,建议使用webjars的方式,通过动态版本号(webjars-locator 的方式)来使用(因为第三方库在项目开发中变动频率很小,即便是变动也是版本号的修改)。
2. 我们使用自己存放在静态资源映射目录中的资源的时候,建议使用md5 资源文件名的方式来使用(项目开发中一些css、js文件会经常修改)。
3. 项目素材文件建议放到 classpath:/static (或其他)目录中,打包在项目中,通过CMS维护的一些图片和资源,我们使用配置引用到具体的磁盘绝对路径来使用。
4. 注意使用md5文件名方式的时候,Spring 是有缓存机制的,也就是说,在服务不重启的情况下,你去变动修改这些资源文件,其文件名的md5值并不会改变,只有重启服务再次访问才会生效。如果需要每次都获取实际文件的md5值,需要重写相关类来实现,我们不建议这样做,因为一直去计算文件md5值是需要性能代价的。