课程计划
- 1、商品详情页面展示,动态展示(jsp + redis)
- 2、使用freemarker实现网页静态化(解决高并发)
- 3、使用ActiveMq同步生成静态网页
1、商品详情页面展示,动态展示(jsp + redis)
从架构中可以看出商品详情页面是一个表现层工程。
创建一个商品详情页面展示的Maven工程。
1.1、工程搭建
表现层工程taotao-item-web。打包方式war。可以参考表现层工程taotao-portal-web。
不使用骨架创建该Maven工程,继承父工程,不在赘图!
1.1.1、pom文件
配置对taotao-manager-interface的依赖和修改tomcat端口号。
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.taotao</groupId>
<artifactId>taotao-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>taotao-item-web</artifactId>
<packaging>war</packaging>
<dependencies>
<!-- 配置对common的依赖 -->
<dependency>
<groupId>com.taotao</groupId>
<artifactId>taotao-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 配置对taotao-manager-interface的依赖:表现层调用服务要通过该接口 -->
<dependency>
<groupId>com.taotao</groupId>
<artifactId>taotao-manager-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- 配置对dubbo的依赖 -->
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<!-- 排除对低版本jar包的依赖 -->
<exclusions>
<exclusion>
<artifactId>spring</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>netty</artifactId>
<groupId>org.jboss.netty</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8086</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.1.2、框架整合
整合后的框架结构如下图所示(并导入静态页面):
1.1.3、springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置加载属性文件 -->
<context:property-placeholder location="classpath:resource/resource.properties"/>
<!-- 配置包扫描器,扫描所有需要带@Controller注解的类 -->
<context:component-scan base-package="com.taotao.item.controller" />
<!-- 配置注解驱动 -->
<mvc:annotation-driven />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 引用dubbo服务 :需要先引入dubbo的约束-->
<dubbo:application name="taotao-item-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<!-- <dubbo:reference interface="com.taotao.content.service.ContentService" id="contentService" /> -->
</beans>
1.1.4、web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>taotao-item-web</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置解决post乱码的过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置springmvc的前端控制器 -->
<servlet>
<servlet-name>taotao-item-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation,
springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>taotao-item-web</servlet-name>
<!-- 拦截(*.html)结尾的请求,实现了网页的伪静态化,SEO:搜索引擎优化-->
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
1.2、功能分析
在搜索结果页面点击商品图片或者商品标题,展示商品详情页面。
在商品搜索系统中的搜索结果页面search.jsp中,修改如下:
请求的url:/item/{itemId}
参数:商品id
返回值:String 逻辑视图
业务逻辑:
1、从url中取参数,商品id
2、根据商品id查询商品信息(tb_item)得到一个TbItem对象,缺少images属性,可以创建一个pojo继承TbItem,添加一个getImages()方法,放在在taotao-item-web工程中。由于没有涉及到网络传输,所以该pojo不需要实现序列化接口。
代码如下:
/**
* 增加新属性images的TbItem
* @author chenmingjun
* @date 2018年11月27日下午1:23:26
* @version 1.0
*/
public class Item extends TbItem {
public Item() {
}
public Item(TbItem tbItem) {
// 由于我们根据商品id查询到的是TbItem,但是我们需要的是TbItem
// 方式一:初始化属性,将TbItem中的属性的值设置到Item中的属性中来
this.setId(tbItem.getId());
this.setTitle(tbItem.getTitle());
this.setSellPoint(tbItem.getSellPoint());
this.setPrice(tbItem.getPrice());
this.setNum(tbItem.getNum());
this.setBarcode(tbItem.getBarcode());
this.setImage(tbItem.getImage());
this.setCid(tbItem.getCid());
this.setStatus(tbItem.getStatus());
this.setCreated(tbItem.getCreated());
this.setUpdated(tbItem.getUpdated());
// 方式二:使用工具类,将“原来数据TbItem”中的属性的值拷贝到“现在数据Item”的属性中来
// BeanUtils.copyProperties(tbItem, this);
}
public String[] getImages() {
String image2 = this.getImage();
if (image2 != null && !"".equals(image2)) {
String[] strings = image2.split(",");
return strings;
}
return null;
}
}
3、根据商品id查询商品描述。
4、展示到页面。
1.3、Dao层
查询tb_item、tb_item_desc两个表,都是单表查询。可以使用逆向工程。
1.4、Service层
1.4.1、分析
在taotao-manager-interface和taotao-manager-service工程中添加接口的方法和实现。
1、根据商品id查询商品信息
参数:商品id
返回值:TbItem
2、根据商品id查询商品描述
参数:商品id
返回值:TbItemDesc
1.4.2、接口定义
taotao-manager-interface工程中定义ItemService.java
/**
* 测试:根据商品id查询商品信息
* @param itemId
* @return
*/
TbItem getItemById(Long itemId);
/**
* 根据商品id查询商品描述
* @param itemId
* @return
*/
TbItemDesc getItemDescById(Long itemId);
1.4.3、接口实现
taotao-manager-service工程中ItemServiceImpl.java
@Override
public TbItem getItemById(Long itemId) {
TbItem tbItem = itemMapper.selectByPrimaryKey(itemId);
return tbItem;
}
@Override
public TbItemDesc getItemDescById(Long itemId) {
TbItemDesc tbItemDesc = itemDescMapper.selectByPrimaryKey(itemId);
return tbItemDesc;
}
1.4.4、发布服务
在taotao-manager-service工厂中applicationContext-service.xml中发布服务:
1.5、表现层
1.5.1、分析
表现层调用服务层的方法,表现层应当是商品详情工程taotao-item-web。
1.5.2、引用服务
先在taotao-item-web工程中的pom.xml中配置对taotao-manager-interface的依赖。
再在taotao-item-web工程中的springmvc.xml中引入服务:
1.5.3、Controller
请求的url:/item/{itemId}
参数:商品id
返回值:String 逻辑视图
/**
* 商品详情的Controller
* @author chenmingjun
* @date 2018年11月27日下午2:48:52
* @version 1.0
*/
@Controller
public class ItemController {
@Autowired
private ItemService itemService;
@RequestMapping("item/{itemId}")
public String showItemInfo(@PathVariable Long itemId, Model model) {
// 跟据商品id查询商品信息
TbItem tbItem = itemService.getItemById(itemId);
// 把TbItem转换成Item对象
Item item = new Item(tbItem);
// 根据商品id查询商品描述
TbItemDesc tbItemDesc = itemService.getItemDescById(itemId);
// 把查询到的数据传递给页面
model.addAttribute("item", item);
model.addAttribute("itemDesc", tbItemDesc);
// 返回逻辑视图item.jsp
return "item";
}
}
以上是通过数据库查询得到商品的数据,进行展示,但是一般商品的详情页面的访问的并发量是比较高的
,所以为了减轻数据库的压力
,需要做优化
。
优化方案就是:添加缓存
。
1.6、向业务逻辑中添加缓存
1.6.1、缓存添加分析
使用redis做缓存。
业务逻辑:
1、根据商品id到缓存中命中。
2、查到缓存,直接返回。
3、查不到缓存,查询数据库。
4、查到数据,把数据放到缓存中。
5、返回数据。
缓存中缓存热点数据
,为了提高缓存的使用率
,需要设置缓存的有效期
,一般是一天的时间
,可以根据实际情况调整
。
需要使用String类型
来保存商品数据,为什么呢?
答:因为我们要设置每一个key的过期时间,String类型的key可以设置,而Hash里面的key是不支持设置过期时间的,此时不能使用Hash类型(便于内容归类)。
使用String类型
来保存商品数据,该如何归类呢?
答:可以通过加前缀
方法对redis中的key进行归类
。
例如:
ITEM_INFO:123456:BASE
ITEM_INFO:123456:DESC
[root@itheima bin]# pwd
/usr/local/redis/bin
[root@itheima bin]# ./redis-cli -h 192.168.25.153 -p 6379
192.168.25.153:6379> set ITEM_INFO:123456:BASE 123
OK
192.168.25.153:6379> set ITEM_INFO:123456:DESC 456
OK
192.168.25.153:6379> get ITEM_INFO:123456:BASE
"123"
192.168.25.153:6379> get ITEM_INFO:123456:DESC
"456"
192.168.25.153:6379>
如下图所示:
扩展知识:
如果把数据库中的
二维表
保存到redis中,该如何存储呢?答:
1、表名就是第一层
2、主键就是第二层
3、字段名是第三层
三层使用“:”分隔作为key,value就是字段中的内容。
1.6.2、添加redis客户端到服务层工程
其实缓存加到表现层和服务层都可以,加到表现层只能这个表现层调用,加到服务层可以多个表现层调用,所以推荐将redis客户端加到服务层工程。可以参考taotao-content-service工程。
在taotao-manager-service工程中的pom.xml中添加如下:
要添加缓存,可以使用之前开发过的JedisClient。
1.6.3、编写redis配置文件applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 配置对redis单机版的连接 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
<!-- 手动配置的jedis单机版客户端实现类bean:会在spring容器中加载 -->
<bean id="jedisClientPool" class="com.taotao.jedis.JedisClientPool"/>
<!-- 单机版和集群版不能共存,使用单机版时注释集群版的配置。使用集群版,把单机版注释。-->
<!--
配置对redis集群版的连接
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg>
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7001"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7002"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7003"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7004"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7005"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7006"></constructor-arg>
</bean>
</set>
</constructor-arg>
</bean>
手动配置的jedis集群版客户端实现类bean:会在spring容器中加载
<bean id="jedisClientCluster" class="com.taotao.jedis.JedisClientCluster"/>
-->
</beans>
1.6.4、添加缓存
在taotao-manager-service工程中添加如下缓存。
实现类ItemServiceImpl的需要的属性:
实现类ItemServiceImpl的方法如下:
取商品信息添加缓存:
@Override
public TbItem getItemById(Long itemId) {
// 添加缓存的原则是:不能够影响现有的业务逻辑
// 查询数据库之前先查询缓存
try {
if (itemId != null) {
// 注入JedisClient对象,根据key获取缓存
String jsonstring = jedisClient.get(ITEM_INFO_KEY + ":" + itemId + ":BASE");
if (StringUtils.isNotBlank(jsonstring)) { // 缓存中有,则转换后返回
// 设置缓存过期时间
jedisClient.expire(ITEM_INFO_KEY + ":" + itemId + ":BASE", ITEM_INFO_KEY_EXPIRE);
// 把jsonstring转成java对象
TbItem tbItem = JsonUtils.jsonToPojo(jsonstring, TbItem.class);
return tbItem;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 缓存中没有,去查询数据库
TbItem tbItem = itemMapper.selectByPrimaryKey(itemId);
// 把从数据库中查询到的结果添加到缓存
try {
if (tbItem != null) {
// 注入JedisClient对象,添加缓存
jedisClient.set(ITEM_INFO_KEY + ":" + itemId + ":BASE", JsonUtils.objectToJson(tbItem)); // redis中不能存java对象,存的都是字符串(json串)
// 设置缓存过期时间
jedisClient.expire(ITEM_INFO_KEY + ":" + itemId + ":BASE", ITEM_INFO_KEY_EXPIRE);
}
} catch (Exception e) {
e.printStackTrace();
}
return tbItem;
}
同理,取商品描述添加缓存:
@Override
public TbItemDesc getItemDescById(Long itemId) {
// 添加缓存的原则是:不能够影响现有的业务逻辑
// 查询数据库之前先查询缓存
try {
if (itemId != null) {
// 注入JedisClient对象,根据key获取缓存
String jsonstring = jedisClient.get(ITEM_INFO_KEY + ":" + itemId + ":DESC");
if (StringUtils.isNotBlank(jsonstring)) { // 缓存中有,则转换后返回
// 设置缓存过期时间
jedisClient.expire(ITEM_INFO_KEY + ":" + itemId + ":DESC", ITEM_INFO_KEY_EXPIRE);
// 把jsonstring转成java对象
TbItemDesc tbItemDesc = JsonUtils.jsonToPojo(jsonstring, TbItemDesc.class);
return tbItemDesc;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 缓存中没有,去查询数据库
TbItemDesc tbItemDesc = itemDescMapper.selectByPrimaryKey(itemId);
// 把从数据库中查询到的结果添加到缓存
try {
if (tbItemDesc != null) {
// 注入JedisClient对象,添加缓存
jedisClient.set(ITEM_INFO_KEY + ":" + itemId + ":DESC", JsonUtils.objectToJson(tbItemDesc)); // redis中不能存java对象,存的都是字符串(json串)
// 设置缓存过期时间
jedisClient.expire(ITEM_INFO_KEY + ":" + itemId + ":DESC", ITEM_INFO_KEY_EXPIRE);
}
} catch (Exception e) {
e.printStackTrace();
}
return tbItemDesc;
}
1.6.5、添加属性文件并加载属性文件
第一步:添加属性文件
如下图:
第二步:在applicationContext-dao.xml中加载属性文件:使用:
<context:property-placeholder location="classpath:properties/*.properties" />
2、使用FreeMarker实现网页静态化(解决高并发)
什么是静态化?
通过一些技术手段(FreeMarker)将动态的页面(JSP、asp.net、php) 转换成静态的页面,通过浏览器直接访问静态页面。
为什么要静态化?
1、通过浏览器直接访问静态的页面,不需要经过程序处理,它的访问速度高。
2、稳定性好。
3、更有效的防止安全漏洞问题,比如:不易遭受黑客攻击。
4、静态的页面更容易被搜索引擎收录。
怎么样实现静态化?
可以使用FreeMarker实现网页静态化。
2.1、什么是FreeMarker
FreeMarker
是一个用Java语言
编写的模板引擎
,它基于模板输出文本
。FreeMarker与Web容器无关
,即在Web运行时,它并不知道Servlet或HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java等。
目前企业中:主要用FreeMarker
做静态页面或是页面展示。
使用Velocity
模板引擎快速生成代码,有空自学。
2.2、FreeMarker的使用方法
我们在taotao-item-web中测试使用FreeMarker。
把FreeMarker的jar包添加到工程中。
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
在Maven工程中是添加依赖。
原理:
使用步骤:
第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
第二步:设置模板文件所在的路径。
第三步:设置模板文件使用的字符集。一般就是utf-8。
第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
第五步:创建一个模板使用的数据集,可以是POJO也可以是map。一般是Map。
第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成文件路径和文件名。
第七步:调用模板对象的process方法输出文件。
第八步:关闭流。
模板:
${hello}
在taotao-item-web工程中新建一个测试类,测试使用FreeMarker。如下图所示:
测试代码如下:
@Test
public void freeMarkerTest() throws Exception {
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File("C:/my/learn/Java/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl"));
// 第三步:设置模板文件使用的字符集。一般就是utf-8
configuration.setDefaultEncoding("utf-8");
// 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
Template template = configuration.getTemplate("hello.ftl");
// 第五步:创建一个模板使用的数据集,可以是POJO也可以是Map。推荐使用是Map。因为Map比较灵活。
Map<Object, Object> dataModel = new HashMap<>();
// 向数据集中添加数据
// 1、取Map中key的值
dataModel.put("hello", "this is my first freemarker test.");
// 第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成的文件路径和文件名。
Writer out = new FileWriter(new File("C:/my/learn/test/temp/out/hello.txt"));
// 第七步:调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 第八步:关闭流。
out.close();
}
2.3、Eclipse中可以使用FreeMarker的插件
将以下文件放置到eclipse的安装目录的plugins中,重启eclipse就可以用了。xxx.ftl文件就会出现高亮及颜色相关的提示。
2.4、模板的语法
注意:我们模板使用的数据集是Map集合,所以以下例子中均与Map集合有关
。
2.4.1、取Map中key的值
上面的2.2、FreeMarker的使用方法
中演示的代码就是该例子。
编辑模型数据代码逻辑:
// 1、取Map中key的值
dataModel.put("hello", "this is my first freemarker test.");
模板代码:
hello.ftl
${hello}
2.4.2、取Map中pojo的属性的值
Student对象。属性有:学号、姓名、年龄、家庭住址。
/**
* 测试FreeMarker使用的POJO
* @author chenmingjun
* @date 2018年11月29日
* @version V1.0
*/
public class Student {
private int id;
private String name;
private int age;
private String address;
public Student() {
}
public Student(int id, String name, int age, String address) {
super();
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
// getter方法和setter方法
student.ftl的取值方式:${key.property}
java代码如下:
@Test
public void freeMarkerTest() throws Exception {
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File("C:/my/learn/Java/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl"));
// 第三步:设置模板文件使用的字符集。一般就是utf-8
configuration.setDefaultEncoding("utf-8");
// 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
Template template = configuration.getTemplate("student.ftl");
// 第五步:创建一个模板使用的数据集,可以是POJO也可以是Map。推荐使用是Map。因为Map比较灵活。
Map<Object, Object> dataModel = new HashMap<>();
// 向数据集中添加数据
// 1、取Map中key的值
// dataModel.put("hello", "this is my first freemarker test.");
// 2、取Map中pojo属性的值
Student student = new Student(1, "小明", 18, "北京青年路");
dataModel.put("student", student);
// 第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成的文件路径和文件名。
Writer out = new FileWriter(new File("C:/my/learn/test/temp/out/student.html"));
// 第七步:调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 第八步:关闭流。
out.close();
}
输出的文件如下:
注意:访问map中pojo中的pojo的属性,使用
属性导航
的方式。如果模型数据中设置的值是1000以上,会出现千分位(
1,000
),在取值的时候可以使用?c
去除(${student.id?c}
)。
2.4.3、取Map中List集合中的数据
<#list studentList as student>
${student.id}/${studnet.name}
</#list>
循环使用格式:
<#list 要循环的数据 as 循环后的数据>
</#list>
编辑模型数据代码逻辑:
@Test
public void freeMarkerTest() throws Exception {
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File("C:/my/learn/Java/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl"));
// 第三步:设置模板文件使用的字符集。一般就是utf-8
configuration.setDefaultEncoding("utf-8");
// 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
Template template = configuration.getTemplate("student.ftl");
// 第五步:创建一个模板使用的数据集,可以是POJO也可以是Map。推荐使用是Map。因为Map比较灵活。
Map<Object, Object> dataModel = new HashMap<>();
// 向数据集中添加数据
// 1、取Map中key的值
// dataModel.put("hello", "this is my first freemarker test.");
// 2、取Map中pojo的属性的值
// Student student = new Student(1, "小明", 18, "北京青年路");
// dataModel.put("student", student);
// 3、取Map中List集合中的数据
List<Student> studentList = new ArrayList<>();
studentList.add(new Student(1, "小明1", 18, "北京青年路1"));
studentList.add(new Student(2, "小明2", 19, "北京青年路2"));
studentList.add(new Student(3, "小明3", 20, "北京青年路3"));
studentList.add(new Student(4, "小明4", 21, "北京青年路4"));
studentList.add(new Student(5, "小明5", 22, "北京青年路5"));
dataModel.put("studentList", studentList);
// 第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成的文件路径和文件名。
Writer out = new FileWriter(new File("C:/my/learn/test/temp/out/student.html"));
// 第七步:调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 第八步:关闭流。
out.close();
}
修改模板:
student.ftl
<html>
<head>
<title>FreeMarker测试页面</title>
</head>
<body>
<table border="1">
<tr>
<th>序号</th>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>家庭住址</th>
</tr>
<#list studentList as student>
<#if student_index % 2 == 0>
<tr bgcolor="red">
<#else>
<tr bgcolor="blue">
</#if>
<td>${student_index}</td>
<td>${student.id}</td>
<td>${student.name}</td>
<td>${student.age}</td>
<td>${student.address}</td>
</tr>
</#list>
</table>
</body>
</html>
其中:studentList as student
中的
studentList:为模型设置中的key
student:为集合中的元素的名称(可以任意)
浏览器效果如下:
2.4.4、取Map中Map集合中的数据
编辑模型数据代码逻辑:
@Test
public void freeMarkerTest() throws Exception {
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File("C:/my/learn/Java/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl"));
// 第三步:设置模板文件使用的字符集。一般就是utf-8
configuration.setDefaultEncoding("utf-8");
// 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
Template template = configuration.getTemplate("student.ftl");
// 第五步:创建一个模板使用的数据集,可以是POJO也可以是Map。推荐使用是Map。因为Map比较灵活。
Map<Object, Object> dataModel = new HashMap<>();
// 向数据集中添加数据
// 1、取Map中key的值
// dataModel.put("hello", "this is my first freemarker test.");
// 2、取Map中pojo的属性的值
// Student student = new Student(1, "小明", 18, "北京青年路");
// dataModel.put("student", student);
// 3、取Map中List集合中的数据
// List<Student> studentList = new ArrayList<>();
// studentList.add(new Student(1, "小明1", 18, "北京青年路1"));
// studentList.add(new Student(2, "小明2", 19, "北京青年路2"));
// studentList.add(new Student(3, "小明3", 20, "北京青年路3"));
// studentList.add(new Student(4, "小明4", 21, "北京青年路4"));
// studentList.add(new Student(5, "小明5", 22, "北京青年路5"));
// dataModel.put("studentList", studentList);
// 4、取Map中Map集合中的数据
Map<Object, Object> studentMap = new HashMap<>();
studentMap.put("stu1",new Student(1, "小艺1", 18, "北京物资学院1"));
studentMap.put("stu2",new Student(1, "小艺2", 19, "北京物资学院2"));
studentMap.put("stu3",new Student(1, "小艺3", 20, "北京物资学院3"));
studentMap.put("stu4",new Student(1, "小艺4", 21, "北京物资学院4"));
studentMap.put("stu5",new Student(1, "小艺5", 22, "北京物资学院5"));
dataModel.put("studentMap", studentMap);
// 第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成的文件路径和文件名。
Writer out = new FileWriter(new File("C:/my/learn/test/temp/out/student.html"));
// 第七步:调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 第八步:关闭流。
out.close();
}
修改模板:
student.ftl
<html>
<head>
<title>FreeMarker测试页面</title>
</head>
<body>
<table border="1">
<tr>
<th>序号</th>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>家庭住址</th>
</tr>
<#list studentMap?keys as key>
<#if key_index % 2 == 0>
<tr bgcolor="red">
<#else>
<tr bgcolor="blue">
</#if>
<td>${key_index}</td>
<td>${studentMap[key].id}</td>
<td>${studentMap[key].name}</td>
<td>${studentMap[key].age}</td>
<td>${studentMap[key].address}</td>
</tr>
</#list>
</table>
</body>
</html>
浏览器效果如下:
2.4.5、取循环中的下标
<#list studentList as student>
${student_index}
</#list>
下标从0开始,当然也可以支持运算,比如:${student_index+1}
则输出为1,2,3,…
演示同上2.4.3、取Map中List集合中的数据
所示。
2.4.6、判断
<#if student_index % 2 == 0>
...
<#else>
...
</#if>
演示同上2.4.3、取Map中List集合中的数据
所示。
2.4.7、取Map中的日期类型
编辑模型数据代码逻辑:
// 5、取Map中的日期类型
dataModel.put("date", new Date());
修改模板:
student.ftl
${date} (date是属性名)如果传来的是一个Date类型数据会报错,取出来时需要进行格式化。格式化方式如下:
当前日期:${date?date}<br>
当前时间:${date?time}<br>
当前日期和时间:${date?datetime}<br>
自定义日期格式:${date?string("yyyy/MM/dd HH:mm:ss")}<br>
浏览器效果如下:
当前日期:2018-11-30
当前时间:11:20:20
当前日期和时间:2018-11-30 11:20:20
自定义日期格式:2018/11/30 11:20:20
2.4.8、对Map中的null值的处理
编辑模型数据代码逻辑:
模板:
这是没有问题的,如果代码中注释掉呢?即在模板代码中直接取一个不存在的值(值为null)时会报异常。
所以需要针对空值(null)做处理。
模板的代码修改如下:
student.ftl
方式一:
${test!"说明是null值,作为默认值的我出现了"}
方式二:
${test!""}
方式三:
${test!}
方式四:
${test!}
<br>
// 使用if判断null值<br>
<#if test??>
取的test不是null值
<#else>
取的test是null值
</#if>
2.4.9、include标签
<#include “模板名称”>
先创建一个模板文件:hello.ftl
在student.ftl中使用<#include "hello.ftl"/>引用模板。
2.5、FreeMarker整合spring
为了测试方便,在taotao-item-web工程中的pom.xml引入jar包:
FreeMarker的jar包
注意:还需要spring-context-support的jar包
2.5.1、创建整合spring的配置文件
可以在taotao-item-web工程中的springmvc.xml中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置加载属性文件 -->
<context:property-placeholder location="classpath:resource/resource.properties"/>
<!-- 配置包扫描器,扫描所有需要带@Controller注解的类 -->
<context:component-scan base-package="com.taotao.item.controller" />
<!-- 配置注解驱动 -->
<mvc:annotation-driven />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 配置FreeMarker -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
<!-- 引用dubbo服务 :需要先引入dubbo的约束-->
<dubbo:application name="taotao-item-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<dubbo:reference interface="com.taotao.service.ItemService" id="itemService" />
</beans>
因为测试方法所在的方法是java环境,但是现在需要的是web环境,需要将FreeMarker注入进去,所以需要编写Controller进行测试,而不能编写测试方法了。
2.5.2、Controller
请求的url:/genhtml
参数:无
返回值:OK(String,需要使用@ResponseBody)
业务逻辑:
1、从spring容器中获得FreeMarkerConfigurer对象。
2、从FreeMarkerConfigurer对象中获得Configuration对象。
3、使用Configuration对象获得Template对象。
4、创建模型数据集。设置模型数据一般使用的是Map,也可以使用POJO。
5、创建输出文件的Writer对象。
6、调用模板对象的process方法,生成文件。
7、关闭流。
测试代码如下:
/**
* FreeMarker测试管理的Controller
* @author chenmingjun
* @date 2018年11月30日 下午1:31:29
* @version V1.0
*/
@Controller
public class GenHtmlController {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@RequestMapping("/genHtml")
@ResponseBody
public String genHtml() throws Exception {
// 1、从spring容器中获得FreeMarkerConfigurer对象。
// 2、从FreeMarkerConfigurer对象中获得Configuration对象。
Configuration configuration = freeMarkerConfigurer.getConfiguration();
// 3、使用Configuration对象获得Template对象。
Template template = configuration.getTemplate("hello.ftl");
// 4、创建模型数据集。设置模型数据一般使用的是Map,也可以使用POJO。
Map<Object, Object> dataModel = new HashMap<>();
dataModel.put("hello", "freemarker & spring test");
// 5、创建输出文件的Writer对象。指定输出目录及文件名。
Writer out = new FileWriter(new File("C:/my/learn/test/temp/out/test.html"));
// 6、调用模板对象的process方法,生成文件。
template.process(dataModel, out);
// 7、关闭流。
out.close();
return "OK";
}
}
2.6、商品详情页面静态化
2.6.1、网页静态化-方案分析
输出文件的名称:商品id+“.html”
输出文件的路径:工程外部的任意目录。
网页访问:使用nginx(http服务器)访问静态网页。在此方案下tomcat只有一个作用就是生成静态页面(因为tomcat的强项是处理jsp,对于处理静态资源的访问不擅长)。
工程部署:可以把taotao-item-web部署到多个服务器上。
生成静态页面的时机:商品添加后,生成静态页面。可以使用Activemq,订阅topic方式(监听商品添加事件)。
多台服务器订阅同一个主题(topic) 多台服务器生成的html都是一样。
2.6.2、网页静态化-FreeMarker模板改造
原来使用的是JSP展示页面,我们可以参考原来的JSP页面样式展示,将JSP中的JSTL标签
、@page
等语法,换成freemarker的标签及语法规则。并命名文件名为xxx.ftl,在taotao-item-web工程中的WEB-INF目录下,如下图:
注意:在footer.ftl中,需要处理空值的问题,例如:
2.7、商品详情页面静态化方案实现(Windows版本的nginx)
2.7.1、实现分析
在taotao-item-service工程中消费(接收)消息。
使用ActiveMQ需要导入ActiveMQ的依赖包,在Maven工程中是添加依赖。
在taotao-item-web工程中的pom.xnl文件中添加依赖:
<!-- 配置对ActiveMQ客户端的依赖 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>${activemq.version}</version>
</dependency>
接收消息:
先配置消息相关的配置文件(包括目的地,默认的消息监听容器)
编写listener(实现Messagelistener接口)
获取消息中的商品ID,查询出数据集(模板和数据)
生成静态网页的逻辑:
要做的事情:准备模板文件,准备数据集,数据集通过消息获取商品的id查询数据库获取。
1、配置FreeMarker的配置文件(模板的目录,默认字符集)
2、获取Configuration
3、设置数据集
4、加载模板
5、设置输出目录文件(FileWriter)
6、生成文件,关闭流(输出文件的名称:商品id+".html")
7、部署http服务器(推荐使用nginx)
2.7.2、消息监听器的编写
/**
* 监听消息-生成静态网页
* @author chenmingjun
* @date 2018年11月30日 下午6:07:43
* @version V1.0
*/
public class ItemChangeGenHTMLMessageListener implements MessageListener {
// 注入ItemService
@Autowired
private ItemService itemService;
// 注入freeMarkerConfigurer
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@Value("HTML_OUT_PATH")
private String HTML_OUT_PATH;
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
// 1、从消息中取出商品id
TextMessage textMessage = (TextMessage) message;
try {
String text = textMessage.getText();
if (StringUtils.isNotBlank(text)) {
// 2、通过接收到的消息转换成商品id,根据不商品id查询商品的信息
Long itemId = Long.valueOf(text);
// 调用商品服务查询商品的信息
TbItem tbItem = itemService.getItemById(itemId);
TbItemDesc tbItemDesc = itemService.getItemDescById(itemId);
// 3、使用FreeMarker生成静态页面
this.genHtml("item.ftl", tbItem, tbItemDesc);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 使用FreeMarker生成静态页面的方法
* @param templateName
* @param tbItem
* @param tbItemDesc
* @throws Exception
*/
private void genHtml(String templateName, TbItem tbItem, TbItemDesc tbItemDesc) throws Exception {
// 1、从spring容器中获得FreeMarkerConfigurer对象。
// 2、从FreeMarkerConfigurer对象中获得Configuration对象。
Configuration configuration = freeMarkerConfigurer.getConfiguration();
// 3、使用Configuration对象获得Template对象。
Template template = configuration.getTemplate(templateName);
// 4、创建模型数据集。设置模型数据一般使用的是Map,也可以使用POJO。
Map<Object, Object> dataModel = new HashMap<>();
dataModel.put("item", tbItem);
dataModel.put("itemDesc", tbItemDesc);
// 5、创建输出文件的Writer对象。指定输出目录及文件名。
Writer out = new FileWriter(new File(HTML_OUT_PATH + tbItem.getId() + ".html"));
// 6、调用模板对象的process方法,生成文件。
template.process(dataModel, out);
// 7、关闭流。
out.close();
}
}
编写所需要的属性文件resource.properties
#静态页面的输出路径
HTML_OUT_PATH=C:/my/learn/test/temp/item/
2.7.3、配置springmvc-activemq.xml 和 web.xml
在taotao-item-service工程中
需要配置消费者端的配置文件springmvc-activemq.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.168:61616"></property>
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"></property>
</bean>
<!-- 接收和发送消息时使用的类 -->
<!-- 配置消息的Destination对象 -->
<!-- 这个是话题目的地,一对多的 -->
<bean id="itemAddTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg name="name" value="item-add-topic"></constructor-arg>
</bean>
<!-- 配置消息的消费者 -->
<!-- 先配置监听器 -->
<bean id="itemChangeGenHTMLMessageListener" class="com.taotao.item.listener.ItemChangeGenHTMLMessageListener" />
<!-- 再配置消息监听容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="itemAddTopic" />
<property name="messageListener" ref="itemChangeGenHTMLMessageListener" />
</bean>
</beans>
需要在web.xml中配置加载springmvc-activemq.xml文件:
测试:在后台管理系统中,添加商品,测试能够生成静态页面。
2.7.4、http服务器的安装及配置
使用nginx作为Http服务器,我们暂使用windows版本的nginx。
解压到相应的磁盘,(注意:不要将nginx解压到带有中文目录的目录中,否则启动不起来)
修改/conf目录下的nginx.conf配置如下:
2.7.5、添加JS及样式等静态资源
2.8.5、启动nginx并测试
第一种方式:双击nginx.exe
第二种方式:使用命令:
cd到nginx所在的目录:
启动命令:start nginx.exe
关闭命令:nginx.exe -s stop
刷新配置文件:nginx.exe -s reload
查看任务管理器:如下:
说明启动成功。
浏览器访问地址:http://localhost/item/149265523408245.html
测试成功!
注意:为了后续的学习的方便,这里只是演示如何生成静态页面,因为需要先生成静态页面才能访问,而生成静态页面比较麻烦,所以后面的学习依旧使用动态页面展示商品详情。
3、两天学习小结
通过这两天的学习,现在总结一下:
商品详情页面模块的实现:
通过solr全文搜索找到商品,通过商品id去redis中找当前id的缓存,找不到就去数据库中查找并添加到缓存中。
为了提高redis的高可用,把不常访问的商品从redis缓存中清除:使用定时。
每次点击都会把key的时间重置,当key在他的生命中没有被点击就会从redis中清除,再次访问时再次添加。
两方面影响用户访问速度:
数据库查询
使用缓存
服务器生成html页面
使用freemaker生成静态页面
Freemaker生成静态页面的时机
添加商品后使用activemq广播消息,freemaker监听到消息后去数据库查询商品并生成静态页面。
为什么不去redis中获取商品信息呢?
答:添加商品时还没有及时存储到redis中。因为activemq广播消息到redis接收消息需要一些时间。
为什么不直接使用商品信息,却还要到数据库中查询?
答:因为不在一个项目中,传输数据麻烦,也起不到提高效率的作用,而且修改数据时也要修改静态页面。
redis存储数据库表信息;
Key = 表名:id:字段
Value = 字段值
提高用户的访问速度的两种方案:
一、使用redis缓存
二、网页静态化