Spring JSP

Spring 提供的 JSP 标签库

1 表单绑定标签库

  • 可以绑定一个模型的对象到注册表单,表单中的某些标签可以通过这个模型对象的属性进行填充。
  • 提供一个展现错误的标签

使用声明

1
2
<%-- tags/form, 并且声明前缀为 sf,即 Spring Form--%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>

具体的 JSP 表单

1
2
3
4
5
<sf:form method="POST" commandName="user">
First Name: <sf:input path="firstName" /><br/>
......
Password: <sf:password path="password" /><br/>
</sf:form>

sf:form 标签会渲染一个 HTML <form> 标签, 通过 commandName 绑定模型中的一个对象,key 为 user, 这个对象的属性会被其他标签引用

<sf:input> 标签会渲染一个 HTML <input> 标签 ,type 为 text,path 的值,会被渲染成该 <input> 标签的 value 属性的值

当然也可以在 <sf:input> 标签中指定 type 属性

1
Email: <sf:input path="email" type="email" /><br/>

控制器方法 — 注册 GET 方法, 模型中必须添加一个 User 对象

1
2
3
4
5
@RequestMapping(value = "/register", method = RequestMethod.GET)
public String showRegistration(Model model) {
model.addAttribute(new User());
return "registerFormSpringForm";
}

在校验失败后,表单中会预先填充之前输入的值。是在 POST 方法,是直接返回的注册视图实现的, GET 方法中添加 User 对象,只是为了初始展示注册试图 JSP不报错

展现错误

如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。

1
2
3
<%-- path 属性指定要显示模型对象属性 firstName 的错误信息 --%>
<%-- cssClass 属性设置了 css 属性,将错误显示更加突出 --%>
<sf:errors path="firstName" cssClass="error" />

如果有验证错误的话, 会渲染成一个个HTML <span>标签,显示错误信息。

为每个输入域设置cssErrorClass属性,着重显示需要修正的输入域,

1
2
<sf:label path="firstName" cssErrorClass="labelerror">FirstName:</sf:label>
<sf:input path="firstName" cssErrorClass="inputerror"/>

现在已经为文本标记和输入域设置了样式,在有校验错误的时候, HTML 最终的 <label> <input> 标签的 class 属性会被设置成相应的值,展示 CSS 样式

2 Spring 通用 JSP 标签库

使用声明

1
2
<%-- tags, 并且声明前缀为 s,即 Spring--%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>

3 JSTL

JSTL 依赖

https://www.cnblogs.com/kouzikaile/p/7094571.html

https://blog.csdn.net/qq_22860341/article/details/79207665

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     <!-- jstl -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- jstl-api -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!-- jstl-impl -->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
</dependency>

4 Apache Tiles

1
2
3
4
5
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-extras</artifactId>
<version>3.0.7</version>
</dependency>

==使用Apache Tiles 需要添加以上依赖及JSTL依赖==

如果提示包找不到,可能需要将相应的依赖从右边一刀左边的的lib目录下(Put into Output Root)

观察制品输出目录 Output directory 是否存在相应的包

Spring MVC Restful

Spring 提供的将资源的 Java 表述形式转换为发送给客户端的形式

  • 内容协商 - Content Negotiation: 选择一个视图,将模型渲染为呈现给客户端的表述形式
  • 消息转换器 - Message Conversion: 通过一个消息转换器将控制器所返回的的的对象转换为呈现给客户端的表述形式

内容协商 - Content Negotiation

内容协商的步骤:

  1. 确定请求的媒体类型
    • 先根据请求 URI 的文件扩展名,如 .json 确定请求的类型是 Json
    • 文件扩展名不能得到任何媒体类型的话,考虑请求 Accept 头部信息。Accept 头部接受的类型就表明了客户端的想要的 MIME 类型
    • 没有头部信息,默认接受 / 作为默认的内容类型,也就是可以接受各种形式的资源表述
  2. 找到适合请求媒体类型的最佳视图

为此,提供了一个 ContentNegotiatingViewResolver 内容协商视图解析器

ContentNegotiatingViewResolver要求其他的视图解析器将逻辑视图名解析为视图。解析得到的每个视图都会放到一个列表中。这个列表装配完成后,ContentNegotiatingViewResolver会循环客户端请求的所有媒体类型,在候选的视图中查找能够产生对应内容类型的视图。第一个匹配的视图会用来渲染模型。

假设控制器方法返回了一个 spittles 逻辑视图名称, 程序中定义了两个试图解析器,对应的视图渲染的表述形式不一样,如 HTML 和 Json, 如果都存在对应的视图,这时,会根据请求要求的类型,找到第一个匹配其类型的视图,渲染模型数据。

消息转换器 - Message Conversion

@ResponseBody注解会告知Spring,我们要将返回的对象作为资源发送给客户端,并将其转换为客户端可接受的表述形式。DispatcherServlet将会考虑到请求中Accept头部信息,并查找能够为客户端提供所需表述形式的消息转换器。

使用 MappingJackson2XmlHttpMessageConverter, 实现 Json 与 Java 对象转换需要引入依赖:

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>

使用 MappingJackson2XmlHttpMessageConverter, 实现 XML 与 Java 对象转换需要引入依赖:

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.8</version>
</dependency>

@RestController 注解

表明该控制器的所有处理方法都应用了消息转换功能,相当于为每个方法添加 @ResponseBody 注解,能将方法返回的 Java 对象转换为合适的资源表述,作为响应返回给客户端。

FactoryBean

Restful Client

编写 Restful Client 的代码也同样存在样板式代码,也就是说有很多相同的代码要重复写,如

  • 创建 HttpClient 客户端
  • 创建请求,填充头部信息,请求体
  • 执行请求,对返回的响应进行解析处理

RestTemplate 解救人民于水火,提供了各种 Http Method 的请求,并且简化了请求的代码,只需要初始化一个 Template, 就可以调用各种 Http Method 对应的方法执行请求, RestTemplate 可以通过注入的方式来使用。

示例代码:

Get 请求 (template.getForObject)

1
2
3
4
RestTemplate template = new RestTemplate();
String parameterUri = "http://localhost:8080/rest/blog/responseEntity/{name}";
MessageEntity messageEntity = template.getForObject(parameterUri, MessageEntity.class, "Guoph");
System.out.println(messageEntity);

指定请求头部的 Post 请求 (template.exchange)

1
2
3
4
5
6
7
8
9
10
String parameterUri = "http://localhost:8080/rest/blog/responseEntity/{name}";

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<Object> httpEntity = new HttpEntity<>(headers);

ResponseEntity<MessageEntity> responseEntity = template.exchange(parameterUri, HttpMethod.GET, httpEntity,
MessageEntity.class, "Guoph");

System.out.println(responseEntity);

Spring JDBC

Spring 定义的数据访问异常体系相比于 JDBC 的 SQLException 要丰富的多,并且没有与特定的持久化方式相关联。 持久化机制与数据访问层隔离开来。

DataAccessException 非检查型异常,不强制进行捕获,因为 Spring 认为触发异常的很多问题是不能在 catch 代码块中进行捕获的,如连接信息有误、语法错误等异常(就算捕获了,也不能做任何处理)。是否要捕获异常取决于开发人员。

模板方法模式

构造器 @Inject 注解 需要加入依赖:

1
2
3
4
5
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

Spring JDBC

Spring JDBC 框架承担了必要的资源管理异常处理工作,简化了 JDBC 代码, 只需要关注必要的数据库读写代码即可。

Spring 将数据访问的样板代码抽象到模板类中。

Spring 为 JDBC 提供了三个模板类:

  • JdbcTemplate:最基本的 Spring JDBC 模板,支持简单的 JDBC 数据库访问功能以及基于索引参数的查询(赋值的顺序与 SQL 语句占位符的顺序要一致) Spring 3.1 开始,支持 Java 5 的自动装箱、泛型以及可变参数列表等特性来简化 JDBC 模板的使用
  • NamedParameterJdbcTemplate:可以将值以命名参数的形式绑定到 SQL 中(相对于索引参数)

