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緩存
    二、網頁靜態化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM