专栏首页Java系列教程Java单体应用 - 常用框架 - 06.Spring Web(iot-admin2)
原创

Java单体应用 - 常用框架 - 06.Spring Web(iot-admin2)

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 是在容器被创建时即被装配好了。
  • 对于 scope 为 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);
    }

分别运行测试,通过验证可见:

beanscope 配置值

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 属性用于指定该 beanid 值。

比如修改 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 是指存储在用户本地终端上的数据,同时它是与具体的 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,如下图:

Cookie查看 - 光束云 - work100.net

提升用户体验

现在浏览器中已经存储了2个 Cookie:loginIdremember

当我们重新登录的时候,页面需要实现 登录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自动跳转到 http://localhost:8080/login

接下来重写 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 ,运行项目,效果如下:

运行效果 - 光束云 - work100.net

4.实例源

实例源码已经托管到如下地址:

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java入门 - 高级教程 - 05.网络编程

    原文地址:http://www.work100.net/training/java-networking.html

    光束云
  • Java CookieUtils 工具类

    原文地址:http://www.work100.net/tools/code-java/cookie-utils.html

    光束云
  • Java单体应用 - 项目实战(后台) - 01.后台登录

    原文地址:http://www.work100.net/training/monolithic-project-iot-cloud-admin-login.ht...

    光束云
  • Java各种规则引擎

    (2)新建配置文件/src/resources/META-INF/kmodule.xml

    matinal
  • 小程序腾讯云短信接口服务

    做个小程序需要发送验证码,短信接口是腾讯云的。了解官方的sdk和demo发现对于我这种浅层次的人来说太麻烦了,然后就从网上找了一版.

    用户5907943
  • 腾讯云短信接口服务

    做个小程序需要发送验证码,短信接口是腾讯云的。了解官方的sdk和demo发现对于我这种浅层次的人来说太麻烦了,然后就从网上找了一版。短信服务开通可以访问 腾讯...

    聚优云惠
  • mongodb-java-driver基本用法

    1、先下载mongodb-java-driver 目前最新版本是2.9.3 2、下面是基本的CRUD示例代码: 1 package com.cnblogs....

    菩提树下的杨过
  • Spring之动态注册bean

    如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运行了,而这个基础平台是一直都在运行的,所以在新来...

    一灰灰blog
  • AI那么大,我该如何做起——记西浦开放创造实验室workshop: Let&#39;s AI together

    “就像汽车改变交通形态一样,你是要成为一个造车的人?还是学会开车?”这是GPU世界总编辑陈泳翰在西交利物浦开放创造实验室举办的workshop上的开场白。 20...

    GPUS Lady
  • 糟糕,手机密码不保!剑桥大学新研究,用AI算法“监听”手机打字,触摸屏也中招

    你可能听不到,但手指每在屏幕上轻轻点一下,都会发出一个声波。离屏幕不到1厘米远的麦克风,轻轻松松就能记录下来。

    量子位

扫码关注云+社区

领取腾讯云代金券