JavaWeb高级编程(下篇)

Java标准标签库

JSP标签语法中包含一些简写可以帮助轻松编写JSP。这些简写中第一个就是taglib指令。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

指令是XML文档中引用XML命名空间的一种方式,是XMLNS技术的替代品。
指令taglib中的prefix特性代表了在JSP页面中引用标签库时使用的命名空间。
特性uri标志着TLD中为该标签库定义的URI。
所有的JSP标签都遵守相同的基本语法:

<prefix:tagname[ attribute=value[ attribute=value[ ...]]]
</prefix:tagname>

使用核心标签库

使用命名空间:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

使用方法:

<c:out value="${someVariable}" default="value not specified" excapeXml="false" />

其中excapeXml设置为假禁止对保留的XML字符进行转义。default属性指定当value特性为null时使用的默认值。

标签<c:url>
标签<c:url>可以正确地对URL编码,并且在需要添加会话ID的时候重写它们,它还可以在JSP中输出URL。

<c:url value="http://www.example.com/test.jsp">
	<c:param name="story" value="${storyId}" />
</c:url>

<c:url>最有用的地方就在于对相对URL的编码。

标签<c:if>
标签<c:if>是一个条件标签,用于控制是否渲染特定内容。

<c:if test="${something == somethingElse}" var="itWasTrue">
	execute only if test is true
</c:if>

<c:if test="${itWasTrue}" />
	do something
</c:if>

特性test指定了一个条件,只有该条件为真时,<c:if>标签中内嵌的内容才会被执行。var特性将把测试结果保存为变量。

标签<c:choose>,<c:when>,<c:otherwise>
上述三个标签提供了复杂的if/else-if/else逻辑。

<c:choose>
	<c:when test="${something}">
    	"if"
    </c:when>
    <c:when test="{somethingElse}">
    	"else if"
    </c:when>
    ...
    <c:otherwise>
    	"else"
    </c:otherwise>
</c:choose>

食用方法如上,类比 if-elseif-elseif-...-else 。

标签<c:forEach>
用于迭代并重复它的嵌套主题内容固定次数,或者遍历某些集合或数组。

<c:forEach var="i" begin="0" end="100" step="3">
	Line ${i} <br />
</c:forEach>

<c:forEach items="${users}" var="user">
	${user.name} <br />
</c:forEach>

特性items中的表达式必须是一些集合、Map、Iterator、Enumeration、对象数组或者原生数组。

标签<c:redirect>
该标签将把用户重定向至另一个URL,由name属性决定。

<c:redirect url="http://www.example.com/" />  
<c:redirect url="/tickets" >
	<c:param name="action" value="view" />
</c:redirect>

标签<c:import>
该标签用于获取特性的URL资源内容。

<c:import url="/copyright.jsp" />

<c:import url="ad.jsp" context="/store" var="advertisement" scope="request">
	<c:param name="category" value="${forumCategory}" />
</c:import>

<c:import url="http://www.example.com/embeddedPlayer.do?video=f8ETe45646"
	varReader="player" charEncoding="UTF-8">
	<wrox:writeViderPlugin reader="${player}" />
</c:import>

第一个样例将应用程序本地的copyright.jsp中的内容内嵌在页面中。第二个样例将ad.jsp?category=${forumCategory}的内容保存到请求作用域的字符串变量advertisement中,并对category查询参数进行正确的编码。第三个样例获取了一些外部资源,并将它导出为名为player的Reader对象。

标签<c:set><c:remove>
<c:set>标签可以设置新的或现有的作用域变量,还可以使用它对应的标签<c:remove>从作用域中删除变量。

<c:set var="myVariable" value="hello" />
${myVariable}
<c:remove var="myVariable" scope="page" />

编写自定义标签和函数库

为了使用标签文件中定义的标签,可以使用taglib中的tagdir特性帮助你完成:

<%@ taglib prefix="myTags" tagdir="/WEB-INF/tags" %>

tagdir目录中所有的.tag或者.tagx文件都将被绑定到myTags命名空间。

