第7章 Spring Boot集成模板引擎
其实,没有任何一个模板引擎(jsp,velocity,thymeleaf,freemarker,etc)可以完全实现MVC绝对的分层,只有“自由度”上的界定罢了。因为MVC本来就是相互关联的,不可分割的一个整体。
在MVC模式中,模板引擎的工作原理基本一样,比如说以freemarker为例,如下图:
7.1 Spring Boot集成jsp模板
7.2 Spring Boot集成thymeleaf模板
7.3 Spring Boot集成velocity模板
本节我们使用SpringBoot集成velocity开发一个极简的服务监控系统。
我们使用SpringBoot 1.4.5.RELEASE, 这是SpringBoot集成Velocity views的最新版本,目前看也是最后一个版本。数据库ORM层我们采用jpa。
默认情况下,Spring Boot会配置一个VelocityViewResolver,如果需要的是VelocityLayoutViewResolver,你可以自己创建一个名为velocityViewResolver的bean。你也可以将VelocityProperties实例注入到自定义视图解析器以获取基本的默认设置。
以下示例使用VelocityLayoutViewResolver替换自动配置的velocity视图解析器,并自定义layoutUrl及应用所有自动配置的属性:
@Bean(name = "velocityViewResolver")
public VelocityLayoutViewResolver velocityViewResolver(VelocityProperties properties) {
VelocityLayoutViewResolver resolver = new VelocityLayoutViewResolver();
properties.applyToViewResolver(resolver);
resolver.setLayoutUrl("layout/default.vm");
return resolver;
}
1.配置pom依赖
引入spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- Starter for building MVC web applications using Velocity views. Deprecated since 1.4 -->
<version>1.4.5.RELEASE</version>
<!--<version>1.5.2.RELEASE</version>-->
</parent>
引入velocity-starter
<!--web容器支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Velocity starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-velocity</artifactId>
</dependency>
默认配置下spring boot会从src/main/resources/templates目录中去找模板
引入jpa,mysql-connector
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.application.properties配置
# VELOCITY TEMPLATES (VelocityAutoConfiguration) 在SpringBoot
spring.velocity.templateEncoding=UTF-8
spring.velocity.properties.input.encoding=UTF-8
spring.velocity.properties.output.encoding=UTF-8
spring.velocity.resourceLoaderPath=classpath:/templates/
spring.velocity.suffix=.html
server.port=7001
# security
#security.user.name=admin
#security.user.password=admin
# Spring Boot log level
logging.config=logback.xml
logging.level.org.springframework.web: INFO
#mysql
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/femon?useUnicode=true&characterEncoding=UTF8
spring.datasource.username = qa_conn
spring.datasource.password = qa_conn
spring.datasource.driverClassName = com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
3.Dao层代码示例
package com.femon.dao;
import java.util.Date;
import java.util.List;
import com.femon.entity.ServiceData;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author 一剑 2015年12月23日 下午9:34:15
*/
public interface ServiceDataDao extends PagingAndSortingRepository<ServiceData, Integer> {
@Query("select h from ServiceData h where h.serviceId = ?1")
List<ServiceData> findByServiceId(int serviceId, Pageable pageable);
@Query("select h from ServiceData h where h.serviceId = ?1 and h.sampleTime between ?2 and ?3 ")
List<ServiceData> findByServiceIdAndDay(int serviceId, Date dayStart, Date dayEnd, Pageable pageable);
@Query("select h from ServiceData h where h.serviceId = ?1 and state=0 and h.sampleTime between ?2 and ?3 ")
Page<ServiceData> findFailByServiceIdAndDay(int serviceId, Date dayStart, Date dayEnd, Pageable pageable);
ServiceData save(ServiceData h);
}
4.Controller层代码示例
package com.femon.controller;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.femon.dao.ServiceDao;
import com.femon.dao.ServiceDataDao;
import com.femon.entity.Service;
import com.femon.entity.ServiceData;
import com.femon.result.ServiceDataResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* @author 一剑 2015年12月29日 下午5:23:22
*/
@Controller
public class ServiceController {
@Autowired
ServiceDataDao historyDao;
@Autowired
ServiceDao serviceDao;
......
@RequestMapping(value = "/service", method = RequestMethod.GET)
public ModelAndView list(Model model, @RequestParam(value = "page", defaultValue = "0", required = false) int page,
@RequestParam(value = "count", defaultValue = "10", required = false) int count,
@RequestParam(value = "order", defaultValue = "ASC", required = false)
Sort.Direction direction,
@RequestParam(value = "sort", defaultValue = "gmtCreate", required = false)
String sortProperty,
@RequestParam(value = "hostCode", defaultValue = "1", required = false) int hostCode) {
ModelAndView modelAndView = new ModelAndView("/service");
Page result = null;
if (hostCode == 0) {
result = serviceDao.findAll(new PageRequest(page, count, new Sort(direction, sortProperty)));
} else {
result = serviceDao.findByHost(hostCode, new PageRequest(page, count, new Sort(direction, sortProperty)));
}
long totalPages = result.getTotalPages();
List<Integer> pageIndexList = new ArrayList<Integer>((int)totalPages);
for (int i = 0; i < totalPages; i++) {
pageIndexList.add(i);
}
int currentPage = page;
List<Service> serviceList = result.getContent();
model.addAttribute("serviceList", serviceList);
model.addAttribute("pageIndexList", pageIndexList);
model.addAttribute("currentPage", currentPage);
model.addAttribute("currentHost", hostCode);
return modelAndView;
}
......
}
5.执行定时任务
简单的定时任务的执行,只需要使用@Scheduled注解即可。但是要在启动类上面加上注解@EnableScheduling。
支持cron表达式:
@Scheduled(cron="0 * * * * MON-FRI")
和fixedRate:
@Scheduled(fixedRate = 60000)
等使用方式。
代码示例
@Component
public class WatchService {
@Scheduled(fixedRate = 60000)
public void curlJob() {
...
}
}
6.启动类代码
应用入口类:Application.java
package com.femon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author 一剑
*/
@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.femon")
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
7.运行测试
Run Application,你将看到如下的输出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.5.RELEASE)
2017-04-05 14:08:35.456 INFO 93948 --- [ main] com.femon.Application : Starting Application on jacks-MacBook-Air.local with PID 93948 (/Users/jack/book/femon/target/classes started by jack in /Users/jack/book/femon)
2017-04-05 14:08:35.466 INFO 93948 --- [ main] com.femon.Application : No active profile set, falling back to default profiles: default
2017-04-05 14:08:35.742 INFO 93948 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@36c88a32: startup date [Wed Apr 05 14:08:35 CST 2017]; root of context hierarchy
2017-04-05 14:08:37.996 INFO 93948 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$e6b9fa58] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-04-05 14:08:38.673 INFO 93948 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 7001 (http)
2017-04-05 14:08:38.686 INFO 93948 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2017-04-05 14:08:38.687 INFO 93948 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-04-05 14:08:38.811 INFO 93948 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-04-05 14:08:38.812 INFO 93948 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3079 ms
.......
2017-04-05 14:08:45.135 INFO 93948 --- [ main] o.s.w.s.v.velocity.VelocityConfigurer : ClasspathResourceLoader with name 'springMacro' added to configured VelocityEngine
2017-04-05 14:08:45.471 INFO 93948 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-04-05 14:08:45.486 INFO 93948 --- [ main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2017-04-05 14:08:45.576 INFO 93948 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 7001 (http)
2017-04-05 14:08:45.582 INFO 93948 --- [ main] com.femon.Application : Started Application in 11.253 seconds (JVM running for 12.373)
系统运行效果图:
源代码:https://github.com/EasySpringBoot/femon
7.4 Spring Boot集成freemarker模板
与JSP相比,FreeMarker的一个优点在于不能轻易突破模板语言开始编写Java代码,因此降低了领域逻辑漏进视图层的危险几率。
本节我们给出一个SpringBoot集成freemarker模板引擎的Demo,基于gradle构建。
1.添加spring-boot-starter-freemarker依赖到pom中
buildscript {
ext {
springBootVersion = '1.5.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-freemarker')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
2.写后端Controller
package com.example.controller;
import java.util.Date;
import java.util.Map;
import com.example.biz.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Created by jack on 2017/4/5.
*/
@Controller
public class WelcomeController {
@Value("${application.message:Hello,World}")
private String message = "Hello,World";
@Autowired
BookService bookService;
@GetMapping("/welcome")
public String welcome(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", this.message);
model.put("books", bookService.findAll());
return "welcome";
}
}
3.写前端ftl代码
<!DOCTYPE html>
<html lang="zh">
<body>
Date: ${time?date}
<br>
Time: ${time?time}
<br>
Message: ${message}
<div>
<#list books as book>
<li>书名: ${book.name}</li>
<li>作者: ${book.author}</li>
<li>出版社: ${book.press}</li>
</#list>
</div>
</body>
</html>
4.运行测试
运行DemoApplication, 我们将看到如下日志:
2017-04-05 22:05:06.020 INFO 98366 --- [ main] com.example.DemoApplication : Starting DemoApplication on jacks-MacBook-Air.local with PID 98366 (/Users/jack/book/demo/build/classes/main started by jack in /Users/jack/book/demo)
......
o.s.w.s.v.f.FreeMarkerConfigurer : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2017-04-05 22:05:08.848 INFO 98366 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-04-05 22:05:08.911 INFO 98366 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 5678 (http)
2017-04-05 22:05:08.931 INFO 98366 --- [ main] com.example.DemoApplication : Started DemoApplication in 3.37 seconds (JVM running for 4.017)
2017-04-05 22:25:02.870 INFO 98366 --- [nio-5678-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-04-05 22:25:02.872 INFO 98366 --- [nio-5678-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-04-05 22:25:02.902 INFO 98366 --- [nio-5678-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 30 ms
浏览器访问:http://localhost:5678/welcome
我们将看到如下输出:
本节工程源码 :https://github.com/EasySpringBoot/HelloWorld/tree/freemarker_demo_2017.4.6