Spring MVC 之使用 Apache Tiles

字数1,259 大约花费6分钟

有时候,一些页面会共用同样的布局,比如相同的头部菜单或者底部内容,可以将重复的内容抽取出来,写在单独的文件里,而每个页面在适当的地方引入这些文件。但是即使这样,也还是显得繁琐,而且一旦布局变化(比如头部的菜单移动到侧边栏),每个文件也都要改。而使用 Apache Tiles,可以将方便地重复使用布局模板,由于布局间可以继承,对布局变化的处理也更加方便。

定义 Tiles 视图解析器

下面代码是继承自 WebMvcConfigurerAdapter 的 WebConfig 类,在其中定义了 TilesConfigurer 和 TilesViewResolver 的 bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package tantanit.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("tantanit.web")
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
super.addResourceHandlers(registry);
}


// Tiles
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tiles = new TilesConfigurer();
tiles.setDefinitions(new String[] {
"/WEB-INF/layout/tiles.xml",
"/WEB-INF/views/**/tiles.xml"
});
tiles.setCheckRefresh(true);
return tiles;
}

@Bean
public ViewResolver viewResolver() {
return new TilesViewResolver();
}

}

上述代码中,配置 TilesConfigurer,指定 tiles 定义文件,并指定了多个 tiles 定义文件。然后,定义视图解析器为 TilesViewResolver。

下面让我们看一下 /WEB-INF/layout/tiles.xml 的内容。

tiles 定义

更多

Spring in Acton 4 读书笔记之使用 Thymeleaf

字数806 大约花费3分钟

JSP 的缺陷

尽管 JSP 历史悠久、应用广泛,但它有以下缺陷:

  • 尽管看起来像,但既不是 HTML,也不是 XML
  • JSP 标签库使文档格式不友好
  • 如果 JSP 标签没有在服务端正确编译后发给浏览器,浏览器由于不理解 JSP 标签,渲染的结果是一个灾难
  • JSP 标准和 servlet 耦合,所以 JSP 只能用于基于 servlet 的 web 应用视图。像 email 或者不基于 servlet 的 web 应用就无法使用 JSP

由于 JSP 有以上缺陷,许多模板试图取代 JSP,而其中 Thymeleaf 是一个令人兴奋的选择。Thymeleaf 自然,不依赖于标签库。可以在任何欢迎 HTML 的地方编辑和渲染。并且由于不依赖于 servlet 标准,所以使用范围比 JSP 广。

如何配置 Thymeleaf 视图解析器

要在 Spring 中使用 Thymeleaf,需要配置以下三个 bean,以集成 Thymeleaf 和 Spring:

更多

Spring in Acton 4 读书笔记之视图解析

字数753 大约花费3分钟

什么是视图解析

浏览器只识别静态的 html 文件。MVC 中,controller 并不直接生成 html 文件,而只负责为 model 填充数据,然后将 model 转给视图(view)。并且 controller 只知道视图的逻辑名称,并不负责视图的内容。这样,就将转发请求和解析视图这两件事在 Spring MVC 中进行了解耦。controller 和 view 之间的耦合只在于对 model 中数据的定义。
由于 controller 只提供了视图的逻辑名称,Spring 要知道怎样渲染视图,就需要视图解析器的帮助。视图解析器定义了视图模板文件所在的目录和后缀,和视图逻辑名称拼接之后,就构成了视图完整的路径。

Spring MVC 中定义的视图解析器接口和视图接口如下:

1
2
3
4
5
6
7
8
9
10
11
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale)
throws Exception;

}

public interface View {
String getContentType();
void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception
;

}

ViewResolver 解析视图名称,并返回 View。View 则根据 model(数据)以及 request 渲染出 response。只要实现了代码中的两个方法,就可以解析视图了。然而在 Spring MVC 中,一般不需要再这样做,因为 Spring MVC 为各种常用视图框架提供了视图解析器的实现。

视图解析器 描述
InternalResourceViewResolver 将视图解析为 Web 应用的内部资源(一般为 JSP)
TilesViewResolver 将视图按 Apache Tile 定义进行解析,Tiles 2.0 和 Tiles 3.0 分别有一个 TilesViewResolver 实现。
FreeMarkerViewResolver 将视图按 FreeMarker 模板解析
ThymeleafViewResolver 将视图按 Thymeleaf 模板解析
ResourceBundleViewResolver 将视图解析为 ResourceBundle(一般是属性文件)
UrlBasedViewResolver 根据视图名称直接解析
VelocityViewResolver 根据 Velocity 模板解析
VelocityLayoutViewResolver 根据 Velocity 布局,找到对应定义进行解析
BeanNameViewResolver 将视图解析成 Spring 应用上下文中的 bean,bean 的 id 和视图一样
XmlViewResolver 解析 XML, 和 BeanNameViewResolver 类似
XsltViewResolver 解析 XSLT
ContentNegotiatingViewResolver 根据视图的类型将视图转发给相应的另一个视图解析器
JasperReportsViewResolver 将视图按 JasperReports 定义解析

更多

Spring in Acton 4 读书笔记之 Spring MVC 高级配置

字数718 大约花费3分钟

上一篇文章 中讲解了 Spring MVC 的基础配置,本文对应 Spring in Action(Spring 实战)第四版第七章中(7.1.1 Customizing DispatcherServlet configuration)和 (7.1.2 Adding additional servlets and filters) 的内容,将讲解如何自定义 Spring MVC 的配置。

如同在上一篇文章所介绍的,任何继承了 AbstractAnnotationConfigDispatcherServletInitializer 的类都会自动被用来在应用程序 DispatcherServlet 上下文中配置 DispatcherServlet 和其它应用上下文。AbstractAnnotationConfigDispatcherServletInitializer 定义了三个抽象方法,getServletMappings,getServletConfigClasses 以及 getRootConfigClasses,分别定义默认的 servlet 映射、DispatcherServlet 上下文配置文件以及其它(中间层和后端)应用上下文配置文件。继承了 AbstractAnnotationConfigDispatcherServletInitializer 的类必须实现这三个方法。

为 DispatcherServlet 添加自定义配置

除了以上三个一定要进行的配置外,根据使用场景的不同,有时需要进行一些其它的配置。AbstractAnnotationConfigDispatcherServletInitializer 提供了一些方法,当有需要时,可以通过覆盖这些方法,提供额外的配置。比如 customizeRegistration 方法,AbstractAnnotationConfigDispatcherServletInitializer 在 servlet 上下文中注册了 DispatcherServlet 之后,会调用 customizeRegistration 方法,通过覆盖 customizeRegistration 方法,可以为 DispatcherServlet 提供额外配置。比如下面的代码就是在处理类型为 multipart 的请求,将临时文件夹定义为 /tmp/spittr/uploads。

1
2
3
4
5
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads"));
}

添加自定义的 servlet,filter 和 listener

更多

Spring MVC 的基础配置

字数1,141 大约花费5分钟

配置 DispatcherServlet

DispatcherServlet 是 Spring MVC 的核心,它负责接收 request,并且决定 request 将转给哪个组件。历史上,包括 DispatcherServlet 的 servlet 是 web.xml 文件配置,而 web.xml 文件包含在 war 里。现在仍然可以用这种方式进行配置,但更好用的方式是使用 java 文件来配置 servlet 容器中的 DispatcherServlet。

任何继承了 AbstractAnnotationConfigDispatcherServletInitializer 的类都会自动被用来在应用程序上下文中配置 DispatcherServlet 和 Spring 应用上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package spittr.config;
import org.springframework.web.servlet.support.
AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
}