为编写自定义的标签和函数库,必须了解JSP标签库XSD和使用它编写标签的方式。
关于JSP标签库XSD有一件重要的事情需要注意:模式使用了严格的元素顺序,这意味着所有使用的元素必须严格按照特定的顺序出现,否则该TLD文件将是无效的。

<description>JSTL 1.2 core library</description>  
<display-name>JSTL core</display-name>
<tlib-version>1.2</tlib-version>
<short-name>c</short-name>
<uri>http://java.sun.com/jsp/jstl/core</uri>

关于以上代码:

  • 元素提供了XML工具可以显示有用的名称,但与TLD的实际内容无关并且是可选的。如果需要,可以添加许多,这样可以为不同的语言指定不同的显示名称和描述。
  • 元素为可选元素,这里没有出现,它必须出现在之前。
  • 是必须元素。它定义了标签库的版本,其中只能使用数字和圆点。
  • 表示该标签库推荐使用,也是默认的前缀,也是必须的,不能包括空白,或者以数字或下划线开头。
  • 定义了该标签库的URI。

元素是TLD的主要元素,负责定义标签库中的标签。

<tag>
	<description>
    	catches throwable
    </description>
    <name>catch</name>
    <tag-class>org.apache.taglibs.standard.tag.common.core.catchTag</tag-class>
    <body-content>JSP</body-content>
    <attribute>
    	<description>
        	name of the exported scoped variable for the exception thrown from a nested action.
        </description>
        <name>var</name>
        <required>false</required>
        <rtexprvalue>false</rtexprvalue>
    </attribute>
</tag>

一样,它可以由0个或多个嵌套的元素。在这些元素之后需要一个元素,它将指定JSP标签的名称在本例中,完整的标签名称为<c:catch>,c是标签库,catch是标签的名称。一个标签明显只可以有一个名称。接下来是元素,它表示负责执行标签的标签处理器类。
接下来是指定了标签中允许嵌套的内容类型。
之后是0个或多个元素,该元素提供了使用该标签定义的结果变量的相关信息。
在标签的元素之后,可以定义0个或多个,它将为标签定义可用的特性。
标签之后是,该属性不常见,只可以指定该布尔元素一次或者忽略它。默认值为假,用于表示是否允许通过元素指定特性值。
之后是可选的元素,为标签的使用提供样例。

标签文件实际上就是一种JSP,只不过使用的语义稍有不同。
JAR库中的标签文件必须定义在TLD中,另外,如果希望将一个或多个标签文件分配到相同的命名空间,那么需要在TLD中定义这些标签,即使它们不再JAR文件中。
在TLD的所有元素之后,可以添加0个或多个元素,定义属于库的标签文件。

<tag-file>
	<description>this tag outputs bar.</description>  
    <name>foo</name>
    <path>/WEB-INF/tags/foo.tag</path>
</tag-file>

元素中可选'元素。
指定前缀之后的标签名;
指定实现自定义标签的.tag文件所在的路径。
在TLD中定义标签文件之后,就可以使用元素定义0个或多个JSP函数。

<function>
	<description>
    ...
    <description>
    <name>...</name>
    <function-class>
    url
    </function-class>
    <function-signature>
    ...
    </function-signature>
    <example>
    ...
    </example>
</function>

标注一个标准Java类的完全限定名称,函数签明实际上是此类的静态方法签名。任何公共类上的所有公共静态方法都可以通过这种方式成为JSP函数。

在TLD中所有的标签之后,可以使用元素定义0个或多个标签库扩展。
相对于page指令,标签文件有一个tag指令。该指令将替换JSP page指令的必要功能,并且替换了TLD文件的元素中的许多配置。
创建自定义标签的最简单的方式就是:编写一个标签使用含有tagdir特性的taglib指令。

<%@ taglib prefix="template" tagdir="/WEB-INF/tags/temple" %>

使用过滤器改进应用程序

过滤器是可以拦截访问资源的请求、资源的响应或者同时拦截两者的应用组件。
过滤器可以检测和修改请求和响应,它们甚至可以拒绝、重定向或转发请求。
过滤器在初始化时将调用init方法,它可以访问过滤器的配置、初始化参数和SevletContext,正如Servlet的init方法一样。当请求进入过滤器中时,doFilter方法将会被调用,它提供了对ServletRequest、ServletResponse和FilterChain对象的访问。在doFilter之中,可以拒绝请求或者调用FilterChain对象的doFilter方法,可以修改请求和响应,并且可以封装请求和响应对象。
尽管只有一个Servlet可以处理请求,但可以使用许多过滤器拦截请求。
如同Servlet一样,过滤器可以被映射到URL模式,这会决定哪个过滤器将拦截某个请求。在任何匹配某个过滤器的URL模式的请求在被匹配的Servlet处理之前将首先进入该过滤器。
在声明和映射过滤器拦截请求之前,必须如同Servlet一样声明和映射它们。传统的方式是在部署描述符中使用元素。必须至少包含一个名字和类名,它还可以包含描述、显示名称、图标以及一个或多个初始化参数。

<filter>
	<filter-name>myFilter</filter-name>
    <filter-class>com.wrox.MyFilter</filter-class>
</filter>

与Servlet不同的是,过滤器不可以在第一个请求到达时加载。过滤器的init方法总是在应用程序启动时调用。
在声明了过滤器之后,可以将它映射到任意数目的URL或Servlet名称。

