• 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缓存
        二、网页静态化
  • 相关阅读:
    451. Sort Characters By Frequency
    424. Longest Repeating Character Replacement
    68. Text Justification
    44. Wildcard Matching
    160. Intersection of Two Linked Lists
    24. Swap Nodes in Pairs
    93. 递归实现组合型枚举
    98. 分形之城
    97. 约数之和
    96. 奇怪的汉诺塔
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/10046664.html
Copyright © 2020-2023  润新知