day76_淘淘商城项目_09_商品详情页动态展示实现(jsp+redis) + FreeMarker模板引擎入门 + 商品详情页静态化实现(Win版本的nginx作http服务器)_匠心笔记

课程计划

  • 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>
        <!-- 配置对taotao-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,但是我们需要的是Item
        // 方式一:初始化属性,将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就是字段中的内容。
示例如下:
存一条数据:
tb_user:7:id 7
tb_user:7:username zhangsan
tb_user:7:password 123456

存另一条数据:
tb_user:8:id 8
tb_user:8:username lisi
tb_user:8:password 456789

存其余数据同理,不再赘述!
......

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.java需要的属性:


实现类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模板引擎实现网页静态化或者Velocity模板引擎实现网页静态化。案例我们使用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包添加到taotao-item-web工程中。

<!-- 配置对FreeMarker模板引擎的依赖 --!>
<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("D:/learn/Eclipse/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl/test/"));
        // 第三步:设置模板文件使用的字符集。一般就是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("D:/temp/javaee28/test/hello.txt"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    }

2.3、Eclipse中可以使用FreeMarker的插件

将以下文件放置到eclipse的安装目录的plugins中,重启eclipse就可以用了。xxx.ftl文件就会出现高亮及颜色相关的提示。

2.4、FreeMarker模板的语法使用

  注意:我们模板使用的数据集是Map集合,所以以下例子中均与Map集合有关

2.4.1、取Map中key的值

上面的2.2、FreeMarker的使用方法中演示的代码就是该例子。
编辑模型数据的java代码逻辑如下:

    // 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("D:/learn/Eclipse/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl/test/"));
        // 第三步:设置模板文件使用的字符集。一般就是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("D:/temp/javaee28/test/student.html"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    }

输出的文件如下:


注意:访问map中pojo中的pojo的属性,使用属性导航的方式。
如果模型数据中设置的值是1000以上,会出现千分位(1,000),在取值的时候可以使用?c去除(${student.id?c})。

2.4.3、取Map中List集合中的数据

模板文件student2.ftl代码如下:

<#list studentList as student>
    ${student.id}/${studnet.name}
</#list>

循环使用格式:

<#list 要循环的数据 as 循环后的数据>
</#list>

