天天看點

Java單體應用 - 常用架構 - 06.Spring Web(iot-admin2)Spring Web

原文位址: http://www.work100.net/training/monolithic-frameworks-spring-web.html 更多教程: 光束雲 - 免費課程

Spring Web

請參照如上

章節導航

進行閱讀

1.項目重構

我們繼續以上一章節的

iot-admin

為基礎,複制一份重命名為

iot-admin2

,修改

pom.xml

<artifactId>iot-admin2</artifactId>

接下來我們重構

iot-admin2

項目:

1.1.實作自動裝載 ApplicationContext

啟動容器時需要自動裝載

ApplicationContext

,Spring 提供的

ContextLoaderListener

就是為了自動裝配

ApplicationContext

的配置資訊

修改 POM

需要在

pom.xml

增加

org.springframework:spring-web

依賴:

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>5.1.8.RELEASE</version>
</dependency>           

完整的

pom.xml

檔案:

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <groupId>net.work100.training.stage2</groupId>
    <artifactId>iot-admin2</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <spring.version>5.2.3.RELEASE</spring.version>
        <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
        <javax.jstl.version>1.2</javax.jstl.version>
        <junit.version>4.12</junit.version>
        <slf4j.version>1.7.25</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${javax.servlet-api.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${javax.jstl.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>
</project>           

配置 web.xml

修改

web.xml

的配置,代碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-context*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>loginController</servlet-name>
        <servlet-class>net.work100.training.stage2.iot.admin.web.controller.LoginController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>loginController</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
</web-app>           

1.2.重構 SpringContext

當一個類實作了這個接口(

ApplicationContextAware

)之後,這個類就可以友善獲得

ApplicationContext

中的所有

bean

換句話說,就是這個類可以直接擷取 Spring 配置檔案中,所有有引用到的 Bean 對象。

重構

SpringContext

類,代碼如下:

package net.work100.training.stage2.iot.admin.commons.context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * <p>Title: SpringContext</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 14:31
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
public class SpringContext implements ApplicationContextAware, DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(SpringContext.class);

    private static ApplicationContext applicationContext;

    /**
     * 使用 ApplicationContext,通過 beanId 擷取執行個體
     *
     * @param beanId
     * @param <T>
     * @return
     */
    public static <T> T getBean(String beanId) {
        assertContextInjected();
        return (T) applicationContext.getBean(beanId);
    }

    /**
     * 使用 ApplicationContext,通過 class 擷取執行個體
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        assertContextInjected();
        return applicationContext.getBean(clazz);
    }

    public void destroy() throws Exception {
        logger.debug("銷毀 ApplicationContext");
        applicationContext = null;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContext.applicationContext = applicationContext;
    }

    private static void assertContextInjected() {
        if (applicationContext == null) {
            throw new RuntimeException("還未在 spring-context.xml 中配置 SpringContext 對象");
        }
    }
}           

1.3.配置

spring-context.xml

spring-context.xml

檔案,增加

springContext

Bean 的定義:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <bean id="springContext" class="net.work100.training.stage2.iot.admin.commons.context.SpringContext"/>

    <!-- DAO -->
    <bean id="userDao" class="net.work100.training.stage2.iot.admin.dao.impl.UserDaoImpl"/>

    <!-- Service -->
    <bean id="userService" class="net.work100.training.stage2.iot.admin.service.impl.UserServiceImpl"/>
</beans>           

springContext

Bean 必須放在最前面

1.4.修改 Bean 執行個體化邏輯

LoginController

類,通過

Class

的方式擷取

Bean

對象,代碼如下:

package net.work100.training.stage2.iot.admin.web.controller;

import net.work100.training.stage2.iot.admin.commons.context.SpringContext;
import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import net.work100.training.stage2.iot.admin.service.impl.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * <p>Title: LoginController</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:28
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
public class LoginController extends HttpServlet {

    private UserService userService = SpringContext.getBean(UserServiceImpl.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String loginId = req.getParameter("loginId");
        String loginPwd = req.getParameter("loginPwd");

        User user = userService.login(loginId, loginPwd);

        // 登入成功
        if (user != null) {
            // 重定向到首頁
            resp.sendRedirect("/main.jsp");
        }
        // 登入失敗
        else {
            // 跳轉回登入頁
            req.setAttribute("message", "登入ID或登入密碼錯誤");
            req.getRequestDispatcher("/index.jsp").forward(req, resp);
        }
    }
}           

UserServiceImpl

beanId

Bean

package net.work100.training.stage2.iot.admin.service.impl;

import net.work100.training.stage2.iot.admin.commons.context.SpringContext;
import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;

/**
 * <p>Title: UserServiceImpl</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:26
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao = SpringContext.getBean("userDao");

    public User login(String loginId, String loginPwd) {
        return userDao.getUser(loginId, loginPwd);
    }
}           

1.5.運作測試

啟動

Tomcat

運作項目,測試登入功能是否正常。

2.Bean的裝配方式

截止目前為止,咱們 Bean 的裝配方式是通過代碼

getBean()

的方式從容器擷取指定的 Bean 執行個體,容器首先會調用 Bean 類的無參構造器,建立空值的執行個體對象。

除了使用

getBean()

的裝配方式外,還可以使用注解的裝配方式。

2.1.容器中 Bean 的作用域

在學習 Bean 的裝配方式之前,我們先了解一下 Bean 的作用域。

當通過 Spring 容器建立一個 Bean 執行個體時,不僅可以完成 Bean 的執行個體化,還可以通過

scope

屬性,為 Bean 指定特定的作用域。

Spring 支援 5 種作用域。

  • singleton:單态模式。即在整個 Spring 容器中,使用

    singleton

    定義的 Bean 将是單例的,隻有一個執行個體。預設為單态的。
  • prototype:原型模式。即每次使用

    getBean

    方法擷取的同一個

    <bean />

    的執行個體都是一個新的執行個體。
  • request:對于每次 HTTP 請求,都将會産生一個不同的 Bean 執行個體。
  • session:對于每個不同的 HTTP session,都将産生一個不同的 Bean 執行個體。
  • global session:每個全局的 HTTP session 對應一個 Bean 執行個體。典型情況下,僅在使用 portlet 叢集時有效,多個 Web 應用共享一個 session。一般應用中,global-session 與 session 是等同的。

注意事項:

  • 對于 scope 的值 request、session 與 global session,隻有在 Web 應用中使用 Spring 時,該作用域才有效。
  • 對于 scope 為

    singleton

    的單例模式,該 Bean 是在容器被建立時即被裝配好了。
  • prototype

    的原型模式,Bean 執行個體是在代碼中使用該 Bean 執行個體時才進行裝配的。

舉例:

我們修改下

spring-context.xml

檔案中對

userService

Bean 的設定,分别為:

<bean id="userService" class="net.work100.training.stage2.iot.admin.service.impl.UserServiceImpl" scope="singleton"/>           
<bean id="userService" class="net.work100.training.stage2.iot.admin.service.impl.UserServiceImpl" scope="prototype"/>           

然後修改下

LoginController

類:

private UserService userService1 = SpringContext.getBean(UserServiceImpl.class);
    private UserService userService2 = SpringContext.getBean(UserServiceImpl.class);
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(userService1 == userService2);
    }           

分别運作測試,通過驗證可見:

bean

scope

配置值

userService1 == userService2

結果
singleton

true

prototype

false

2.2.基于注解的裝配方式

對于 DI 使用注解,将不再需要在 Spring 配置檔案中聲明 Bean 執行個體。Spring 中使用注解, 需要在原有 Spring 運作環境基礎上再做一些改變。

需要在 Spring 配置檔案中配置元件掃描器,用于在指定的基本包中掃描注解。

spring-context.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <context:annotation-config />
    <context:component-scan base-package="net.work100.training.stage2.iot.admin"/>
</beans>           

使用 @Component 注解

需要在類上使用注解

@Component

,該注解的

value

屬性用于指定該

bean

id

值。

比如修改

SpringContext

package net.work100.training.stage2.iot.admin.commons.context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * <p>Title: SpringContext</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 14:31
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
@Component
public class SpringContext implements ApplicationContextAware, DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(SpringContext.class);

    private static ApplicationContext applicationContext;

    /**
     * 使用 ApplicationContext,通過 beanId 擷取執行個體
     *
     * @param beanId
     * @param <T>
     * @return
     */
    public static <T> T getBean(String beanId) {
        assertContextInjected();
        return (T) applicationContext.getBean(beanId);
    }

    /**
     * 使用 ApplicationContext,通過 class 擷取執行個體
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        assertContextInjected();
        return applicationContext.getBean(clazz);
    }

    public void destroy() throws Exception {
        logger.debug("銷毀 ApplicationContext");
        applicationContext = null;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContext.applicationContext = applicationContext;
    }

    private static void assertContextInjected() {
        if (applicationContext == null) {
            throw new RuntimeException("還未在 spring-context.xml 中配置 SpringContext 對象");
        }
    }
}           

Spring 還提供了

3

個功能基本和

@Component

等效的注解:

  • @Repository

    :用于對

    DAO

    實作類進行注解
  • @Service

    Service

  • @Controller

    Controller

比如,分别修改:

UserDaoImpl

package net.work100.training.stage2.iot.admin.dao.impl;

import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

/**
 * <p>Title: UserDaoImpl</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:23
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {

    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    public User getUser(String loginId, String loginPwd) {
        logger.debug("調用方法 getUser(loginId:{}, loginPwd:{})", loginId, loginPwd);

        // 根據 loginId 查詢出使用者資訊
        User user = getUserByLoginId(loginId);
        if (user != null) {
            // 驗證 loginPwd 是否正确(區分大小寫)
            if (user.getLoginPwd().equals(loginPwd)) {
                return user;
            }
        }
        return null;
    }


    /**
     * 擷取模拟的使用者資料
     *
     * @param loginId 登入ID
     * @return
     */
    private User getUserByLoginId(String loginId) {
        // 模拟 DB 存在的使用者資料
        User dbUser = new User();
        dbUser.setUserName("Xiaojun");
        dbUser.setLoginId("admin");
        dbUser.setLoginPwd("admin");

        // 判斷是否存在 loginId 的使用者(忽略大小寫)
        if (dbUser.getLoginId().equalsIgnoreCase(loginId)) {
            logger.info("比對上使用者:{}", dbUser);
            return dbUser;
        }
        logger.warn("未比對任何使用者,将傳回 null");
        return null;
    }
}           

UserServiceImpl

package net.work100.training.stage2.iot.admin.service.impl;

import net.work100.training.stage2.iot.admin.commons.context.SpringContext;
import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import org.springframework.stereotype.Service;

/**
 * <p>Title: UserServiceImpl</p>
 * <p>Description: </p>
 * <p>Url: http://www.work100.net/training/monolithic-frameworks-spring-web.html</p>
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:26
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始建立
 * -----------------------------------------------
 */
@Service(value = "userService")
public class UserServiceImpl implements UserService {

    private UserDao userDao = SpringContext.getBean("userDao");

    public User login(String loginId, String loginPwd) {
        return userDao.getUser(loginId, loginPwd);
    }
}           

然後重新啟動

Tomcat

運作,驗證效果。

因為我們使用的

Spring Web

,是以

@Controller

注解在這裡先不實作,等在

Spring MVC

章節再實作

其它注解

除了上面的講述的注解方式,還有如下的注解方式:

注解 解釋

@Scope

需要在類上使用注解 @Scope,其 value 屬性用于指定作用域。預設為 singleton。

@Value

需要在屬性上使用注解 @Value,該注解的 value 屬性用于指定要注入的值。

@Autowired

需要在域屬性上使用注解 @Autowired,該注解預設使用 按類型自動裝配 Bean 的方式。

@Resource

需要在域屬性上使用注解 @Resource,該注解有一個 name 屬性,可以建立指定的 bean

@PostConstruct

在方法上使用 @PostConstruct 相當于初始化
這些注解方式,我們在後續的課程會陸續介紹。

2.3.注解與 XML 配置的差別

Bean裝配方式 優點 缺點
配置友善,直覺

以寫死的方式寫入到了 Java 代碼中;

其修改是需要重新編譯代碼的。

XML

對其所做修改,無需編譯代碼;

隻需重新開機伺服器即可将新的配置加載。

配置繁瑣
若注解與 XML 同用,XML 的優先級要高于注解。這樣做的好處是,需要對某個 Bean 做修改,隻需修改配置檔案即可。

3.浏覽器端存儲技術簡介

3.1.浏覽器端資料存儲方式

Cookie

Cookie 是指存儲在使用者本地終端上的資料,同時它是與具體的 Web 頁面或者站點相關的。

Cookie 資料會自動在 Web 浏覽器和 Web 伺服器之間傳輸,也就是說 HTTP 請求發送時,會把儲存在該請求域名下的所有 Cookie 值發送給 Web 伺服器,是以伺服器端腳本是可以讀、寫存儲在用戶端的 Cookie 的操作。

LocalStorage

在 HTML5 中,新加入了一個 localStorage 特性,這個特性主要是用來作為本地存儲來使用的,解決了 Cookie 存儲空間不足的問題(Cookie 中每條 Cookie 的存儲空間為 4k),localStorage 中一般浏覽器支援的是 5M 大小,這個在不同的浏覽器中 localStorage 會有所不同。

SessionStorage

SessionStorage 與 LocalStorage 的唯一一點差別就是 LocalStorage 屬于永久性存儲,而 SessionStorage 屬于當會話結束的時候,SessionStorage 中的鍵值對就會被清空。

UserData、GlobalStorage、Google Gear

這三種的使用都有一定的局限性,例如:

  • userData 是 IE 浏覽器專屬,它的容量可以達到 640K,這種方案可靠,不需要安裝額外插件,隻不過它僅在IE下有效
  • globalStorage 适用于 Firefox 2+ 的浏覽器,類似于 IE 的 userData
  • google gear 是谷歌開發出的一種本地存儲技術,需要安裝 Gear 元件

Flash ShareObject(Flash Cookie)

這種方式能能解決上面提到的 Cookie 存儲的兩個弊端,而且能夠跨浏覽器,應該說是目前最好的本地存儲方案。不過,需要在頁面中插入一個 Flash,當浏覽器沒有安裝 Flash 控件時就不能用了。所幸的是,沒有安裝 Flash 的使用者極少。

3.2.實作

記住我

功能

記住我 是指記住

登入ID

,那麼

登入ID

記在哪裡呢?

通過上面所述的浏覽器資料存儲方式的知識,我們使用 Cookie 存儲資料。

建立工具類 CookieUtils

Cookie 是一種用戶端技術,那麼我如何通過 Java 代碼進行操控呢,下面我們來建立一個工具類

CookieUtils

net.work100.training.stage2.iot.admin.commons

下建立包

utils

,然後在其下建立類

CookieUtils

,代碼如下:

package net.work100.training.stage2.iot.admin.commons.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * <p>Title: CookieUtils</p>
 * <p>Description: </p>
 *
 * @author liuxiaojun
 * @date 2020-02-15 11:36
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-15   liuxiaojun     初始建立
 * -----------------------------------------------
 */
public final class CookieUtils {

    /**
     * 得到Cookie的值(不解碼)
     *
     * @param request    請求
     * @param cookieName Cookie名稱
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     * 得到Cookie的值
     *
     * @param request    請求
     * @param cookieName Cookie名稱
     * @param isDecoder  是否解碼
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 得到Cookie的值
     *
     * @param request      請求
     * @param cookieName   Cookie名稱
     * @param encodeString 編碼格式
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 設定Cookie的值 不設定生效時間預設浏覽器關閉即失效,也不編碼
     *
     * @param request     請求
     * @param response    響應
     * @param cookieName  Cookie名稱
     * @param cookieValue Cookie值
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     * 設定Cookie的值 在指定時間内生效,但不編碼
     *
     * @param request      請求
     * @param response     響應
     * @param cookieName   Cookie名稱
     * @param cookieValue  Cookie值
     * @param cookieMaxAge cookie生效的最大秒數
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxAge) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxAge, false);
    }

    /**
     * 設定Cookie的值 不設定生效時間
     *
     * @param request     請求
     * @param response    響應
     * @param cookieName  Cookie名稱
     * @param cookieValue Cookie值
     * @param isEncode    是否編碼
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     * 設定Cookie的值 在指定時間内生效, 編碼參數
     *
     * @param request      請求
     * @param response     響應
     * @param cookieName   Cookie名稱
     * @param cookieValue  Cookie值
     * @param cookieMaxAge cookie生效的最大秒數
     * @param isEncode     是否編碼
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxAge, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxAge, isEncode);
    }

    /**
     * 設定Cookie的值 在指定時間内生效, 編碼參數(指定編碼)
     *
     * @param request      請求
     * @param response     響應
     * @param cookieName   Cookie名稱
     * @param cookieValue  Cookie值
     * @param cookieMaxAge cookie生效的最大秒數
     * @param encodeString 編碼格式
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxAge, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxAge, encodeString);
    }

    /**
     * 删除Cookie帶cookie域名
     *
     * @param request    請求
     * @param response   響應
     * @param cookieName Cookie名稱
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }

    /**
     * 設定Cookie的值,并使其在指定時間内生效
     *
     * @param request      請求
     * @param response     響應
     * @param cookieName   Cookie名稱
     * @param cookieValue  Cookie值
     * @param cookieMaxAge cookie生效的最大秒數
     * @param isEncode     是否編碼
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxAge, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxAge > 0) {
                cookie.setMaxAge(cookieMaxAge);
            }
            if (null != request) {
                // 設定域名的cookie
                String domainName = getDomainName(request);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 設定Cookie的值,并使其在指定時間内生效
     *
     * @param request      請求
     * @param response     響應
     * @param cookieName   Cookie名稱
     * @param cookieValue  Cookie值
     * @param cookieMaxAge cookie生效的最大秒數
     * @param encodeString 編碼格式
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxAge, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxAge > 0)
                cookie.setMaxAge(cookieMaxAge);
            if (null != request) {
                // 設定域名的cookie
                String domainName = getDomainName(request);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到cookie的域名
     *
     * @param request 請求
     * @return
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;
        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            serverName = serverName.toLowerCase();
            serverName = serverName.substring(7);
            final int end = serverName.indexOf("/");
            serverName = serverName.substring(0, end);
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
                // www.xxx.com.cn
                domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                // xxx.com or xxx.cn
                domainName = "." + domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }

        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }
}           

User

類,增加成員變量,代碼如下:

private boolean remember;

    public boolean isRemember() {
        return remember;
    }

    public void setRemember(boolean remember) {
        this.remember = remember;
    }           

index.jsp

設定

記住我

的 Checkbox 名稱:

<input type="checkbox" id="remember" name="remember">           

LoginController

設定2個類變量:

private final static String COOKIE_LOGIN_ID = "loginId";
    private final static String COOKIE_REMEMBER = "remember";           

doPost

方法:

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String loginId = req.getParameter("loginId");
        String loginPwd = req.getParameter("loginPwd");
        boolean remember = "on".equals(req.getParameter("remember"));

        System.out.println(remember);
        User user = userService1.login(loginId, loginPwd);

        // 登入成功
        if (user != null) {
            if (remember) {
                // Cookie 存儲一周
                CookieUtils.setCookie(req, resp, COOKIE_REMEMBER, "on", 7 * 24 * 60 * 60);
                CookieUtils.setCookie(req, resp, COOKIE_LOGIN_ID, loginId, 7 * 24 * 60 * 60);
            } else {
                // 删除 Cookie
                CookieUtils.deleteCookie(req, resp, COOKIE_REMEMBER);
                CookieUtils.deleteCookie(req, resp, COOKIE_LOGIN_ID);
            }
            // 重定向到首頁
            resp.sendRedirect("/main.jsp");
        }
        // 登入失敗
        else {
            // 跳轉回登入頁
            req.setAttribute("message", "登入ID或登入密碼錯誤");
            req.getRequestDispatcher("/login.jsp").forward(req, resp);
        }
    }           

通過如上代碼,當我們登入驗證通過後,并且選擇了

記住我

,浏覽器中将存儲了2個Cookie,如下圖:

Java單體應用 - 常用架構 - 06.Spring Web(iot-admin2)Spring Web

提升使用者體驗

現在浏覽器中已經存儲了2個 Cookie:

loginId

remember

當我們重新登入的時候,頁面需要實作

登入ID

自動填充,

記住我

自動勾選,那麼我們繼續改造。

為了更好的示範,我們将

index.jsp

檔案複制一份重命名為

login.jsp

,然後修改

index.jsp

代碼:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="refresh" content="0; url=/login">
</head>
<body>

</body>
</html>           
如上代碼目的是:使用者通路根位址 http://localhost:8080/> 時,URL自動跳轉到

接下來重寫

LoginController

類中的

doGet

方法,代碼如下:

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        boolean remember = "on".equals(CookieUtils.getCookieValue(req, COOKIE_REMEMBER));
        if (remember) {
            String loginId = CookieUtils.getCookieValue(req, COOKIE_LOGIN_ID);
            req.setAttribute("remember", remember);
            req.setAttribute("loginId", loginId);
        }
        req.getRequestDispatcher("/login.jsp").forward(req, resp);
    }           

最後完善

login.jsp

頁面,

form

表單的代碼如下:

<form action="/login" method="post">
                <div class="input-group mb-3">
                    <input name="loginId" type="text" class="form-control" placeholder="登入ID" value="${loginId}">
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-user"></span>
                        </div>
                    </div>
                </div>
                <div class="input-group mb-3">
                    <input name="loginPwd" type="password" class="form-control" placeholder="登入密碼">
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-lock"></span>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-8">
                        <div class="icheck-primary">
                            <input type="checkbox" id="remember" name="remember" ${remember?"checked":""}>
                            <label for="remember">
                                記住我
                            </label>
                        </div>
                    </div>
                    <!-- /.col -->
                    <div class="col-4">
                        <button type="submit" class="btn btn-primary btn-block">登入</button>
                    </div>
                    <!-- /.col -->
                </div>
            </form>           

重新開機 Tomcat ,運作項目,效果如下:

Java單體應用 - 常用架構 - 06.Spring Web(iot-admin2)Spring Web

4.執行個體源碼

執行個體源碼已經托管到如下位址:

上一篇:

綜合執行個體

下一篇:

Spring MVC
如果對課程内容感興趣,可以掃碼關注我們的

公衆号

QQ群

,及時關注我們的課程更新
Java單體應用 - 常用架構 - 06.Spring Web(iot-admin2)Spring Web
Java單體應用 - 常用架構 - 06.Spring Web(iot-admin2)Spring Web