在 Servlet 3.0 环境中,容器会在 classpath 下查找任何实现了 javax.servlet .ServletContainerInitializer 接口的类,并用这些类来配置 servlet 容器。Spring 提供了一个实现这个接口的类:SpringServletContainerInitializer,这个类会查找实现了 WebApplicationInitializer 接口的类,并且作为后者的代理,对 servlet 上下文进行配置。Spring 3.2 引进了 AbstractAnnotationConfigDispatcherServletInitializer,这个类实现了 WebApplicationInitializer 接口。在上面的程序中,由于 SpittrWebAppInitializer 继承了 AbstractAnnotationConfigDispatcherServletInitializer,也就实现了 WebApplicationInitializer 接口。所以当程序在 servlet 3.0 容器中部署时,SpittrWebAppInitializer 会被自动查找到,并且用来配置 servlet 上下文。

尽管名字很长,AbstractAnnotationConfigDispatcherServletInitializer 很容易使用。SpittrWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer,并覆盖了三种方法。第一个方法,getServletMappings(),确定了 DispatcherServlet 将会被映射到哪些路径。例子中被映射到 /,表示 DispatcherServlet 将会是应用程序的默认 servlet, 它将处理这个应用接收到的所有的请求。

更多

Spring in Action 4 读书笔记之 Spring MVC 中请求的处理流程

字数869 大约花费3分钟

我计划完成 50 到 100 篇有关 Spring 的文章,这是第十四篇。本文对应 Spring in Action(Spring 实战)第四版第五章的 5.1.1(5.1.1 Following the life of a request),将讲述 Spring MVC 中请求的流程。

每当用户在浏览器点击链接或者提交表单的时候,就会生成一个 request。request 就像一个邮差,负责将信息从一个地方地方传递到另一个地方。

request 很忙,它从离开浏览器开始,一直到跟着 response 返回到浏览器结束,会经过很多站,而每一站都释放一些信息,又带上一些新的信息。下图显示了在 Spring MVC 中,request 在哪些站点停留。

Spring MVC 中请求的处理流程

更多

Spring in Acton 4 读书笔记之使用 AOP 为类动态添加方法

字数891 大约花费3分钟

我计划完成 50 到 100 篇有关 Spring 的文章,这是第十三篇。本文对应 Spring in Action(Spring 实战)第四版第四章中(4.3.4 Annotating introductions)的内容,将讲解如何使用标签为类动态添加方法。

一些像 Ruby 和 Groovy 这样的语言,有开放类(open classes)的概念,可以在不改变类和对象的定义的情况下,增加新的方法。不幸的是,Java 没有那么动态,一旦一个类编译好之后,很难再为这个类增加功能了。

但是仔细想想,使用 AOP 的时候,难道不是在动态增加功能吗?虽然没有为类增加方法,但是为这些类执行时增加了一些功能性。进而,使用 AOP 的 introduction 的概念,可以给 Spring 的 bean 添加方法。

在 Spring 中,aspect 对目标对象进行代理,并且和目标对象有相同接口。试想一下,如果不仅具有目标对象的接口,还添加了一些目标对象没有的接口,那么通过调用这个代理,就可以实现目标对象没有实现的接口了。这样,尽管没有改变目标对象的代码,却在功能上添加了方法。

工作原理如下图:
使用 AOP 为类动态添加方法

更多

Spring in Acton 4 读书笔记之使用 AOP 监听函数的参数

字数676 大约花费3分钟

在上一篇文章 Spring in Action 4 读书笔记之使用标签创建 AOP 中讲解了如何使用标签定义 aspect,本文继续进行这部分内容。如前所述,Spring 的 AOP 都是作用在方法级别,有时候,需要监听函数的参数,本文讲解如何根据不同的参数值,执行不同的行为。比如下面的代码,记录不同参数执行的次数。

定义 aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package soundsystem;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackCounter {

private Map<Integer, Integer> trackCounts =
new HashMap<Integer, Integer>();

@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int))" +
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}

@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}

public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)
}

}

