MVC:Model +View +Controller(数据模型+视图+控制器);
三层架构:Presentation tier +Application tier +Data tier(展现层+应用层+数据访问层);
实际上MVC只存在三层架构的展现层。
MyMvcConfig.java
@Configuration
@EnableWebMvc
@ComponentScan("MVC")
public class MyMvcConfig extends WebMvcConfigurerAdapter{
@Bean
public InternalResourceViewResolver viewResolver()
{
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
WebInitializer.java
public class WebInitializer implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MyMvcConfig.class);
ctx.setServletContext(servletContext);
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
HelloController.java
@Controller
public class HelloController{
@RequestMapping("/index")
public String hello()
{
return "index";
}
}
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title heretitle>
head>
<body>
Spring MVC
body>
html>
DemoObj.java
public class DemoObj {
private Long id;
private String name;
public DemoObj() { //jackson对对象和json做转换时一定需要此构造。需要jackson包。
super();
}
public DemoObj(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;
}
}
DemoAnnoController.java
@Controller
@RequestMapping("/anno")
public class DemoAnnoController {
//此方法未标注路径,因此使用类级别的路径/anno;produces可定制返回的response的媒体类型和字符集。
@RequestMapping(produces="text/plain;charset=UTF-8")
public @ResponseBody String index(HttpServletRequest request)
{
return "url:"+request.getRequestURL()+" can access";
}
//接受路径参数,结合@PathVariable使用
@RequestMapping(value="/pathvar/{str}",produces="text/plain;charset=UTF-8")
public @ResponseBody String demoPathVar(@PathVariable String str , HttpServletRequest request)
{
return "url:"+request.getRequestURL()+" can access,str "+str;
}
//演示常规的request参数获取
@RequestMapping(value="/requestParam",produces="text/plain;charset=UTF-8")
public @ResponseBody String passRequestParam(Long id , HttpServletRequest request)
{
return "url:"+request.getRequestURL()+" can access,id "+id;
}
@RequestMapping(value="/obj",produces="application/json;charset=UTF-8")//演示参数到对象
@ResponseBody //@ResponseBody支持将返回值放在response体内,而不是返回一个页面;此注解可放置在返回值前或者方法上。
public String passObj(DemoObj obj , HttpServletRequest request)
{
return "url:"+request.getRequestURL()+" can access,obj id "+obj.getId()+" obj name "+obj.getName();
}
//映射不同的路径到相同的方法
@RequestMapping(value={"/name1","/name2"},produces="text/plain;charset=UTF-8")
public @ResponseBody String remove(HttpServletRequest request)
{
return "url:"+request.getRequestURL()+" can access";
}
}
@RestController演示
DemoController.java
@RestController //声明控制器,并且返回数据时不需要@ResponseBody。
@RequestMapping("/rest")
public class DemoController {
@RequestMapping(value="/getjson",produces={"application/json;charset=UTF-8"})//返回数据的媒体类型为json.
public DemoObj getjson(DemoObj obj)
{
return new DemoObj(obj.getId()+1,obj.getName()+"yy");//直接返回对象,对象会自动转换成json。
}
}
程序的静态文件等需要直接访问,避免被拦截。
MyMvcConfig.java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{ //addResourceLocations指的是文件放置的目录,addResourceHandler指的是对外暴露的访问路径。
registry.addResourceHandler("/image/**").addResourceLocations("classpath:/image/");
}
拦截器实现对每一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。
可让普通的Bean实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter类来实现自定义拦截器。
DemoInterceptor.java
public class DemoInterceptor extends HandlerInterceptorAdapter{
//重写preHandle方法,在请求发生前执行。
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle)
{
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
System.out.println("请求前");
return true;
}
//重写postHandle方法,在请求发生后执行。
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handle,ModelAndView modelAndView)
{
long startTime = (Long) request.getAttribute("startTime");
request.removeAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("请求后");
System.out.println("本次请求处理时间为:"+new Long(endTime -startTime)+"ms");
request.setAttribute("handlingTime", endTime-startTime);
}
}
MyMvcConfig.java
//拦截器的Bean.
@Bean
public DemoInterceptor demoInterceptor()
{
return new DemoInterceptor();
}
//重写addInterceptors方法,注册拦截器。
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(demoInterceptor());
}
ExceptionHandlerAdvice.java
@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(value=Exception.class)//@ExceptionHandler用于全局处理控制器里的异常。
public ModelAndView exception(Exception exception,WebRequest request)
{
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage","出错啦");
return modelAndView;
}
@ModelAttribute//@ModelAttribute本来的作用是绑定键值对到Model里,此处是让全局的@RequestMapping都能获得在此处设置的键值对。
public void addAttributes(Model model)
{
model.addAttribute("msg","额外信息");
}
@InitBinder
public void initBinder(WebDataBinder webDataBinder)
{ //用来自动绑定前台请求参数到Model中
//@InitBinder方法不能有返回值,它必须为void
//WebDataBinder是DataBinder的子类,用于完成由表单到JavaBean属性的绑定。
webDataBinder.setDisallowedFields("id");
}
}
AdviceController.java
@Controller
public class AdviceController{
@RequestMapping("/advice")
public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj)
{
throw new IllegalArgumentException("非常抱歉,参数有误.来自@ModelAttribute"+msg);
}
}
error.jsp
<body>
${ errorMessage }
body>
@RequestMapping("/index")
public String hello()
{
return "index";
}
此处没有任何业务处理,只是简单的转向,写了至少三行代码。实际开发中会涉及大量这样的页面转向,我们可以通过配置addViewControllers来简化配置:
MyMvcConfig.java
@Override
public void addViewControllers(ViewControllerRegistry registry)
{
registry.addViewController("/index").setViewName("/index");
}
路径参数如果带”.”的话,”.”后面的值将被忽略。例如:
通过重写configurePathMatch方法可不忽略”.”后面的参数。
MyMvcConfig.java
@Override
public void configurePathMatch(PathMatchConfigurer configurer)
{
configurer.setUseSuffixPatternMatch(false);
}
HttpMessageConverter是用来处理request和response里的数据的。
MyMessageConverter.java
public class MyMessageConverter extends AbstractHttpMessageConverter<DemoObj>{
public MyMessageConverter() {
//新建一个我们自定义的媒体类型yd/yds。
super(new MediaType("yd","yds",Charset.forName("UTF-8")));
}
//重写readInternal方法,处理请求的数据。处理由"-"隔开的数据,并转成DemoObj的对象
@Override
protected DemoObj readInternal(Class extends DemoObj> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
String temp = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));
String[] tempArr = temp.split("-");
return new DemoObj(new Long(tempArr[0]), tempArr[1]);
}
//表明本HttpMessageConverter只处理DemoObj这个类。
@Override
protected boolean supports(Class> clazz) {
return DemoObj.class.isAssignableFrom(clazz);
}
//重写writeInternal,处理如何输出数据到response。此例中,我们在原样输出前面加上"hello"。
@Override
protected void writeInternal(DemoObj obj, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
String out = "hello :"+obj.getId()+"-"+obj.getName();
outputMessage.getBody().write(out.getBytes());
}
}
addViewControllers中添加viewController映射页面访问演示页面。
registry.addViewController("/converter").setViewName("/converter");
配置自定义的HttpMessageConverter的Bean,在Spring MVC里注册HttpMessageConverter有两个方法:
@Override
public void extendMessageConverters(List> converters) {
converters.add(converter());
}
@Bean
public MyMessageConverter converter()
{
return new MyMessageConverter();
}
ConverterController.java
@Controller
public class ConverterController{
@RequestMapping(value="/convert",produces={"yd/yds"})
/*@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,
比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。
通过@requestBody可以将请求体中的JSON字符串绑定到相应的bean上,当然,也可以将其分别绑定到对应的字符串上。*/
public @ResponseBody DemoObj convert(@RequestBody DemoObj obj)//绑定到相应的bean上
{
return obj;
}
}
converter.jsp
<body>
<div id="resp">div>
<input type="button" onclick="rep();"value="请求" />
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">script>
<script>
function rep()
{
$.ajax({
url:"convert",
data:"1-yangdong",
type:"POST",
contentType:"yd/yds",
success:function(data)
{
$("#resp").html(data);
}
});
}
script>
body>
本节的服务器端推送方案都是基于:当客户端向服务器发送请求,服务端会抓住这个请求不放,等有数据更新的时候才返回给客户端,当客户端收到消息后,再向服务端发送请求,周而复始。这种方式的好处是减少了服务器的请求数量,大大减少了服务器的压力。
本节提供了基于SSE的服务器端推送和基于Servlet3.0+的异步方法特性,其中第一种方式需要新式浏览器的支持,第二种方式是跨浏览器的。
1:SSE
除了IE,其他基本都支持了。
SseController.java
@Controller
public class SseController {
@RequestMapping(value="/push",produces="text/event-stream;charset=UTF-8")//text/event-stream,这是服务器端SSE的支持。
public @ResponseBody String push(HttpServletResponse response,HttpServletRequest request) throws UnsupportedEncodingException
{ System.out.println("SseController");
Random r = new Random();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "data:Testing 1,2,3"+r.nextInt()+"\n\n";
}
}
sse.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<meta name="content-type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<div id="msgFromPush">div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">script>
<script>
if(!!window.EventSource)
{
var source = new EventSource('push');
s = '';
source.addEventListener('message',function(e){
s+=e.data+"
";
$("#msgFromPush").html(s);
});
source.addEventListener('open',function(e){
console.log("连接打开.");
},false);
source.addEventListener('error',function(e){
if(e.readyState == EventSource.CLOSED)
{
console.log("连接关闭.");
}else
{
console.log(e.readyState);
}
},false);
}
else
{
console.log("你的浏览器不支持sse");
}
script>
body>
html>
registry.addViewController("/sse").setViewName("/sse");
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);//开启异步支持
PushService.java
@Service
public class PushService {
private DeferredResult deferredResult;
public DeferredResult getAsyncUpdate() {//在PushService里产生DeferredResult给控制器用,通过
deferredResult = new DeferredResult();//@Scheduled注解的方法定时更新DeferredResult。
return deferredResult;
}
@Scheduled(fixedDelay = 5000)
public void refresh()
{
if (deferredResult != null) {
deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
}
}
}
AysncController.java
@Controller
public class AysncController{
@Autowired
private PushService pushService;
@RequestMapping("/defer")
@ResponseBody
public DeferredResult deferredResult()//异步任务的实现是通过控制器从另一个线程返回一个DeferredResult。
{
return pushService.getAsyncUpdate();
}
}
async.jsp
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">script>
<script>
deferred();//页面打开就向后台发送请求
function deferred()
{
$.get('defer',function(data){
console.log(data);//在控制台输出服务端推送的数据
deferred();//一次请求完成后再向后台发送请求。
})
}
//使用的是jquery的Ajax请求,所以没有浏览器的兼容问题
script>
@EnableScheduling //开启计划任务的支持
registry.addViewController("/async").setViewName("/async");
参考书籍:Spring Boot 实战
以上只是学习所做的笔记, 以供日后参考。如有错误请指正,谢谢啦!!!