Java for Web学习笔记(六三):Controller替代Servlet(5)Model和View
来源:互联网 发布:网络上有重名上不去网 编辑:程序博客网 时间:2024/06/05 10:04
View和Model
我们可以这样简单地理解MVC,C是controller,进行具体的处理,处理后得到的结果(数据)放入Model中,将Model传递到view,view具体负责向client呈现。
Spring提供了很多View:
- InternalResourceView:使用传统的jsp
- JstlView:使用支持JSTL的jsp,JstView和InternalResourceView将Model的属性转换为request的属性,因此可以jsp中通过EL来获取model中的值。
- FreeMarkerView:支持 FreeMarker模板yinq
- VelocityView:支持Apache Velocity模板引擎
- TilesView:支持Apache Tiles模板引擎
- MappingJackson2JsonView:输出为json格式
- MarshallingView:输出为XML格式
- RedirectView:重定向,在HTTP1.1的303 See Other或者HTTP1.0的302 Found的Location header中给出重定向地址
Spring如何找到view:
- 如果controller返回View或者ModelAndView中还有View,则直接使用该view来进行渲染。
- 如果controller的方法返回String作为view名字,或者ModelAndView中的View是一个String,则Spring需要将该view的名字解析为真正的view,这个解析过程需要解析器,即ViewResolver,这是Spring FrameworkServlet中配置,及在dispacher中配置。
- 如果返回model或者model属性,则是通过请求的url翻译为view 名字,这是通过配置好的RequestToViewNameTranslator实现,再通过ViewResolver找到view。
- 如果返回一个response entity,则根据内容协商来找到相应的view。
直接返回View和View名字
本学习的小例子采用代码配置的方式。下面给出重定向到一个jsp文件的例子,这里实际分为两步:
- 重定向:将/重定向至/dashboard
- 重定向采用直接返回RedirectView
- 通过jsp渲染:将/dashboard,通过/WEB-INF/jsp/view/home/dashboard.jsp渲染呈现出来。
- 返回view的名字,并通过配置好的ViewResolver,获得合适的view实例,因为采用jsp文件进行渲染,采用JstlView。
重定向:返回View
@Controllerpublic class HomeController { @RequestMapping("/") public View home(){ // 重定向很简单,直接返回一个RedirectView实例,给出具体的URL。 // URL可以是绝对路径,开始是协议说明,例如http://或者https://,或者网络前缀// // URL也可以是相对路径,则是基于当前的URL的相对路径,如果以/开头,缺省是基于server的URL,即如果返回 new RedirectView("/dashboard"),则会重定向到http://localhost:8080/dashboard中。在绝大多数的情况下,我们需要的是本web app context的URL,采用new RedirectView(String url, boolean contextRelative),第二个参数设置为true,表示基于app context。 return new RedirectView("/dashboard",true); }}
这个例子很简单,我们根本无需使用到model,直接给出具体的URL,但是我们在此学习如何使用model,如何在里面放入数据,如何在代码中获取model里面数据的案例。
@RequestMapping("/")public View home(Map<String,Object> model){ // [1]通过方法参数获取model // [2] 向model放入数据 model.put("dashboardUrl", "dashboard"); // [3] RedirectView如何从model中获取数据 return new RedirectView("/{dashboardUrl}",true); }
通过jsp渲染:返回view名字
(1) 配置ViewResolver
对于Controller方法返回String作为view名字,需要通过ViewResolver 实例(bean)获得真正的view实例。通过jsp来渲染,使用JstlView,由我们在Servlet context配置中设置ViewResolver获得相应的view。在配置中所有的实例均为Bean,因此加上@Bean的标记,这也是满足Spring自动注入的需要。
@Configuration@ComponentScan( basePackages = "cn.wei.flowingflying.chapter13.site", useDefaultFilters = false, includeFilters = @ComponentScan.Filter(Controller.class))public class ServletContextConfiguration { // 1)必须将ViewResolver设置为@Bean。从某种意义上配置需要注入实例的是Bean,而其余代码中需要注入实例的是Service @Bean public ViewResolver viewResolver(){ // 2)使用InternalResourceViewResolver作为resolver,可以将view的名字转为文件名字。 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // 3)根据需求,view是JstlView,即允许JSTL的tag。这句可以不加,不加将采用缺省的AbstractUrlBasedView,由于后缀给出.jsp,则会自动采用JstlView resolver.setViewClass(JstlView.class); // 4)view实例和具体的URL相关,可能就是构造函数的参数,下面给出jsp URL组成:/WEB-INF/jsp/view/xxxx.jsp。通过这些设置,resolver可以根据view的名字生成JstlView实例 resolver.setPrefix("/WEB-INF/jsp/view/"); resolver.setSuffix(".jsp"); return resolver; }}
如果采用xml配置,可以写为:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/view"/> <property name="suffix" value=".jsp"/></bean>
(2) jsp文件
下面是/WEB-INF/jsp/view/home/dashboard.jsp文件
<%--@elvariable id="text" type="java.lang.String"--%><%--@elvariable id="date" type="java.time.Instant"--%><!DOCTYPE html><html><head> <title>Dashboard</title></head><body> Text : ${text}<br/> Date : ${date}</body></html>
(3) Controller返回String作为view的名字,同时提供model
这里我们通过log4j2对方法的输入和输出进行跟踪。
@RequestMapping(value="/dashboard", method = RequestMethod.GET)public String dashboard(Map<String,Object> model){ logger.entry(model); model.put("text", "This is a model attribute."); model.put("date",Instant.now()); return logger.traceExit("home/dashboard");}
model的设置在之前已经学习,方法返回为"home/dashboard",通过在ServletContextConfiguration配置的ViewResolver实例(bean)获得了具体的jtslView,我们看看log,可以看到JstlView将Model中数据放入request中,这样可以在jsp中通过EL来获取,然后将请求forward到/WEB-INF/jsp/view/home/dashboard.jsp中。
17:23:13.063 [TRACE] HomeController:36 dashboard() - Enter params({})17:23:13.064 [TRACE] HomeController:39 dashboard() - Exit with(home/dashboard)17:23:13.065 [DEBUG] (Spring) DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name 'home/dashboard'; URL [/WEB-INF/jsp/view/home/dashboard.jsp]] in DispatcherServlet with name 'springDispatcher'17:23:13.065 [DEBUG] (Spring) JstlView - Added model object 'text' of type [java.lang.String] to request in view with name 'home/dashboard'17:23:13.065 [DEBUG] (Spring) JstlView - Added model object 'date' of type [java.time.Instant] to request in view with name 'home/dashboard'17:23:13.065 [DEBUG] (Spring) JstlView - Added model object 'org.springframework.validation.BindingResult.date' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'home/dashboard'17:23:13.066 [DEBUG] (Spring) JstlView - Forwarding to resource [/WEB-INF/jsp/view/home/dashboard.jsp] in InternalResourceView 'home/dashboard'17:23:13.066 [DEBUG] (Spring) DispatcherServlet - Successfully completed request
根据请求URL获得view
RequestToViewNameTranslator
如果controller返回的是一个model,或者一个model的属性,见下面例子,则根据请求的URL来获得view。同样的,我们需要一个resolver来完成将请求转换为view名字,然后通过之前的ViewResovler来从view名字获得view实例。我们在servletContext配置中加上:
@Configuration@ComponentScan( basePackages = "cn.wei.flowingflying.chapter13.site", useDefaultFilters = false, includeFilters = @ComponentScan.Filter(Controller.class))public class ServletContextConfiguration { @Bean public ViewResolver viewResolver(){ ... ... } // 1) 和ViewResovler一样,RequestToViewNameTranslator作为@Bean。 @Bean public RequestToViewNameTranslator viewNameTranslator(){ // 2) 使用DefaultRequestToViewNameTranslator,将请求的url去掉web app context URL,以及最后的文件后缀,例如http://localhost:8080/chapter13/foo,将给出view name=foo,例如http://localhost:8080/chapter13/home/foo.html,将给出home/foo return new DefaultRequestToViewNameTranslator(); }}
使用例子
下面是contrller的方法:// 根据请求,则view的名字为user/home,则对应的jsp文件名字为/WEB-INF/jsp/view/user/home.jsp,相关jsp文件,从略//@ModelAttribute 相当于model.put("currentUser",user),如果不指明,则model的属性名字通类名(但首字母小写),即user。@RequestMapping(value = "/user/home", method = RequestMethod.GET)@ModelAttribute("currentUser") public User userHome(){ User user = new User(); user.setUserId(1234987234L); user.setUsername("adam"); user.setName("Adam Johnson"); return logger.traceExit(user);}
返回Response entity,根据内容协商获得view
内容协商
对于request entity,Spring根据Content-Type选择合适的转换器,对于Resposne entity,Spring根据以下规则判断返回的格式,然后根据格式类型决定使用哪个转换器,例如转换为xml,转换为json。
下面规则由先后顺序
- 文件的扩展名字,例如http://localhost:8080/chapter13/user/12.xml,则说明格式为xml,
- 根据请求的参数,缺省为format,也可以配置为其他,例如http://localhost:8080/chapter13/user/12?format=json,格式为json
- 根据请求中Accept消息头来决定,下图是一个HTTP请求头
本例将提供xml和json的转换。需要在pom.xml中含有相关的jar包:
<!-- 下面是json解析相关的 --><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> <scope>compile</scope></dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> <scope>compile</scope></dependency><dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>${jackson.version}</version> <scope>compile</scope> </dependency><!-- 下面是xml解析相关的 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.framework.version}</version> <scope>compile</scope></dependency>
配置转换器
我们需要在servletContext中配置相关的转换器,转换器有两个方向,将输入转换到java对象,已经将输出按格式转换,是双向的,相关代码如下:
// 1)添加消息内容的转换器,需要扩展WebMvcConfigurerAdapter,并在此配置允许@Configuration@EnableWebMvc @ComponentScan( basePackages = "cn.wei.flowingflying.chapter13.site", useDefaultFilters = false, includeFilters = @ComponentScan.Filter(Controller.class))public class ServletContextConfiguration extends WebMvcConfigurerAdapter { // 2)提供具体用于实现转换的实例,采用自动注入方式。得到实例的方法可以配置在servletContext中,即本类中,也可以考虑到可其他上下文使用,设置在root context中。例子采用后者 // 2.1) 注入用于xml封装和解析的实例 @Inject Marshaller marshaller; @Inject Unmarshaller unmarshaller; // 2.2)注入用于jason解析的实例 @Inject ObjectMapper objectMapper; // 3)重新设置消息内容的转换器,需要加上@EnableWebMvc,这个override才能有效。例子中使用configureMessageConverters(),将对转换器完全进行重新设置,我们也可以使用extendMessageConverters()添加我们的转换器,这个在后面讨论 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //【注意】添加顺序是重要的的,因为可能同时适配若干个转换器。 // 3.1)下面几个是常规的转换器 converters.add(new ByteArrayHttpMessageConverter()); converters.add(new StringHttpMessageConverter()); converters.add(new FormHttpMessageConverter()); converters.add(new SourceHttpMessageConverter<>()); // 3.2)添加xml转换器,设置支持application/xml和text/xml MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(); xmlConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "xml"), new MediaType("text", "xml"))); xmlConverter.setMarshaller(this.marshaller); xmlConverter.setUnmarshaller(this.unmarshaller); converters.add(xmlConverter); // 3.3)添加json转换器,设置支持application/json和text/json MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json"), new MediaType("text","json"))); jsonConverter.setObjectMapper(this.objectMapper); converters.add(jsonConverter); logger.debug(converters); //看看有什么log } // 4)定制化设置内容协商配置,如果不定制,均采用缺省值 @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { //表示允许路径扩展后缀(缺省true),如.xml,并允许通过Java Activation Framework来将扩展解析到特定MediaType中(缺省false) configurer.favorPathExtension(true).useJaf(true) .favorParameter(true).parameterName("mediaType") //允许参数设置format(缺省flase),不采用缺省值,设置为mediaType .ignoreAcceptHeader(false) //允许通过Accept头来协商(缺省flase) .defaultContentType(MediaType.APPLICATION_XML) //设置缺省为application/xml .mediaType("xml", MediaType.APPLICATION_XML) //对于路径扩展或者参数为xml,对应到application/xml .mediaType("json", MediaType.APPLICATION_JSON); //对于路径扩展或者参数为json,对应到application/json } ... ...}
log为:
[org.springframework.http.converter.ByteArrayHttpMessageConverter@64ad8e, org.springframework.http.converter.StringHttpMessageConverter@b0ad74, org.springframework.http.converter.FormHttpMessageConverter@118165f, org.springframework.http.converter.xml.SourceHttpMessageConverter@1a0bdff, org.springframework.http.converter.xml.MarshallingHttpMessageConverter@a97f18, org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@1b3bef4]
本例将xml和json解析器的实例获取防止root上下文,相关代码如下:
@Configurable@ComponentScan( basePackages = "cn.wei.flowingflying.chapter13.site", excludeFilters = @ComponentScan.Filter(Controller.class))public class RootContextConfiguration { // 为自动注入提供xml解析和封装的实例,Jaxb2Marshaller同时Marshaller和UnMarshaller的继承。再次说明,这个可以根据需要加载root上下文,或者servlet上下文中 @Bean //所有的实例是Bean,因此需要加上@Bean public Jaxb2Marshaller jaxb2Marshaller(){ Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); //which package to scan for XML-annotated entities marshaller.setPackagesToScan(new String[] { "cn.wei.flowingflying.chapter13.site" }); return marshaller; } // 为自动注入提供json解析和封装的实例 @Bean public ObjectMapper objectMapper(){ ObjectMapper mapper = new ObjectMapper(); //寻找和注册所有的扩展模块,例如JSR 310(Java8 Date and Time)支持模块 mapper.findAndRegisterModules(); //date序列化时不作为timestamp的long,而是作为ISO 8601的string mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //date反序列化是没有时区时,认为是UTC,而不适配到当前时区 mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false); return mapper; }}
我们注意到xml是自动扫描xml标记的,在User类,我们提供相关的标记:
@XmlRootElementpublic class User { private long userId; private String username; private String name; ... ...}
例子
@RequestMapping(value="/user/{userId}", method = RequestMethod.GET)@ResponseBody // 响应消息体,也即Response entity,将触发内容协商public User getUser(@PathVariable("userId") long userId){ User user = new User(); user.setUserId(userId); user.setUsername("john"); user.setName("John Smith"); return user;}
我们可以测试一下http://localhost:8080/chapter13/user/12将给出xml格式,因为缺省配置为xml,http://localhost:8080/chapter13/user/12.json 以及http://localhost:8080/chapter13/user/12&mediaType=json ,都将给出json格式。
强大的Spring生态
我们修改一下ServletContextConfig文件,注释掉configureMessageConverters(),改用extendMessageConverters(),同时增加log,看看已经具有哪些转换器public class ServletContextConfiguration2 extends WebMvcConfigurerAdapter { private static final Logger logger = LogManager.getLogger(); public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { logger.info("Converters : " + converters); //这里加入我们需要新增的转换器。 } ... ...}
Log如下:
15:28:31.166 [DEBUG] - Converters : [org.springframework.http.converter.ByteArrayHttpMessageConverter@1d91848, org.springframework.http.converter.StringHttpMessageConverter@3678ca, org.springframework.http.converter.ResourceHttpMessageConverter@461d29, org.springframework.http.converter.xml.SourceHttpMessageConverter@c05b0a, org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@1020682, org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@ffdbc0, org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@5425a3]
我们注意到已经含有Jaxb2RootElementHttpMessageConverter和MappingJackson2HttpMessageConverter,实际上只要有相关的jar包在lib,就能够提供转换器,包括xml,包括json。因此,我们无需添加转换器,无需注入,只需配置内容协商,也能实现输出xml或者json格式。这就是spring的强大,有很多第三方jar包围着它转,生态够大。
我们也同时注意到,xml使用的转换器是Jaxb2RootElementHttpMessageConverter,而不是之前配置的MarshallingHttpMessageConverter,我们可以不导入spring-oxm,同样也能支持xml转换。
- Java for Web学习笔记(六三):Controller替代Servlet(5)Model和View
- Java for Web学习笔记(六五):Controller替代Servlet(7)上传和下载(自定义View)
- Java for Web学习笔记(六二):Controller替代Servlet(4)方法返回值
- Java for Web学习笔记(五九):Controller替代Servlet(1)请求匹配
- Java for Web学习笔记(六十):Controller替代Servlet(2)方法中的参数
- Java for Web学习笔记(六一):Controller替代Servlet(3)body映射到参数
- Java for Web学习笔记(六四):Controller替代Servlet(6)Spring Form Tag
- Java for Web学习笔记(八三):RESTful(3)Controller
- Java for Web学习笔记(六):Servlet(4)HttpServletResponse
- Qt Model/View 学习笔记(三)
- Java for Web学习笔记(六六):Service和Repository(1)抽象分层
- Java for Web学习笔记(三六):自定义tag(4)自定义Tag文件
- Java for Web学习笔记(三):Servlet(1)Maven
- 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)
- 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)
- 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)
- 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)
- 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)
- [Unity&blender]安装问题AL lib: <EE> UpdateDeviceParams: Failed to set 44100hz , got 48000hz instead
- bzoj2081 Beads
- redis和memcacahe、mongoDB的区别
- Java计算两个日期间的年,月,日之差
- POJ-1456 Supermarket 贪心
- Java for Web学习笔记(六三):Controller替代Servlet(5)Model和View
- 持久化redis有几种方式?
- Node.js 笔记(一)nodejs npm express 的安装
- 【PYTHON】 Missing parentheses in call to 'print'
- jquery 邮件较验,手机较验
- node学习笔记
- 《数据结构》堆排序
- 关于python2和python3共存下装pygame
- 安装SQL Server 2008 报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本”解决方法