Spring 与 Hibernate 的集成

Spring 提供了与 JDBC 类似的模板类 HibernateTemplate,能够保证每个事务使用同一个 Session, 弊端在于 Repository 的实现与 Spring 耦合。

可以直接使用 Hibernate 的上下文 Session Contextual Session, 其实 Hibernate 等持久化框架已经达到了和模板类类似的功能,如获取链接,创建语句,管理事务,关闭资源等,因此直接使用也是比较方便的。

需要声明一个 org.springframework.orm.hibernate4.LocalSessionFactoryBean bean(对象关系映射既可以通过注解,也可以通过配置文件声明), 继承自 Spring 的 FactoryBean, 会产生 Hibernate 的 SessionFactory

但是不使用 Spring 提供的模板类,问题就是 Repository 实现类会抛出 Hibernate 相关的异常,而不是 Spring 定义的非检查型异常。解决方案是:定义一个 PersistenceExceptionTranslationPostProcessor bean。

PersistenceExceptionTranslationPostProcessor是一个bean 后置处理器(bean post-processor),它会在所有拥有@Repository注解的类上添加一个通知器(advisor),这样就会捕获任何平台相关的异常并以Spring非检查型数据访问异常的形式重新抛出。

@Repository 注解

@Repository注解,这会为我们做两件事情。首先,@Repository是Spring的另一种构造性注解,它能够像其他注解一样被Spring的组件扫描所扫描到, 表明在组件扫描的时候会自动创建。这样就不必明确声明HibernateSpitterRepository bean了,只要这个Repository类在组件扫描所涵盖的包中即可。

除了帮助减少显式配置以外,@Repository还有另外一个用处。让我们回想一下模板类,它有一项任务就是捕获平台相关的异常,然后使用Spring统一非检查型异常的形式重新抛出。

Spring MVC

Spring MVC

设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象。

使用Java类配置Spring MVC

Servlet 3.0 查找 javax.servlet.ServletContainerInitializer 的实现类来配置 Servlet 容器;Spring 提供了该接口的实现:SpringServletContainerInitializer , 并且又会查找实现WebApplicationInitializer 的类来进行配置,并引入了一个AbstractAnnotationConfigDispatcherServletInitializer的基础实现,所以在Spring中通过Java代码进行配置的时候,需要扩展该基础实现类。

这种配置DispatcherServlet的方式只能部署到支持Servlet 3.0的服务器中才能工作。

UML 图示:

SpringServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Delegate the ServletContext to any WebApplicationInitializer implementations present on the application classpath.
Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring's WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method.

Assuming that one or more WebApplicationInitializer types are detected, they will be instantiated (and sorted if the @@Order annotation is present or the Ordered interface has been implemented). Then the WebApplicationInitializer.onStartup(ServletContext) method will be invoked on each instance, delegating the ServletContext such that each instance may register and configure servlets such as Spring's DispatcherServlet, listeners such as Spring's ContextLoaderListener, or any other Servlet API componentry such as filters.

/* Servlet 3.0+ 会自动在应用的 classpath 中寻找 WebApplicationInitializer 的实现类,并对每一个实现类进行实例化,将 ServletContext 委托给他们,对每一个实例调用 WebApplicationInitializer.onStartup(ServletContext), 加载和注册所有的 ServletListenerFilter */

If no WebApplicationInitializer implementations are found on the classpath, this method is effectively a no-op. An INFO-level log message will be issued notifying the user that the ServletContainerInitializer has indeed been invoked but that no WebApplicationInitializer implementations were found.


// org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, @NotNull ServletContext servletContext) throws ServletException {

}
}

Root Application Context
As such, it typically contains middle-tier services, data sources, etc.

Servlet Application Context
As such, it typically contains controllers, view resolvers, locale resolvers, and other web-related beans.

Root Application Context 用来初始化 ContextLoaderListener 对象,并将该对象注册到 Servlet 容器中

Servlet Application Context 用来初始化 DispatcherServlet 对象,并将该对象注册到 Servlet 容器中,随后添加 Filters, 添加 Mapping 映射, 当然用户还可以通过 customizeRegistration(ServletRegistration.Dynamic registration) 来进行自定义之后的操作,如配置 Multipart 处理的参数

DispatcherServlet

1
2
3
4
5
6
7
8
// DispatcherServlet api 与 Web.xml中 对应的初始化参数
setContextClass(Class) / 'contextClass'
setContextConfigLocation(String) / 'contextConfigLocation'
setContextAttribute(String) / 'contextAttribute'
setNamespace(String) / 'namespace'

// indicates which ApplicationContextInitializer classes should be used to further configure the internal application context prior to refresh().
setContextInitializerClasses(String) / 'contextInitializerClasses'

编写控制器方法

  • 处理查询参数请求
1
2
3
4
5
6
7
8
/**
* 对类似 /messageList?count=10 的url进行处理
*/
@RequestMapping(value = "/messageList", method = RequestMethod.GET)
public String message(@RequestParam(value = "count", defaultValue = "20") int count, Model model) {
model.addAttribute("messageList", repository.getMessageList(count));
return "message";
}
  • 处理路径变量
1
2
3
4
5
6
7
8
/**
* 对类似 /messageList/Tom 这样的url进行处理
*/
@RequestMapping(value = "/messageList/{name}", method = RequestMethod.GET)
public String messageListByName(@PathVariable String name, Model model) {
model.addAttribute("messageList", repository.getMessageListByName(name));
return "message";
}
  • 处理表单提交,以及增加校验
1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(@Valid User user, Errors errors) {
if (errors.hasErrors()) {
errors.getAllErrors().forEach(error -> printError(error));
return "registerFormSpringForm";
}
System.out.println("--------" + user.getUsername());
repository.saveUser(user);
return "redirect:/blog/" + user.getUsername();
}

==C:\Users\guo.IntelliJIdea2018.3\system\tomcat\Unnamed_spring_web_java_config\work\Catalina\localhost\ROOT==

表单数据校验

在使用validator对表单数据进行校验时,需要

Spring对Java校验API(Java Validation API,又称JSR-303)的支持。从Spring 3.0开始,在Spring MVC中提供了对Java校验API的支持。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可,比如Hibernate Validator。

java的 JSR 303: Bean Validation 只是一个规范,并没有具体的实现,hibernate-validator 项目是对其的一个实现,因此,具体使用要引入相应的JAR包:validation-api(定义校验注解) hibernate-validator(具体实现)

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>

==并且如果之前没有添加,后来添加了,要生效,必须重新打包部署,即重新生产 Artifacts, 并部署到tomcat上,否则就不会起作用!!==

使用spring-test进行测试

在用spring-test进行测试的时候,需要和spring其他组件的jar包版本一致,否则就可能会出现NoSuchMethodError

1
java.lang.NoSuchMethodError: org.springframework.util.StreamUtils.emptyInput()Ljava/io/InputStream;

Apache Common Lang包来实现equals()hashCode()方法。

对“/spittles/12345”发起GET请求要优于对“/spittles/show?spittle_id=12345”发起请求。前者能够识别出要查询的资源,而后者描述的是带有参数的一个操作——本质上是通过HTTP发起的RPC。

Spring 源码

1
public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext

ConfigurableWebApplicationContext 提供了 configLocation 的属性,为 web application context 设置 config 文件的路径

public class ContextLoaderListener extends ContextLoader implements ServletContextListener 中,在监听到 ServletContext 初始化时,会初始化一个 WebApplication 实例(实现了ConfigurableWebApplicationContext接口),会从 web.xml 中 配置的 Servlet Context 参数中寻找 contextConfigLocation, 填充 configLocation .