开发颠覆者SpringBoot实战---------SpringMVC的基础学习
pom.xml文件
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>myspringboot</groupId>
<artifactId>springmvc4</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- 全局配置 -->
<properties>
<!-- java编译级别 -->
<java.version>1.7</java.version>
<!-- 编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- web -->
<jsp.version>2.2</jsp.version>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<!-- spring -->
<spring-framework.version>4.1.5.RELEASE</spring-framework.version>
<!-- logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<!-- springmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- 其他web依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<!-- spring and transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- 使用sl4j和logback作为日志) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 对象和json或xml的转换 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.5.3</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 非必要,简化io操作 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<!-- maven编译级别 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
一、简单控制器
创建一个index.jsp文件在src/main/resources/views下。
/**
* springmvc配置
* @author think
*/
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class MyMvcConfig {
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/classes/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
}
/**
* web配置
* @author think
*/
public class WebInitializer implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MyMvcConfig.class);
context.setServletContext(servletContext);//与配置类关联
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
/**
* 简单的一个控制器
* @author think
*/
@Controller
public class ControllerTest {
@RequestMapping("/index")//配制方法与url的映射
public String hello(){
return "index";
}
}
WebApplicationInitializer是spring提供用来配置servlet3.0的接口,从而替代web.xml,此接口会自动SpringServletContainerInitializer(用来启动serlvet3.0容器)获取到。
二、springmvc常用注解
1、@Controller声明是一个控制器,dispatcherservlet会自动扫描此注解的类,并将web讲求映射到有@RequestMapping的方法上。
2、在声明普通bean时,使用@Component、@Service、@Repository、@Controller是等同的,但是在作为springmvc的控制器bean时,只能使用@Controller。
3、@RequestMapping用来映射web请求、处理类和方法的,支持servlet的request和response作为参数,也支持对request和response的媒体类型进行配置。
4、@ResponseBody支持将返回值放在response体内,而不是返回一个页面,在很多基于ajax的程序时,可以用此注解返回数据,而不是页面。
5、@RequestBody允许参数再request体内,而不是在地址后面
6、@PathVariable用来接收路径参数,如/news/001,可以接收001作文参数
7、@RestController是组合注解,组合了@Controller和@ResponseBody
简单使用:
/**
* 实体类
* @author think
*/
public class Obj {
private Long id;
private String name;
public Obj() {//对象转换json时候需要此空参构造
super();
}
public Obj(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Obj [id=" + id + ", name=" + name + "]";
}
}
/**
* 参数转化
* @author think
*/
@Controller
@RequestMapping("/obj")//映射此类的访问路径是/obj
public class ObjController {
@RequestMapping(produces = "text/plain;charset=UTF-8")//使用此类的访问路径,produces设置返回的response的媒体类型和字符集
public @ResponseBody String normal(HttpServletRequest request){
return "url:" + request.getRequestURL();
}
@RequestMapping(value = "/{pathVar}", produces = "text/plain;charset=UTF-8")//value接收的路径参数,结合@PathVariable获取访问路径参数
public @ResponseBody String pathVar(@PathVariable String pathVar, HttpServletRequest request){
return "url:" + request.getRequestURL() + ",路径参数:" + pathVar;
}
@RequestMapping(value = "/requestParam", produces = "text/plain;charset=UTF-8")//常规的request作为参数,获取路径后面拼接的id=xx
public @ResponseBody String requestParam(Long id, HttpServletRequest request){
return "url:" + request.getRequestURL() + ",id:" + id;
}
@RequestMapping(value = "/turn2Obj", produces = "application/json;charset=UTF-8")//对象自动转换json,返回的媒体对象为json
@ResponseBody//可以用在方法上也可以用在方法中
public Obj turn2Obj(Obj obj, HttpServletRequest request){
return new Obj(obj.getId(), obj.getName());
}
@RequestMapping(value = {"/name1", "/name2"}, produces = "text/plain;charset=UTF-8")//不同路径映射到相同的方法
public @ResponseBody String multiPath(HttpServletRequest request){
return "url:" + request.getRequestURL();
}
}
三、静态资源映射和拦截器
SpringMVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解来开启对SpringMVC的配置支持,这个累里面有许多好用的方法,这样我们可以重写这个类的方法来完成我们的常用配置。
创建一个静态文件demo.txt在src/main/resource/assets下。
/**
* 自定义拦截器
* @author think
*/
public class Interceptor extends HandlerInterceptorAdapter{
//请求发生前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
//请求发生后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
request.removeAttribute("startTime");
Long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - startTime));
request.setAttribute("handlingTime", endTime - startTime);
}
}
/**
* 配置
* @author think
*/
@Configuration
@EnableWebMvc//开启springmvc支持,没有此句,重写WebMvcConfigurer无效
@ComponentScan("com")
public class MyMvcConfig implements WebMvcConfigurer{
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/classes/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
@Bean//配置拦截器的bean
public Interceptor getInterceptor(){
return new Interceptor();
}
@Override//静态资源映射
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//addResourceHandler对外暴露的访问路径,addResourceLocations文件放置的目录
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
}
@Override//注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getInterceptor());
}
}
这样就可以访问项目目录下的静态文件资源,同时可以对请求进行拦截。
四、@ControllerAdvice
@ControllerAdvice作为一个全局配置的控制器,其组合了@Component,自动注册为spring的bean,所有配置信息都写在本类里面,注解了@Controller类的方法可以使用@ExceptionHandler,@ModelAttribute,@InitBinder,对所有注解了@RequestMapping的控制器都有效。
创建一个处理异常的页面error.jsp文件在src/main/resources/views下。
/**
* 全局配置
* @author think
*/
@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(value = Exception.class)//全局异常配置
public ModelAndView exception(Exception exception, WebRequest request){
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage", exception.getMessage());
return modelAndView;
}
@ModelAttribute//自动绑定键值对到model中,所有@RequestMapping的能获得此处的值
public void addAttributes(Model model){
model.addAttribute("msg", "额外信息");
}
@InitBinder//自动绑定前端的请求参数到model中
public void initBinder(WebDataBinder binder){
binder.setDisallowedFields("id");//将request参数封装为对象的时候忽略id这个属性,正常获取id参数还是可以获取到的。
}
}
@Controller
public class AdviceController {
@RequestMapping("advice")
public String getGlobal(@ModelAttribute("msg") String msg, Obj obj, String id){
throw new IllegalArgumentException("异常,msg:" + msg + ",obj:" + obj + ",id:" + id);
}
}
发送 “springmvc4/advice?id=55&name=名字” 请求后,在error.jsp中用${errorMessage }就可以获取到:
五、其他配置
1、快捷返回页面ViewController
//简单的返回页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
super.addViewControllers(registry);
}
用上面的代码可以替代掉下面的代码,从而达到一个简化的目的,前提是这只是一个简单的页面转向,不涉及到任何的逻辑。
@Controller
public class ControllerTest {
@RequestMapping("/index")
public String hello(){
return "index";
}
}
2、路径匹配参数配置
在SpringMVC中,请求路径如果带有“.”的话,后面的值将会被忽略,例如访问“/springmvc4/obj/pathvar.33”时,是无法获取“obj/”后面的完整参数。通过重写configurePathMatch将设置为不忽略“.”后面的参数:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
super.configurePathMatch(configurer);
}
六、文件上传
创建一个upload.jsp在src/main/resource/views下:
<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"><br>
<input type="submit" value="上传">
</form>
在控制器中用MultipartFile或MultipartFile[]接受单个或多个文件:
/**
* 页面跳转
* @author think
*/
registry.addViewController("/toUpload").setViewName("/upload");
/**
* 文件上传
* @author think
*/
@Controller
public class UploadController {
@RequestMapping(value="/upload", method=RequestMethod.POST)
public @ResponseBody String upload(MultipartFile file){
try {
FileUtils.writeByteArrayToFile(new File("F://" + file.getOriginalFilename()), file.getBytes());
return "ok";
} catch (Exception e) {
return "wrong";
}
}
}
七、HttpMessageConverter
HttpMessageConverter是用来处理request和response数据的,Spring为我们内置了大量的HttpMessageConverter,下面是自定义HttpMessageConverter。
/**
* 自定义MessageConverter
* @author thinkHttp
*/
public class MyMessageConverter extends AbstractHttpMessageConverter<Obj>{
//新建一个自定义的媒体类型
public MyMessageConverter() {
super(new MediaType("application", "x-wisely", Charset.forName("UTF-8")));
}
//处理请求数据:处理由"-"隔开的数据,并转为Obj对象
@Override
protected Obj readInternal(Class<? extends Obj> clazz, HttpInputMessage input)
throws IOException, HttpMessageNotReadableException {
String temp = StreamUtils.copyToString(input.getBody(), Charset.forName("UTF-8"));
System.out.println(temp);
String[] sts = temp.split("-");
return new Obj(Long.parseLong(sts[0]), sts[1]);
}
//表明本类只处理Obj这个类
@Override
protected boolean supports(Class<?> clazz) {
boolean b = Obj.class.isAssignableFrom(clazz);
return b;
}
//处理如何输出数据到response中
@Override
protected void writeInternal(Obj obj, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
String out = "hello:" + obj.getId() + "-" + obj.getName();
output.getBody().write(out.getBytes());
}
}
/**
* 页面跳转
* @author think
*/
registry.addViewController("/beginConvert").setViewName("/converter");
自定义的HttpMessageConverter在SpringMVC进行注册有两种方式:
- configureMessageConverters:重载会覆盖掉SpringMVC默认注册的多个HttpMessageConverter。
- extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter。
所以我们重写extendMessageConverters:
@Bean
public MyMessageConverter converter(){
return new MyMessageConverter();
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
super.extendMessageConverters(converters);
}
@Controller
public class ConverterController {
@RequestMapping(value = "/convert", produces = {"application/x-wisely"})
public @ResponseBody Obj converts(@RequestBody Obj obj){
return obj;
}
}
创建一个converter.jsp在src/main/resource/views下:
<body>
<div id="resp"></div>
<input type="button" onclick="req()" value="请求">
<script type="text/javascript">
function req(){
$.ajax({
url : 'convert',
data : '1-xiaoming',
type : 'POST',
contentType : 'application/x-wisely',
success : function(data){
alert(data);
document.getElementById('resp').innerHTML = data;
}
});
}
</script>
</body>
请求和响应数据的拦截机制在自定义的HttpMessageConverter的supports方法中:只有当控制器中@ResponseBody返回的对象类型与supports方法的对象类型相一致,才会继续readInternal(读取request的数据)和writeInternal(数据响应到response)。
八、服务端推送技术
推送方案:当客户端向服务端发送请求,服务端会抓住这个请求不放,等有数据更新才返回给客户端,当客户端接受到消息后,再向服务端发送请求,周而复始。这种方式好处是减少了服务器的请求数量,减少服务器的压力。
1、SSE
/**
* 控制器
* @author think
*/
@Controller
public class SSEController {
//text/event-stream这种媒体类型是服务器端SSE的支持,每5秒向浏览器推送随机消息
@RequestMapping(value = "/push", produces = "text/event-stream")
public @ResponseBody String push(){
Random random = new Random();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "data:Testing " + random.nextInt() + "\n\n";
}
}
/**
* 页面跳转
* @author think
*/
registry.addViewController("/sse").setViewName("/sse");
创建一个sse.jsp在src/main/resource/views下:
<body>
<div id="msgFromPush"></div>
<a>ssss</a>
<script type="text/javascript">
if (!!window.EventSource) {//!!是数据强转布尔类型,EventSource对象是新式的浏览器才有,EventSource是SSE的客户端
var source = new EventSource('push');
var s = '';
source.addEventListener('message', function(e){//添加SSE客户端监听,获得服务端推送的消息
s += e.data + '<br>';
document.getElementById('msgFromPush').innerHTML = s;
});
source.addEventListener('open', function(e){
//console.log('连接打开');
}, false);
source.addEventListener('error', function(e){
if (e.readyState == EventSource.CLOSED) {
//console.log('连接断开');
} else {
alert(e.readyState);
//console.log(e.readyState);
}
}, false);
} else {
//console.log('您的浏览器不支持SSE');
}
</script>
</body>
2、Servlet3.0+的异步方法处理
/**
* 开启异步方法支持
* @author think
*/
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
servlet.setAsyncSupported(true);
/**
* 开启定时任务支持
* @author think
*/
@EnableScheduling//开启定时任务支持
public class MyMvcConfig extends WebMvcConfigurerAdapter{
}
/**
* 页面跳转
* @author think
*/
registry.addViewController("/async").setViewName("/async");
/**
* 控制器
* @author think
*/
//异步任务的实现是通过控制器从另外一个线程返回一个DeferredResult
@Controller
public class AsyncController {
@Autowired
PushService pushService;
@RequestMapping("/defer")
public @ResponseBody DeferredResult<String> deferCall(){
return pushService.getAsyncUpdate();
}
}
/**
* 定时任务
* @author think
*/
@Service
public class PushService {
private DeferredResult<String> deferredResult;
public DeferredResult<String> getAsyncUpdate(){
deferredResult = new DeferredResult<>();
return deferredResult;
}
@Scheduled(fixedDelay = 5000)
public void refresh(){
if (deferredResult != null) {
deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
}
}
}
在PushService中产生DeferredResult给控制器使用,通过@Scheduled注解的定时任务更新DeferredResult。
创建一个async.jsp在src/main/resource/views下:
<body>
<script type="text/javascript">
deferred();
function deferred(){
$.get('defer', function(data){
console.log(data);
deferred();
});
}
</script>
</body>