编辑模型数据的java代码逻辑如下:

    @Test
    public void freeMarkerTest() throws Exception 
{
        // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步:设置模板文件所在的路径。
        configuration.setDirectoryForTemplateLoading(new File("D:/learn/Eclipse/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl/test/"));
        // 第三步:设置模板文件使用的字符集。一般就是utf-8
        configuration.setDefaultEncoding("utf-8");
        // 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
        Template template = configuration.getTemplate("student2.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("D:/temp/javaee28/test/student2.html"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    }

修改模板:
student2.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集合中的数据

编辑模型数据的java代码逻辑:

    @Test
    public void freeMarkerTest() throws Exception {
        // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步:设置模板文件所在的路径。
        configuration.setDirectoryForTemplateLoading(new File("D:/learn/Eclipse/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl/test/"));
        // 第三步:设置模板文件使用的字符集。一般就是utf-8
        configuration.setDefaultEncoding("utf-8");
        // 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
        Template template = configuration.getTemplate("student3.ftl");
        // 第五步:创建一个模板使用的数据集,可以是POJO也可以是Map。推荐使用是Map。因为Map比较灵活。
        Map<ObjectObject> 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<ObjectObject> 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("D:/temp/javaee28/test/student3.html"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    }

修改模板:
student3.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中的日期类型

编辑模型数据的java代码逻辑:

    // 5、取Map中的日期类型
    dataModel.put("date"new Date());

修改模板:
student4.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值的处理

编辑模型数据的java代码逻辑如下:


模板文件代码如下:

这是没有问题的,如果代码中注释掉呢?即在模板代码中直接取一个不存在的值(值为null)时会报异常。
所以需要针对空值(null)做处理。
模板代码的格式如下:
方式一:
${test!"说明是null值,作为默认值的我出现了"}

方式二:
${test!""}

方式三:
${test!}

方式四:
${test!}
<br>
// 使用if判断null值<br>
<#if test??>
    取的test不是null
<#else>
    取的test是null
</#if>

2.4.9、include标签

模板代码的格式如下:

<#include “模板名称”>

示例如下:
先创建一个模板文件:hello.ftl,模板中的内容为:${hello}
再创建的另一个模板文件 student5.ftl 中使用 <#include "hello.ftl"/> 引用上述模板。

编辑模型数据的java代码逻辑如下:

    @Test
    public void freeMarkerTest() throws Exception {
        // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步:设置模板文件所在的路径。
        configuration.setDirectoryForTemplateLoading(new File("D:/learn/Eclipse/eclipse-jee-mars-2-win32-x86_64/workspace/taotao/taotao-item-web/src/main/webapp/WEB-INF/ftl/test/"));
        // 第三步:设置模板文件使用的字符集。一般就是utf-8
        configuration.setDefaultEncoding("utf-8");
        // 第四步:使用Configuration对象加载一个模板对象,即创建一个模板对象。
        Template template = configuration.getTemplate("student5.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(2"小艺2"19"北京物资学院2"));
        // studentMap.put("stu3"new Student(3"小艺3"20"北京物资学院3"));
        // studentMap.put("stu4"new Student(4"小艺4"21"北京物资学院4"));
        // studentMap.put("stu5"new Student(5"小艺5"22"北京物资学院5"));
        // dataModel.put("studentMap", studentMap);
        // 5、取Map中的日期类型
        // dataModel.put("date"new Date());
        // 6、对Map中的null值的处理
        // dataModel.put("test""云雀叫了一整天");
        // 第六步:创建一个Writer对象,一般创建一个FileWriter对象,指定生成的文件路径和文件名。
        Writer out = new FileWriter(new File("D:/temp/javaee28/test/student5.html"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    }

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/test" /><!-- 用于测试的目录,实际中需要修改该目录 -->
        <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 GenHTMLTestController {

    @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("D:/temp/javaee28/test/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作http服务器)

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>
</dependency>

接收消息:
  先配置消息相关的配置文件(包括目的地,自定义的消息监听容器)
  编写自定义的消息监听器实现类(实现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 ItemAddGenHTMLMessageListener 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);

                    // 需要等待一下“服务层的消息生产者taotao-manager-service”的事务提交,否则会报空指针异常
                    Thread.sleep(1000);

                    // 调用商品服务查询商品的信息
                    TbItem tbItem = itemService.getItemById(itemId);
                    Item item = new Item(tbItem);
                    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, Item item, 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", item);
        dataModel.put("itemDesc", tbItemDesc);
        // 5、创建输出文件的Writer对象。指定输出目录及文件名。
        Writer out = new FileWriter(new File(HTML_OUT_PATH + item.getId() + ".html"));
        // 6、调用模板对象的process方法,生成文件。
        template.process(dataModel, out);
        // 7、关闭流。
        out.close();
    }
}

编写所需要的属性文件resource.properties

#静态页面的输出路径
HTML_OUT_PATH=D:/temp/javaee28/item/

springmvc.xml中加载所需的属性文件

    <!-- 配置加载属性文件 -->
    <context:property-placeholder location="classpath:resource/resource.properties"/>

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>

    <!-- 接收和发送消息时使用的类 -->
    <!-- 配置消息的消费者 -->
    <!-- 先配置自定义的监听器 -->
    <bean id="itemAddGenHTMLMessageListener" class="com.taotao.item.listener.ItemAddGenHTMLMessageListener" />
    <!-- 再配置消息监听容器 -->
    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destination" ref="itemAddTopic" />
        <property name="messageListener" ref="itemAddGenHTMLMessageListener" />
    </bean>

    <!-- 由于新增商品,对应的商品搜索索引库要同步、要生成订单页面、要同步缓存等,即很多地方要监听商品添加这个事件,所以我们使用Topic -->
    <!-- 这个是话题目的地,一对多的 -->
    <bean id="itemAddTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg name="name" value="item-add-topic"></constructor-arg>
    </bean> 
</beans>

需要在web.xml中配置加载springmvc-activemq.xml文件:


测试:在后台管理系统中,添加商品,测试能够生成静态页面。

2.7.4、http服务器的安装及配置

使用nginx作为Http服务器,演示我们暂使用windows版本的nginx。下次我们使用Linux版本的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、两天学习小结

通过这两天的学习,现在总结一下:

1、商品详情页面模块的实现:
    通过solr全文搜索找到商品,通过商品id去redis中找当前id的缓存,找不到就去数据库中查找并添加到缓存中。
    为了提高redis的高可用,把不常访问的商品从redis缓存中清除:使用定时。
    每次点击都会把key的时间重置,当key在他的生命中没有被点击就会从redis中清除,再次访问时再次添加。

2、两方面影响用户访问速度:
    数据库查询
    使用缓存

3、服务器生成html页面
    使用freemaker生成静态页面

4、Freemaker生成静态页面的时机
    添加商品后使用activemq广播消息,freemaker监听到消息后去数据库查询商品并生成静态页面。
为什么不去redis中获取商品信息呢?
    答:添加商品时还没有及时存储到redis中。因为activemq广播消息到redis接收消息需要一些时间。
为什么不直接使用商品信息,却还要到数据库中查询?
    答:因为不在一个项目中,传输数据麻烦,也起不到提高效率的作用,而且修改数据时也要修改静态页面。

redis存储数据库表信息;
    Key = 表名:id:字段
    Value = 字段值

提高用户的访问速度的两种方案:
    一、使用redis缓存
    二、网页静态化
原文地址:https://www.cnblogs.com/chenmingjun/p/10046664.html