可以看到,这里是有 @Pointcut 标签定义了一个 aspect。除了是有 execution 之外,还使用 args 指定参数为 trackNumber,需要注意的是,args 函数的参数名 trackNumber 需要和被监听的 trackPlayed 函数同名。countTrack 函数使用 @Before 标签,定义了在 trackPlayed 方法执行前做的事。这里访问了 trackPlayed 方法的参数 trackNumber,并且记录了该参数 trackNumber 被执行的次数。

定义配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {

@Bean
public CompactDisc sgtPeppers() {
BlankDisc cd = new BlankDisc();
cd.setTitle("Sgt. Pepper's Lonely Hearts Club Band");
cd.setArtist("The Beatles");
List<String> tracks = new ArrayList<String>();
tracks.add("Sgt. Pepper's Lonely Hearts Club Band");
tracks.add("With a Little Help from My Friends");
tracks.add("Lucy in the Sky with Diamonds");
tracks.add("Getting Better");
tracks.add("Fixing a Hole");
// ...other tracks omitted for brevity...
cd.setTracks(tracks);
return cd;
}

@Bean
public TrackCounter trackCounter() {
return new TrackCounter();
}
}

更多

Spring in Action 4 读书笔记之使用标签创建 AOP

字数1,311 大约花费6分钟

在之前的读书笔记 Spring in Acton 4 读书笔记之 AOP 原理及 Spring 对 AOP 的支持 中,讲到 Spring 对 AOP 的支持包含四方面:

  1. Spring 基于代理的经典的 AOP
  2. 使用 XML 配置将纯 POJO 转化为 aspect
  3. 使用 Spring 的 @Aspect 标签,创建 aspect
  4. Spring 不创建 aspect,只注入(引用)AspectJ 框架的 aspect

Spring in Action(Spring 实战)的第四章第三节(4.3 Creating annotated aspects)讲述了其中第三种,即如何使用标签创建 aspect。本文讲解其中的前面两小节:定义 aspect 以及创建 around advice。

AspectJ 5 引进的主要特性是使用标签创建 aspect。AspectJ 5 的缺点是需要学习扩展的 java 语言。但是 AspectJ 面向标签的编程模式使得将一个类转换成 aspect 变得很容易,只需要在类中加一些标签。

定义一个 aspect

以下是使用标签创建 AOP 的例子,这个例子在之前的文章中有提到过,观众在演出开始前后,以及出问题时,会自动做出一些反应:

更多

Spring in Acton 4 读书笔记之 AOP 的语法

字数686 大约花费3分钟

在之前的读书笔记 Spring in Acton 4 读书笔记之 AOP 原理及 Spring 对 AOP 的支持 中,讲解了 AOP 的各个组成部分,其中 pointcut 和 advice 组成了 aspect,是 AOP 的基础。本文对应 Spring in Action(Spring 实战)第四版的第四章第二节(4.2 Selecting join points with pointcuts)的内容,将讲解 aspect 特别是 pointcut 的语法。

Spring 中的表达式主要使用 AspectJ 的表达式,另外,添加了一个自己的 bean()函数。

AspectJ 的语法

Spring 的 AOP 中,pointcut 使用 AspectJ 的一部分 pointcut 语法表达式,可使用的表达式如下:

  • args(),匹配的函数,其参数的类型必须是 args()指定的类型
  • @args(),匹配的函数,其参数必须被 @args 指定的标签标注
  • execution(),匹配指定的函数
  • this(),匹配的函数,它的 AOP 代理实例必须是指定的类型
  • target(),匹配的函数, 所在类必须是指定的类型
  • @target(),匹配的函数, 所在类必须由指定类型的标签标注
  • within(),匹配的函数, 所在类必须是指定的类型之一
  • @within(),匹配的函数,所在类必须由指定类型之一的标签标注(使用 Spring AOP 时,定义的方法必须在由指定类型之一的标签标注的类中)
  • @annotation,匹配的函数,其自身必须由指定的标签标注。

如果尝试使用 AspectJ 的其它语法,Spring 会抛出 IllegalArgumentException 异常。

更多