Java单体应用 - 常用框架 - 06.Spring Web(iot-admin2)-阿里云开发者社区

开发者社区> 开发与运维> 正文

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

简介: 我们继续以上一章节的 iot-admin 为基础,复制一份重命名为 iot-admin2,修改 pom.xml 中 <artifactId>iot-admin2</artifactId>。 接下来我们重构 iot-admin2 项目。

原文地址:http://www.work100.net/training/monolithic-frameworks-spring-web.html
更多教程:光束云 - 免费课程

Spring Web

序号 文内章节 视频
1 项目重构 -
2 Bean的装配方式 -
3 浏览器端存储技术简介 -
4 实例源码 -

请参照如上章节导航进行阅读

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

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.jpg

提升用户体验

现在浏览器中已经存储了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自动跳转到

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

cookie-remember.jpg

4.实例源码

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


上一篇:综合实例

下一篇:Spring MVC


如果对课程内容感兴趣,可以扫码关注我们的 公众号QQ群,及时关注我们的课程更新

wechat_dingyuehao.jpg
qq_group_qrcode.jpg

版权声明:本文中所有内容均属于阿里云开发者社区所有,任何媒体、网站或个人未经阿里云开发者社区协议授权不得转载、链接、转贴或以其他方式复制发布/发表。申请授权请邮件developerteam@list.alibaba-inc.com,已获得阿里云开发者社区协议授权的媒体、网站,在转载使用时必须注明"稿件来源:阿里云开发者社区,原文作者姓名",违者本社区将依法追究责任。 如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developer2020@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

集结各类场景实战经验,助你开发运维畅行无忧

其他文章