<filter-mapping>
	<filter-name>myFilter</filter-name>
    <url-pattern>/foo</url-pattern>
    <url-pattern>/bar/*</url-pattern>
    <servlet-name>myServlet</servlet-name>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ASYNC</dispatcher>
<filter-mapping>

过滤器将会响应所有相对于应用程序的URL/foo和/bar/*的请求,以及任何最终由Servlet/myServlet处理的请求。这里的两个元素意味着它可以响应普通的请求和由AsyncContext派发的请求。有效的类型有:REQUEST、FORWARD、INCLUDE、ERROR和ASYNC。

如同Servlet一样,可以注解声明和映射过滤器。

@WebFilter(
	filterName = "myFilter",
    urlPatterns = { "/foo","/bar/*" },
    servletNames = { "myServlet" },
    dispatcher = { DispatcherType.REQUEST, DispatcherType.ASYNC }
)
public class MyFilter implements Filter{
	...
}

使用注解声明和映射过滤器的主要缺点是:不能对过滤器链上的过滤器进行排序。如果希望在不使用部署描述符的情况下控制过滤器的执行顺序,那么需要使用编程式配置。

如同Servlet、监听器和其他组件一样,可以在ServletContext中以编程的方式配置过滤器。不使用部署描述符和注解,调用ServletContext的方法注册和映射过滤器即可。因为这必须要在ServletContext结束之前完成,所有通常需要在ServletContextListener的contextInitialized方法中也实现。
过滤器顺序决定了过滤器在过滤器链中出现的位置,这反过来也决定了过滤器什么时候处理请求。
使用注解时无法对过滤器进行排序。
定义过滤器顺序是很简单的:匹配请求的过滤器将按照它们出现在部署描述符或者编程式配置中的顺序添加到过滤器链中。不同的请求将匹配不同的过滤器,但使用的过滤器顺序总是相同的。URL映射的过滤器优先级比Servlet名称映射到的过滤器高。如果两个过滤器都可以匹配某个请求,一个是URL模式而另一个是Servlet名称,那么在过滤器链中,由URL模式匹配的过滤器总是出现由Servlet名称匹配的过滤器之前。

如果使用AsyncContext直接处理响应对象,代码将在所有过滤器的范围之外执行。不过,如果使用AsyncContext的dispatch方法在内部将请求转发到某个URL,那么映射到ASYNC请求的过滤器可以拦截该内部转发请求,并应用必要的额外逻辑。

使用日志监控应用程序

"日志"不一定就是一个文件。
日志事件有着不同的类型和严重程度。
常见的日志级别:

通用名称 | 级别 | 常用语义

  • | -
    致命错误 | 没有对等的常量 | 表示一种严重的错误形式,通常这种错误会导致系统崩溃或者提前终止
    错误 | SEVER | 表示发生了严重的问题
    警告 | WARNING | 表示发生了一些可能是也可能不是问题的事件,并且可能需要进行检查
    信息 | INFO | 表示信息级别的日志,这些日志对应用程序监控和调试来说是非常有用的

介绍SpringFramework

Spring Framework是一个Java应用程序容器,它提供了许多有用的特性,例如反转控制、依赖注入、抽象数据访问、事务管理等。

Spring Framework的核心特点之一就是对两个紧密相关的观念的支持:控制反转(IoC)和依赖注入(DI)。IoC是一个软件设计模式:组装器将在运行时而不是在编译时绑定对象。当某些程序逻辑组件,例如Service A,依赖于另一个程序逻辑组件Service B时,该依赖将在应用程序运行时实现,而不是由Service A直接实例化Service B。
尽管理论上可以通过多种方式实现IoC,但DI时最常见的技术。通过使用DI,一段程序代码可以声明它依赖于另一块程序代码,然后组装器可以在运行时注入它依赖的实例。
因为Spring Framework负责处理实例化和依赖注入,所以它可以通过封装注入依赖的实例,使用其他行为对方法调用进行装饰。
Spring Framework提供了一个松耦合的消息系统,它使用的时发布-订阅模式:系统中的组件通过订阅消息,声明它对该消息感兴趣,然后这些消息的生产者将会发布该消息,而无须关系谁对消息感兴趣。使用Spring Framework时,一个由Spring管理的bean可以通过实现一个通用接口订阅特定的消息类型,其他由Spring管理的对象可以发布这些消息到Spring Framework中,然后由Spring Framework将消息发送到已订阅的bean中。
Spring Framework提供了一个模型-视图-控制器(MVC)模式框架,它可以简化创建交互式Web应用程序的过程。不用动手处理复杂的Servlet、HTTPServletRequest、HttpServletResponse以及JSP转发,Spring将处理这些任务。控制器类的每个方法都将被映射到了一个不同的请求URL、方法或请求的其他属性上。模型将以Map<String Object>的形式从控制器传递到视图。控制器返回的视图或视图名称将使Spring把模型转发到合适的JSP视图。请求和URL路径参数将被自动转换为原始或复杂的控制器方法参数。
使用Spring 的Web MVC框架时,控制器类的行为非常像使用方法级别映射的Servlet。每个方法都可以拥有一个指定特性URL、请求方法、参数存在性、头的值、内容类型和/或期望相应类型的唯一映射。当单元测试对小的代码单元进行测试时,控制器类中可以包含许多映射方法,它们将被按逻辑进行分组。返回到用户配置样例中,该控制器可以含有数十个方法,使用它们分别代表对用户配置的不同操作,但必须使用doGet和DoPost将请求路由到正确的方法。Spring Framework将处理所有的分析和路由工作。
使用Spring时,业务逻辑将被封装到一组被称为服务的业务对象中。这些服务将执行所有用户界面公共的操作。
Spring Framework容器以一个或多个应用上下文的形式存在,由org.springframework.context.ApplicationContext接口表示。一个应用上下文管理一组bean、执行业务逻辑的Java对象、执行任务、持久化和获取持久化数据、响应HTTP请求等。由Spring管理的bean可以自动进行依赖注入、消息通知、定时方法执行、bean验证和执行其他关键的Spring服务。
一个Spring应用程序至少需要一个应用上下文。
在Java EE Web应用程序中,Spring将使用派发器Servlet处理Web请求,该Servlet将把进入的申请委托给合适的控制器,并按需要对请求和响应实体进行转换。
当配置告诉Spring如何运行它所包含的应用程序时,启动进程将启动Spring并将配置指令传递给它。在Java SE应用程序中,只有一种方式启动Spring;通过在应用程序的public static void main(String...)方法中以编程的方式启动。在Java EE应用程序中,有两种选择:可以使用XML创建部署描述符启动Spring,也可以在javax.servlet.ServletContainerInitializer中通过编程的方式启动Spring。
传统的Spring Framework应用程序总是使用Java EE部署描述符启动Spring。至少,这要求在配置文件中创建DispatcherServlet的一个实例,然后以cpntextConfigLocation启动参数的形式为它提供配置文件,并指示Spring在启动时加载它。

<servlet>
	<servlet-name>springDispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/servletContext.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>springDispatcher</servlet-name>
    <url-pattern>/<url-pattern>
</servlet-mapping>

该代码将为DispatcherServlet创建出单个Spring应用上下文,并指示Servlet容器在启动时初始化DispatcherServlet。在初始化的时候,DispatcherServlet将从/WEB-INF/servletContext.xml文件中加载上下文配置并启动应用上下文。
实现了ServletContainerInitializer接口的类将在应用程序开始启动时,并在所有监听器启动之前调用它们的onStartup方法。启动类将使用Spring Java配置通过纯Java的方式启动和配置Spring。

bean是由Spring Framework管理的,所以这是在配置Spring Framework时我们主要需要完成的配置。
我们需要使用 XML命名空间:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
	<mvc:annotation-driven />
    <bean name="greetingServiceImpl" class="xxx.GreetingServiceImpl" />
    <bean name="helloController" class="xxx.HelloController">
    	<property name="greetingService" ref="greetingServiceImpl" />
    </bean>
</beans>

该XML文件将告诉Spring实例化GreetingServiceImpl和HelloController,并将greetingServiceImpl bean注入到helloController bean的greetingService属性中。元素是包含了Spring配置的父元素。在其中几乎可以使用所有其他Spring配置元素--不过一般来讲,元素使用的元素都将引起bean的创建。

混合配置的核心是组件扫描和注解配置。所有标注了@org.springframework.stereotype.Component的类,都将变成Spring管理的bean,这意味着Spring将实例化它们并注入它们的依赖。
其他符合组件扫描的注解:任何标注了@Component的注解都将变成组件注解,任何标注了另一个组件注解的注解也将变成组件注解。因此,标注了@Controller、@Repository和@Service的类也将成为Spring管理的bean。
可以为任何私有、保护和公开字段或者接受一个或多个参数 的公开设置方法标注@Autowired。@Autowired声明了Spring应该在实例化之后注入的依赖,并且它也可以用于标注构造器。通常由Spring管理的bean必须有无参构造器,但对于只含有一个标注了@Autowire的构造器的类,Spring将使用该构造器并注入所有的构造器参数。
通常在bean的所有依赖都注入后,在它作为依赖被注入其他bean之前,可以在该bean上执行某种初始化操作。只需要使用org.springframework.beans.factory.InitializingBean接口就可以轻松实现。在bean的所有配置都完成之后,Spring将调用它的afterPropertiesSet方法。

使用控制器替代Servlet

@RequestMapping是Spring工具集中一个非常强大的工具,通过它可以映射请求、请求的Content-Type或者Accept头、HTTP请求头、指定请求参数或头是否存在,或者这些信息的任意组合。使用了@RequestMapping之后,在Servlet的doGet或者类似的方法中选择正确的方法时,就不再需要使用复杂的切换或者逻辑分支。请求将被自动路由到正确的控制器和方法。
@RequestMapping注解将把请求被映射到的方法缩小到特定的方法上。可以只在控制器方法中添加@RequestMapping,或者同时在控制器类和它的方法中添加。

@RequestMapping("viewProduct")
public String viewProduct(...){...}

@RequestMapping("addToCart")
public String addProductToCart(...){...}

@RequestMapping("writeReview")
public String writeReview(...){...}

在上例中,如果将DispatcherServlet映射到上下文根(/),那么这些方法相对于应用程序的URL将分别变成/viewProduct、/addToCart、/writeReview。
如果控制器中的许多URL都共享一个相同的元素,那么可以使用映射继承来减少映射中的冗余。

@RequestMapping("product")
public class ProductController
{
	@RequestMapping("view")
    public String viewProduct(...){...}
    
    @RequestMapping("addToCart")
    public String addToCart(...){...}
    
    @RequestMapping("writeReview")
    public String writeProductReview(...){...}
}

在上例中,如果DispatcherServlet映射到上下文根的话,那么方法URL将分别变成/product/view、/product/addToCart、和/product/writeReview。

URL映射的另一个重要方面是:如果请求匹配到多个不同的URL映射,那么最具体的映射胜出。

关于@RequestMapping value特性需要了解的最后一件事情是:它可以接受一组URL映射。因此,可以将多个URL映射到指定的方法上。在下例中,home方法将响应URL/,/home和/dashboard:

@RequestMapping({"/","home","dashboard"})
public String home(...){...}

控制器方法可以有任意数量的不同类型的参数。
可以使用几个参数注解表示方法参数的值应该从请求的某些属性中获取。
@RequestParam注解表示被注解的方法参数应该派生自命名请求参数。使用value特性指定请求参数的名称。

@RequestMapping("uesr")
public String user(@RequestParam("id") long userId,
					@RequestParam(value="name", request=false) String name,
                    @RequestParam(value="key",defaultValue="") String key)
{...}

@RequestHeader的工作方式与@RequestParam一致,它提供了对请求头的值的访问,它指定了一个必须的或者可选的请求头,用作相应方法的参数值。因为HTTP头也可以有多个值,所以如果出现这种请求的话,应该使用数组或集合参数类型。
Spring Framework中的URL映射不必是静态值。相反,该URL可以包含一个模板,表示URL的某个部分是不可变的,它的值将在运行时决定。下面的代码脚本演示可如何在URL映射中指定一个URL模板,并通过@PathVariable的方式将该模板变量用作方法参数的值。

@RequestMapping(value="user/(userId)", method=RequestMothod.GET)
public String user(@PathVariable("userId") long userId){...}

URL映射中可以包含多个模板变量,每个模板变量都可以有一个关联的方法参数。另外,还可以将类型为Map<String,String>的单个方法参数标注为@PathVariavble,它将包含URL中所有URI模板变量值。
Spring提供了@MatrixVariable注解,从URL中提取路径参数用作方法参数。
Spring Framework允许指定一个表单对象作为控制器方法的参数。表单对象是含有设置和读取方法的简单POJO。它们不必事先实现任何特殊的接口,也不需要使用任何特殊的注解对控制器方法参数进行标记,Spring将把它识别为一个表单对象。

通过使用@RequestBody注解,Spring将自动把一个请求实体转换为控制器方法参数。

public String update(@RequestBody Account account){...}

Spring将再继续执行两个步骤,将模型从请求中完全离开,并提供可以通过无限种方式实现的高级View接口。InternalResourceView和JstlView将分别实现传统的JSP和JSTL增强JSP视图。它们负责将模型特性转换成请求特性,并将请求转发到正确的JSP。
当控制器方法返回一个View、或者ModelAndView的实现时,Spring将直接使用该View,并且不需要额外的逻辑用于判断如何向客户端展示模型。如果控制器方法返回了一个字符串视图名称或者使用字符串视图名称构造的ModelAndView,Spring必须使用已配置的org.springframework.web.servlet.ViewResolver将视图名称解析成一个真正的视图。如果方法返回的是模型或者模型特性,Spring首先使用已配置的RequestToViewNameTranslator隐式地将请求转换成视图名称,然后使用ViewResolver解析已命名地视图。最后,当控制器方法返回的是响应实体ResponseEntity或者HttpEntity时,Spring将使用内容协商决定将实体展示到哪个视图中。

使用服务和仓库支持控制器

模型-视图-控制器模式

  • 第一步:视图发送命令到控制器
  • 第二步:控制器从模型中读取或操作数据
  • 第三步:模型将数据发送到控制器
  • 第四步:控制器将模型发送到视图

用户界面逻辑是所有只用于支持特定用户界面的逻辑。如果无论用户如何与应用程序交互,都需要某一块相同的代码逻辑,那么该逻辑就是业务逻辑。不过,如果一块代码逻辑只对特定的用户界面有用。
如同将用户界面逻辑和业务逻辑分开一样,也应该将持久逻辑与业务逻辑分隔开。

在控制器-服务-仓库中,仓库是最低的一层,它负责所有的持久化逻辑,将数据保存到数据存储中并从数据存储中读取已保存的数据。使用@Repository注解标记出仓库,表示它的语义目的。启用了组件扫描之后,@Repository类所属的Spring应用上下文将自动实例化、注入和管理这些仓库。通常,每个仓库负责一种持久化对象或实体。
仓库需要实现特定的接口。
服务是仓库之上的下一层。服务封装了应用程序业务逻辑,它将使用其他服务和仓库,但不能使用更高层应用程序的资源。服务被标记上了@Service注解,使它们可以自动实例化和依赖注入。如仓库一样,它也需要实现特定的接口。
如果要为应用程序创建一个RESTful或者SOAP Web服务,那么我们可能需要在应用程序的上下文中创建一个单独的DispatcherServer和@Configuration,并且配置也将变得不同,已反应该上下文中控制器处理请求的不同方式。
不应该在Web应用上下文中管理服务和仓库,而是应该在根应用上下文中,它是所有Web应用上下文的父亲。
在使用@ComponentScan注解时,要使用String[] basePackages特性告诉Spring扫描哪些Java包,查找可用的类。Spring将定义出这些包或子包中的所有类,并针对每个类应用资源过滤器。
对于Spring在基本包中找到的每个类,它都将应用已配置的过滤器。过滤器分为包含过滤器和派出过滤器。如果每个类触发了任意一个包含过滤器,并且未触发任何排除过滤器,那么它将变成Spring bean,这意味着它将被构造、注入、初始化,并执行任何应用在Spring管理bean上的操作。
Spring Framework定义了不同但紧密相关的概念:执行器和调度器。执行器如它的名字所示:它执行任务。调度器负责记住任务应该什么时候执行,然后按时执行。
为了在@Async方法上启用异步方法执行,我们需要在@Configuration类上注解@EnableAsync。同样地,为了在@Scheduled方法上启用计划方法执行,我们需要使用@EnableScheduling注解,你会希望在RootContextConfiguration上添加这些注解,以便在应用程序的所有bean之间共享配置。不过,@EnableAsync@EnableScheduling它们自己可以创建出默认的异步和计划配置。为了自定义该行为,我们需要实现AsyncConfigurer接口返回正确的异步执行器,并通过实现SchedulingConfigurer类将正确的执行器赋给调度器。

@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
...
public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer
{
	....
	@Bean
    public ThreadPoolTaskScheduler taskScheduler(){
    	...
    }
    @Override
    public Executor getAsyncExecutor()
    {
    	...
    }
    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar)
    {
    	...
    }
}

@EnableAsync注解中的proxyTargetClass特性将告诉Spring使用CGLIB库而不是使用Java接口代理创建含有异步或计划方法的代理类。通过这种方式,我们可以在自己的bean上创建接口未指定的异步和计划方法。如果将该特性设置为假,那么只有接口指定的方法可以通过计划或异步的方法执行。新的@Bean方法将把调度器暴露为任何其他bean都可以使用的bean。方法getAsyncExecutor将告诉Spring为异步方法执行使用相同的调度器,configureTasks方法将告诉Spring为计划方法执行使用相同的调度器。
Spring将代理所有对@Bean方法的调用,所以它们永远不会被调用多次。第一次调用@Bean方法的结果将被缓存,并在所有将来的调用中使用。这将允许配置中的多个方法使用其他的@Bean方法。
Spring Framework通过以代理的方法封装受影响的bean对@Async方法提供支持。当Spring在其他依赖它的bean中注入使用了@Async方法的bean时,它实际上注入的是代理,而不是bean自身。然后这些bean将调用代理上的方法。对于普通方法来说,代理只是将调用委托给了底层的方法。对于标注了@Async@javax.ejb.Asynchronous的方法,代理将指示执行其执行该方法,然后立即返回。这种工作方式回导致一个结果:如果bean调用它自己的一个@Async方法,该方法不会异步执行,因为this不可以被代理。因此,如果希望以异步的方式调用一个方法,那么它必须是另一个对象的方法。
创建@Scheduled方法与创建@Async方法并没有太大的区别。所有需要做的就是编写一个完成任务的方法并注解它。关于@Scheduled方法需要注意的重要一点是:它们没有参数。

Bean验证通过为字段、方法等添加注解的方式,指示如何在被标注的目标上应用特定的约束。所有保留策略是运行时并且被标注了@javax.validation.Constraint的注解都代表了一个约束注解。该API中包含了几个预定义的约束注解,我们也可以创建自己的注解并提供对应的javax.validation.ContraintValidator实现,用于处理自定义的注解。ContraintValidator负责评估特定的约束类型。
约束注解可以被添加到字段、方法和方法参数上。添加到字段上时,它表示无论何时在该类的实例调用验证方法,验证器都应该检查字段是否满足约束兼容性。添加在JavaBean访问方法上时,它只是标注底层字段的另一种可选方式而已。在接口方法上添加注解,表示约束应该被应用到方法执行之后的返回值上。在接口的一个或多个方法参数上添加注解,意味着约束应该在方法执行之前作用于方法参数之上。
Spring Framework将自动为使用Java Bean验证的、由Spring管理的bean创建代理。它将拦截对添加了注解的方法的调用并进行适当的验证,检查使用者是否提供了有效的参数或该实现的返回值是否有效。
Spring Framework在Bean Validation正式出现很早之前,就已经提供了对对象自动验证的支持org.springframework.valization.Validator接口根据注解约束制定了验证对象的工具。
在配置Spring Framework的验证支持时,需要定义一个同时实现Valization 和Spring Validator的特殊类型bean。
在大多数情况下,我们需要使用LocalValizationFactoryBean,因为它支持获取底层的Validator,并且支持使用应用程序的其他部分代码中应用国际化的相同MessageSource和资源包文件。
LocalValidatorFactoryBean将自动检测到类路径上的Bean Validation实现,无论是Hibernate Validator还是一些其他的实现,并使用它默认的javax.validation.ValidatorFactory作为支持工厂。不需要创建META-INF/validation.xml文件。不过,有时类路径上可能存在多个Bean验证提供者。在这些情况下,Spring选择使用哪个提供者是不可预测的,所以如果希望使用指定的提供者的话,应该手动设置提供者类。

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean()
{
	LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setProviderClass(HibernateValidator.class);
    return validator;
}

Spring Framework使用了bean后处理器的概念,通过它可以在容器完成启动过程之前配置、自定义和替换配置中的bean。已配置的org.springframework.beans.factory.config.Bean-PostProcessor实现将在bean被注入到依赖它的其他bean之前执行。
你随时可以创建自己的约束注解,但Bean Validation API中已经提供了几个内建的注解,它们可以满足大多数常见的验证需求。所有这些约束都在javax.validation.constraints包中。
将限制注解用于方法验证时,必须总是标注在接口上,而不是实现上。
如果希望指定应该在方法执行时应用的验证组,可以在类上使用@javax.validation.GroupSequence和@ValidateOnExecution。另一方面,通过@Validated可以直接在其中指定验证组,而无须使用额外的注解,另外它可以为同一个控制器类中的不同MVC控制器方法参数指定不同的组。
如果只希望验证特定分组中的限制,那么可以在@Validated注解中指定这些分组。

@Validated({Default.class, Group.class})
public interface EmployeeService
{
...
}

在Bean验证中,限制可以继承另一个限制。当然,这与类的继承不同,因为注解是不能继承的。不过,根据惯例,限制注解通常包含一个目标ElementType.ANNOTATION_TYPE。在定位到限制注解时,Validator将决定注解定义上是否标注了任何其他限制。如果是这样,它将把所有的额外限制和原始限制中定义的逻辑合并成一个复合限制。在这种情况下,限制继承了它被标注的所有限制。如果出于某些原因需要创建一个不能被继承的限制,那么只需要在定义中忽略ElementType.ANNOTATION_TYPE即可。

posted @ 2020-02-04 10:53  范中豪  阅读(241)  评论(0编辑  收藏  举报