源码下载链接:
https://github.com/thymeleaf/thymeleafexamples-gtvg
https://github.com/thymeleaf/thymeleafexamples-stsm
本篇博客主要是在上述两个工程的基础上,仿照stsm工程为gtvg集成Spring框架,使其@Controller等元数据,通过对
Thymeleaf +Spring官方文档的解读,参照Spring Expression Language (SpEL)语言规则对gtvg进行适当的修改。
实验工具:Eclipse, Chrome 浏览器
本次实验主要是在gtvg的基础上进行修改,其项目的基本结构保持不变。
第一步:Maven工程首先应该配置pom.xml,添加需要的依赖
主要的修改为: artifactId 修改为 my_mvn, 添加Spring框架版本及相应的Spring依赖。
<?xml version="1.0" encoding="UTF-8"?>
<!-- ========================================================================= -->
<!-- -->
<!-- Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org) -->
<!-- -->
<!-- Licensed under the Apache License, Version 2.0 (the "License"); -->
<!-- you may not use this file except in compliance with the License. -->
<!-- You may obtain a copy of the License at -->
<!-- -->
<!-- http://www.apache.org/licenses/LICENSE-2.0 -->
<!-- -->
<!-- Unless required by applicable law or agreed to in writing, software -->
<!-- distributed under the License is distributed on an "AS IS" BASIS, -->
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -->
<!-- implied. See the License for the specific language governing -->
<!-- permissions and limitations under the License. -->
<!-- -->
<!-- ========================================================================= -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>thymeleafexamples</groupId>
<artifactId>my_mvn</artifactId>
<packaging>war</packaging>
<version>ci</version>
<name>Thymeleaf Examples - My Sring Thymeleaf MVC </name>
<description>XML/XHTML/HTML5 template engine for Java</description>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<organization>
<name>The THYMELEAF team</name>
<url>http://www.thymeleaf.org</url>
</organization>
<scm>
<url>scm:git:git@github.com:thymeleaf/thymeleafexamples-stsm.git</url>
<connection>scm:git:git@github.com:thymeleaf/thymeleafexamples-stsm.git</connection>
<developerConnection>scm:git:git@github.com:thymeleaf/thymeleafexamples-stsmgit</developerConnection>
</scm>
<developers>
<developer>
<id>dfernandez</id>
<name>Daniel Fernandez</name>
<email>daniel.fernandez AT 11thlabs DOT org</email>
<roles>
<role>Project admin</role>
</roles>
</developer>
</developers>
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<springframework.version>4.3.3.RELEASE</springframework.version>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.html</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<configuration>
<encoding>ISO-8859-1</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>build-dist</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly-dist</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
<configuration>
<descriptors>
<descriptor>${basedir}/src/assembly/sources.xml</descriptor>
</descriptors>
<appendAssemblyId>true</appendAssemblyId>
<finalName>${project.groupId}-${project.artifactId}-${project.version}</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>${thymeleaf.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
第二步:删除原有的application包,仿照stms的application包对gtvg的Spring进行配置,如图所示
SpringServletInitializer.java不必修改,只需修改SpringWebConfig.java(对于该文件,我们也可以以beans.xml的形式实现),删除原有的addFormatters、varietyFormatter、dateFormatter等方法,因为我们在gtvg中并不需要stsm的VarietyFormatter等类,同时修改viewResolver方法,添加 viewResolver.setCharacterEncoding("UTF-8"),使其编码方式为 “UTF-8”,否则中文会乱码。由于我们已经有了SpringWebConfig.java 来实现配置,那么就可以删掉web.xml文件,filter包也用不到,删掉。
/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
package thymeleafexamples.gtvg.application;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
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.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
/*import thymeleafexamples.stsm.web.conversion.DateFormatter;
import thymeleafexamples.stsm.web.conversion.VarietyFormatter;*/
@Configuration
@EnableWebMvc
@ComponentScan("thymeleafexamples.gtvg")
public class SpringWebConfig
extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
public SpringWebConfig() {
super();
}
public void setApplicationContext(final ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
/* ******************************************************************* */
/* GENERAL CONFIGURATION ARTIFACTS */
/* Static Resources, i18n Messages, Formatters (Conversion Service) */
/* ******************************************************************* */
/**
* Dispatcher configuration for serving static resources
*/
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
/**
* Message externalization/internationalization
*/
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
return messageSource;
}
/* **************************************************************** */
/* THYMELEAF-SPECIFIC ARTIFACTS */
/* TemplateResolver <- TemplateEngine <- ViewResolver */
/* **************************************************************** */
@Bean
public SpringResourceTemplateResolver templateResolver(){
// SpringResourceTemplateResolver automatically integrates with Spring's own
// resource resolution infrastructure, which is highly recommended.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
// HTML is the default value, added here for the sake of clarity.
templateResolver.setTemplateMode(TemplateMode.HTML);
// Template cache is true by default. Set to false if you want
// templates to be automatically updated when modified.
templateResolver.setCacheable(true);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(){
// SpringTemplateEngine automatically applies SpringStandardDialect and
// enables Spring's own MessageSource message resolution mechanisms.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// Enabling the SpringEL compiler with Spring 4.2.4 or newer can
// speed up execution in most scenarios, but might be incompatible
// with specific cases when expressions in one template are reused
// across different data types, so this flag is "false" by default
// for safer backwards compatibility.
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
//
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}
}
第三步: 修改控制器
因为我们使用@Controller、@RequestMapping等元数据,可以删除gtvg原有controller包下的所有控制器,进行重写,我下面实现了3个控制器,如图所示:
(一)对于GTVGController.java,主要实现基本的地址请求,实现如下:
GTVGController.java
package thymeleafexamples.gtvg.web.controller;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import thymeleafexamples.gtvg.business.entities.User;
import thymeleafexamples.gtvg.business.entities.Order;
import thymeleafexamples.gtvg.business.entities.Product;
import thymeleafexamples.gtvg.business.services.OrderService;
import thymeleafexamples.gtvg.business.services.ProductService;
@Controller
public class GTVGController {
@Autowired
private ProductService productService;
@Autowired
private OrderService orderService;
public GTVGController() {
// TODO Auto-generated constructor stub
super();
}
@ModelAttribute("today")
public Date getToday() {
return Calendar.getInstance().getTime();
}
@ModelAttribute("prods")
public List<Product> showALlProducts() {
return this.productService.findAll();
}
@ModelAttribute("orders")
public List<Order> showAllOrders() {
return this.orderService.findAll();
}
@RequestMapping({"/", "/gtvg"})
public String showHome(HttpSession session) {
session.setAttribute("user", new User("John", "Apricot", "Antarctica", null));
return "home";
}
@RequestMapping("/userprofile")
public String showUserProfile() {
return "userprofile";
}
@RequestMapping("/subscribe")
public String showSubscribe() {
return "subscribe";
}
@RequestMapping("/product/list")
public String showProductList() {
return "/product/list";
}
@RequestMapping("/order/list")
public String showOrderList() {
return "/order/list";
}
}
为了实现orderProduce、productService的自动装配(@Autowired),需要做一些修改,
1)对于 repositories下的所有_Repository.java文件的类名上方添加@Repository注解,
2)同理,对services包下的所有_Service.java文件,在类名的上方添加@Service注解,再添加一个_Repository类型自动装配的私有成员,
以OrderService.java为例,另外两个Service类同理,修改后的代码如下:
/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
package thymeleafexamples.gtvg.business.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import thymeleafexamples.gtvg.business.entities.Order;
import thymeleafexamples.gtvg.business.entities.repositories.OrderRepository;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public OrderService() {
super();
}
public List<Order> findAll() {
return this.orderRepository.findAll();
}
public Order findById(final Integer id) {
return this.orderRepository.findById(id);
}
public List<Order> findByCustomerId(final Integer customerId) {
return this.orderRepository.findByCustomerId(customerId);
}
}
上述工作也相当于beans.xml的配置(注入依赖)过程
(二)另外两个控制器,主要用于处理order/details 和 product/comments 等更加细节的请求,具体实现如下:
ProductCommentsController.java
package thymeleafexamples.gtvg.web.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import thymeleafexamples.gtvg.business.entities.Product;
import thymeleafexamples.gtvg.business.services.ProductService;
@Controller
public class ProductCommentsController {
@Autowired
private ProductService productService;
@ModelAttribute("prod")
public Product getProduct(Integer prodId) {
return this.productService.findById(prodId);
}
@RequestMapping(path = "/product/comments", params = {"prodId"})
public String showProductComments(final HttpServletRequest request) {
Integer prodId = Integer.valueOf(request.getParameter("prodId"));
getProduct(prodId);
return "/product/comments";
}
}
OrderDetailsController.java
package thymeleafexamples.gtvg.web.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import thymeleafexamples.gtvg.business.entities.Order;
import thymeleafexamples.gtvg.business.services.OrderService;
@Controller
public class OrderDetailsController {
@Autowired
private OrderService orderService;
@ModelAttribute("order")
public Order getOrder(Integer orderId) {
return this.orderService.findById(orderId);
}
@RequestMapping(path="/order/details", params = {"orderId"})
public String showOrderDetails(final HttpServletRequest request) {
final int orderId = Integer.valueOf(request.getParameter("orderId"));
getOrder(orderId);
return "order/details";
}
}
第四步:到第三步为止,大部分工作已经完成,最后就需要修改资源文件(.properties,.html)
1) .properties 文件
在SpringWebConfig.java 有一个messageSource方法,就是用于管理资源文件的(含有@Bean注解的类,均可以在beans.xml文件中配置)。
_.properties文件是用于实现文字国际化表达的:
参考stms的目录,可以发现,它的_.properties文件都是集中在Messages.properties中messageSource.setBasename("Messages");
因此,在src/main/resources 下新建Messages.properties 文件,将gtvg原有templates文件夹下的properties文件复制到对应版本的properties文件中
如下图所示,其中Messages.properties 文件包含了gtvg中home.properties、userfile.properties以及list.properties的内容
这里还有另外一种方法,就是不需要将全部的.properties文件整合到Messages文件中(但是要移到resources文件下),如果有很多文件,这会很麻烦。我们可以修改messageSource方法,如图所示,调用messageSource.addBasenames方法将我们的properties文件动态地注入到工程中。
2) .html 文件
由于使用Spring集成,在Thymeleaf 中使用的方言有些符合OGNL,但是却不符合SpEL(Spring Expression Language), 因此我们应作出相应的修改。
比如:
对于order/list.html, 应该将${#aggregates.sum(o.orderLines.{purchasePrice * amount})}修改为
${#aggregates.sum(o.orderLines.![purchasePrice * amount])}
对于order/details.html 也要作上述修改,还有 SpEL 在 '{th:object,data-th-object}' 只支持(${...}),
因此需要将 <div th:object="*{customer}">修改为<div th:object="{$order.customer}">
第五步:到此为止,我们完成了Spring的集成工作,可以检验一下我们的结果,clean compile tomcat7:run 运行程序,与原有gtvg的项目结果进行比对,如果完全一样,则我们正确集成了Spring。
注意:测试时根URI为localhost:8080/my_mvn/, my_mvn是我们的工程名
参考链接:
http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
http://www.thymeleaf.org/doc/articles/springmvcaccessdata.html
http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/expressions.html
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc