8. JSP
8.1 JSP概述
JSP(全称:Java Server Pages):Java 服务端页面
是一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,还可以定义 Java代码的动态内容,也就是 JSP = HTML + Java
示例:hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>JSP, Hello World</h1>
<%
System.out.println("hello.jsp");
%>
</body>
</html>
作用:jsp主要作用是代替Servlet程序回传html页面数据
应用场景:假设需要通过登录界面跳转到主页,主页上需要显示目前登录的用户名。此时就需要用response.getWriter().write写一大堆html界面,显然很不方便。可以将html界面的内容和Servlet里面接收参数的java代码写在jsp中,设置表单提交地址为jsp文件来实现
8.2 JSP入门
-
导入 JSP 依赖
<dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency>
该依赖的
scope
必须设置为provided
,因为 tomcat 中有这个jar包了,所以在打包时不希望将该依赖打进到工程的war包中 -
创建JSP文件:右键 --> New --> JSP/JSPX
-
编写HTML标签和Java代码
8.3 JSP原理
JSP被称为页面,可以写html标签,但为什么可以写Java代码呢?这是因为 JSP 本质上就是一个 Servlet
- 浏览器第一次访问 hello.jsp 页面
- tomcat 会将 hello.jsp 转换为名为
hello_jsp.java
的一个Servlet
- tomcat 再将转换的 servlet 编译成字节码文件
hello_jsp.class
- tomcat 会执行该字节码文件,向外提供服务
在目录 tomcat-demo2\target\tomcat\work\Tomcat\localhost\tomcat-demo2\org\apache\jsp 即可看到转换后的servlet,即hello_jsp.java 和 hello_jsp.class
查看hello_jsp.java
,可以看到该类为
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
...}
查看tomcat源码,可以看到HttpJspBase
又继承自HttpServlet
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
...}
在 hello_jsp.java
中,有一个名为 _jspService()
的方法,该方法就是每次访问 jsp
时自动执行的方法,和 servlet
中的 service
方法一样 ,在里面可以看到大量的 out.writer(...)
代码,用于写html标签
8.4 JSP脚本
JSP脚本书写规则
JSP脚本用于在 JSP页面内定义 Java代码
JSP 脚本分类
-
<%…%>:内容会直接放到 _jspService() 方法之中
-
JSP脚本
<% System.out.println("hello.jsp"); int i = 3; %>
-
对应的hello_jsp.java内容(在 _jspService() 方法里面)
System.out.println("hello.jsp"); int i = 3;
-
-
<%=…%>:内容会放到out.print()中,作为out.print()的参数
-
JSP脚本
<%="mouse"%> <%=i%>
"mouse"会直接出现在页面中
-
对应的hello_jsp.java内容(在 _jspService() 方法里面)
out.print("mouse"); out.print(i);
-
-
<%!…%>:内容会放到_jspService()方法之外,被类直接包含
-
JSP脚本
<%! void show(){} String name = "zhangsan"; %>
"mouse"会直接出现在页面中
-
对应的hello_jsp.java内容(在hello_jsp类中,但在 _jspService() 方法外面)
void show(){ } String name = "zhangsan";
-
案例
-
新建Brand.java实体类
package org.example.pojo; public class Brand { private Integer id;//id主键 private String BrandName;//品牌名称 private String companyName;//企业名称 private Integer ordered;//排序字段 private String description;//描述字段 private Integer status;//状态:0为禁用,1为启用 //这里省略了getter, setter, toString, 构造器方法 }
注意,创建好Brand.java后,要重新启动服务器,从而在target对应的classes里面生成class文件
仅修改jsp文件,不用重启Tomcat -
创建brand.jsp,并将brand.html复制进去
-
修改brand.jsp,插入java代码
<%@ page import="java.util.List" %> <%@ page import="org.example.pojo.Brand" %> <%@ page import="java.util.ArrayList" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% //查询数据库,这里为简单仅用一个List模拟数据库 List<Brand> brands = new ArrayList<Brand>(); brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1)); brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0)); brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1)); %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" value="新增"><br> <hr> <table border="1" cellspacing="0" width="800"> <tr> <th>序号</th> <th>品牌名称</th> <th>企业名称</th> <th>排序</th> <th>品牌介绍</th> <th>状态</th> <th>操作</th> </tr> <%-- 通过for循环代码展示 --%> <% for(int i=0; i<brands.size(); ++i){ Brand brand = brands.get(i); %> <tr align="center"> <td><%=brand.getId()%></td> <td><%=brand.getBrandName()%></td> <td><%=brand.getCompanyName()%></td> <td><%=brand.getOrdered()%></td> <td><%=brand.getDescription()%></td> <% if(brand.getStatus() == 1){ %> <td><%="启用"%></td> <% }else{ %> <td><%="禁用"%></td> <% } %> <td><a href="#">修改</a> <a href="#">删除</a></td> </tr> <% } %> </table> </body> </html>
JSP的缺点
-
书写麻烦:特别是复杂的页面
-
阅读麻烦
-
复杂度高:运行需要依赖于各种环境,JRE,JSP容器,JavaEE…
-
占内存和磁盘:JSP会自动生成.java和.class文件占磁盘,运行的是.class文件占内存
-
调试困难:出错后,需要找到自动生成的.java文件进行调试
-
不利于团队协作:前端人员不会 Java,后端人员不精 HTML
如果页面布局发生变化,前端工程师对静态页面进行修改,然后再交给后端工程师,由后端工程师再将该页面改为 JSP 页面
JSP 已逐渐退出历史舞台,以后开发更多的是使用 HTML + Ajax 来替代
发展历程
- Servlet + JSP:Servlet负责逻辑处理,封装数据;JSP获取数据,遍历展示
- Servlet + html + ajax:ajax 负责 Servlet 和 html 之间的数据沟通
8.5 EL 表达式
EL(全称Expression Language )表达式语言,用于简化 JSP 页面内的 Java 代码
EL 表达式的主要作用是获取数据
EL 表达式语法:${expression}
。例如:${brands} 就是获取域中存储的 key 为 brands 的数据
使用方式
-
编写Servlet代码:获取数据,并转发给jsp
package org.example.web @WebServlet("/demo") public class ServletDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 准备数据(实际上从数据库中获取,这里用一个List模拟数据库) List<Brand> brands = new ArrayList<Brand>(); brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1)); brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0)); brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1)); //2. 存储到request域中 request.setAttribute("brands", brands); //3. 转发 request.getRequestDispatcher("/brand.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
编写brand.jsp代码:接收数据并展示
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${brands} </body> </html>
-
访问页面和结果展示
域对象
- page:当前页面有效
- request:当前请求有效
- session:当前会话有效
- application:当前应用有效
el 表达式获取数据,会依次从这4个域中寻找,直到找到为止。而这四个域对象的作用范围如下图所示
例如: ${brands}
,el 表达式获取数据,会先从page域对象中获取数据,如果没有再到 requet 域对象中获取数据,如果再没有再到 session 域对象中获取,如果还没有才会到 application 中获取数据
8.6 JSTL标签
概述
JSP标准标签库(Jsp Standarded Tag Library) ,使用标签取代JSP页面上的Java代码。如下代码就是JSTL标签
<c:if test="${flag == 1}">
男
</c:if>
<c:if test="${flag == 2}">
女
</c:if>
只介绍两个最常用的标签:<c:forEach>
标签和 <c:if>
标签
JSTL使用方法
-
导入坐标
<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>
-
在JSP页面上引入JSTL标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-
使用标签
<c:if>
常用标签
-
c:if
//在demo对应的Servlet中添加下列语句,并转发给brand.jsp request.setAttribute("status", 0);
<%--在brand.jsp中判断status值--%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ... <body> <%-- c:if --%> <c:if test="${status==1}"> <h1>启用</h1> </c:if> <c:if test="${status==0}"> <h1>禁用</h1> </c:if> </body>
-
c:forEach
- varStatus:index表示从0开始递增,count表示从1开始递增
8.4节的案例中,brand.jsp可以改为
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" value="新增"><br> <hr> <table border="1" cellspacing="0" width="800"> <tr> <th>序号</th> <th>品牌名称</th> <th>企业名称</th> <th>排序</th> <th>品牌介绍</th> <th>状态</th> <th>操作</th> </tr> <c:forEach items="${brands}" var="brand" varStatus="status"> <tr align="center"> <%-- 这里brand.id实际上会 转为getId --%> <td>${status.count}</td> <td>${brand.brandName}</td> <td>${brand.companyName}</td> <td>${brand.ordered}</td> <td>${brand.description}</td> <c:if test="${brand.status==1}"><td>启用</td></c:if> <c:if test="${brand.status!=1}"><td>禁用</td></c:if> <td><a href="#">修改</a> <a href="#">删除</a></td> </tr> </c:forEach> </table> </body> </html>
forEach第二种写法
从0循环到10,变量名是 i ,每次自增1
<hr> <c:forEach begin="0" end="10" step="1" var="i"> <a href="#">${i}</a> </c:forEach>
效果:
9. MVC模式和三层架构
9.1 MVC模式
MVC 是一种分层开发的模式,其中:
-
M:Model,业务模型,处理业务
-
V:View,视图,界面展示
-
C:Controller,控制器,处理请求,调用模型和视图
9.2 三层架构
三层架构是将项目分成了三个层面,分别是 表现层
、业务逻辑层
、数据访问层
。
- 数据访问层:对数据库的CRUD基本操作
- 业务逻辑层:对业务逻辑进行封装,组合数据访问层中基本功能,形成复杂的业务逻辑功能。例如 注册业务功能 ,我们会先调用数据访问层的 selectByName()方法判断该用户名是否存在,如果不存在再调用数据访问层的insert()方法进行数据的添加操作
- 表现层:接收请求,封装数据,调用业务逻辑层,响应数据
而整个流程是,浏览器发送请求,表现层的Servlet接收请求并调用业务逻辑层的方法进行业务逻辑处理,而业务逻辑层方法调用数据访问层方法进行数据的操作,依次返回到serlvet,然后servlet将数据交由 JSP 进行展示
三层架构的每一层都有特有的包名称:
- 表现层:
org.exampe.controller
或者org.exampe.web
- 业务逻辑层:
org.exampe.service
- 数据访问层:
org.exampe.dao
或者org.exampe.mapper
不同的框架是对不同层进行封装的
9.3 MVC 和 三层架构
三层架构 是对 MVC 模式 实现架构的思想
按照要求将不同层的代码写在不同的包下,每一层里功能职责做到单一,将来如果将表现层的技术换掉,而业务逻辑层和数据访问层的代码不需要发生变化
10. 案例
10.1 环境准备
-
创建项目brand_demo,引入依赖坐标和tomcat插件
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!-- 下面坐标视情况添加 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> </dependencies> <!-- 插件 --> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </plugin> </plugins> </build>
-
创建三层架构的包结构
-
检查数据库表,并创建实体类
package org.example.pojo; /** * 品牌实体类 */ public class Brand { private Integer id; private String brandName; private String companyName; private Integer ordered; private String description; private Integer status; //以下省略getter, setter, toString, 构造器方法 }
-
MyBatis环境:mybatis-config.xml,BrandMapper.xml,BrandMapper接口
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="org.example.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <package name="org.example.mapper"/> </mappers> </configuration>
BrandMapper接口
package org.example.mapper; public interface BrandMapper { }
BrandMapper.xml:注意位置在resource中,目录要与BrandMapper对应
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.mapper.BrandMapper"> </mapper>
10.2 前端界面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/brand_demo/selectAllServlet">查询所有</a>
</body>
</html>
brand.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="button" value="新增" id="add"><br>
<hr>
<table border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
<c:forEach items="${brands}" var="brand" varStatus="status">
<tr align="center">
<%-- 这里brand.id实际上会 转为getId --%>
<td>${status.count}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.ordered}</td>
<td>${brand.description}</td>
<c:if test="${brand.status==1}"><td>启用</td></c:if>
<c:if test="${brand.status!=1}"><td>禁用</td></c:if>
<td>
<a href="/brand_demo/selectByIdServlet?id=${brand.id}">修改</a>
<a href="/brand_demo/deleteServlet?id=${brand.id}">删除</a>
</td>
</tr>
</c:forEach>
</table>
<script>
document.getElementById("add").onclick = function (){
location.href = "/brand_demo/addBrand.jsp"
}
</script>
</body>
</html>
addBrand.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加品牌</title>
</head>
<body>
<h3>添加品牌</h3>
<form action="/brand_demo/addServlet" method="post">
品牌名称:<input name="brandName"><br>
企业名称:<input name="companyName"><br>
排序:<input name="ordered"><br>
描述信息:<textarea rows="5" cols="20" name="description"></textarea><br>
状态:
<input type="radio" name="status" value="0">禁用
<input type="radio" name="status" value="1">启用<br>
<input type="submit" value="提交">
</form>
</body>
</html>
update.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改品牌</title>
</head>
<body>
<h3>修改品牌</h3>
<form action="/brand_demo/updateServlet" method="post">
<%--隐藏域,提交id--%>
<input type="hidden" name="id" value="${brand.id}">
品牌名称:<input name="brandName" value="${brand.brandName}"><br>
企业名称:<input name="companyName" value="${brand.companyName}"><br>
排序:<input name="ordered" value="${brand.ordered}"><br>
描述信息:<textarea rows="5" cols="20" name="description">${brand.description} </textarea><br>
状态:
<c:if test="${brand.status == 0}">
<input type="radio" name="status" value="0" checked>禁用
<input type="radio" name="status" value="1">启用<br>
</c:if>
<c:if test="${brand.status == 1}">
<input type="radio" name="status" value="0" >禁用
<input type="radio" name="status" value="1" checked>启用<br>
</c:if>
<input type="submit" value="提交">
</form>
</body>
</html>
10.3 后端代码
需求:点击“查询所有”进入主页面,主页面允许增加、修改、删除
功能:查询所有,增加,修改,删除
实现步骤:
- 先在BrandMapper中定义接口和sql语句
- 然后完成service层代码
- 根据前端界面的提交地址,编写Servlet代码进行数据转发
项目结构
util
SqlSessionFactoryUtils.java
package org.example.util;
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (Exception e){
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
mapper
BrandMapper.java
package org.example.mapper;
public interface BrandMapper {
/**
* 查询所有
* @return
*/
@Select("select * from tb_brand")
@ResultMap("brandResultMap")
List<Brand> selectAll();
/**
* 新增数据
* @param brand
*/
@Insert("insert into tb_brand values(null, #{brandName}, #{companyName}, #{ordered}, #{description}, #{status})")
void add(Brand brand);
/**
* 根据id查找数据
* @param id
* @return
*/
@Select("select * from tb_brand where id = #{id}")
@ResultMap("brandResultMap")
Brand selectById(int id);
/**
* 修改数据
* @param brand
*/
@Update("update tb_brand set brand_name = #{brandName}, company_name = #{companyName}, ordered = #{ordered}, description = #{description}, status = #{status} where id = #{id}")
void update(Brand brand);
/**
* 删除数据
* @param id
*/
@Delete("delete from tb_brand where id = #{id}")
void delete(int id);
}
mapper.xml
BrandMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.BrandMapper">
<resultMap id="brandResultMap" type="org.example.pojo.Brand">
<result column="brand_name" property="brandName"></result>
<result column="company_name" property="companyName"></result>
</resultMap>
</mapper>
service
BrandService.java
package org.example.service;
public class BrandService {
private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();//所有方法都会调用,放到成员变量里面
/**
* 查询所有
* @return
*/
public List<Brand> selectAll(){
//1. 建立连接
SqlSession sqlSession = factory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
//2. 调用BrandMapper.selectAll()
List<Brand> brands = mapper.selectAll();
//3. 要关闭sqlSession
sqlSession.close();
return brands;
}
/**
* 新增数据
* @param brand
*/
public void add(Brand brand){
SqlSession sqlSession = factory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.add(brand);
//格外注意:要提交事务
sqlSession.commit();
sqlSession.close();
}
/**
* 根据id查找数据
* @param id
* @return
*/
public Brand selectById(int id){
SqlSession sqlSession = factory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
Brand brand = mapper.selectById(id);
sqlSession.close();
return brand;
}
/**
* 修改数据
* @param brand
*/
public void update(Brand brand){
SqlSession sqlSession = factory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.update(brand);
sqlSession.commit();
sqlSession.close();
}
/**
* 删除数据
* @param id
*/
public void delete(int id){
SqlSession sqlSession = factory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.delete(id);
sqlSession.commit();
sqlSession.close();
}
}
web
SelectAllServlet.java
//查询所有
package org.example.web;
@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Brand> brands = service.selectAll();
request.setAttribute("brands", brands);
request.getRequestDispatcher("/brand.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
AddServlet.java
//增加数据
package org.example.web;
@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1. 增加数据
Brand brand = new Brand();
brand.setBrandName(request.getParameter("brandName"));
brand.setCompanyName(request.getParameter("companyName"));
brand.setOrdered(Integer.parseInt(request.getParameter("ordered")));
brand.setDescription(request.getParameter("description"));
brand.setStatus(Integer.parseInt(request.getParameter("status")));
service.add(brand);
//2. 转发到查询所有Servlet
request.getRequestDispatcher("/selectAllServlet").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
SelectByIdServlet.java
//修改数据回显时会用到
package org.example.web;
@WebServlet("/selectByIdServlet")
public class SelectByIdServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
Brand brand = service.selectById(Integer.parseInt(id));
request.setAttribute("brand", brand);
request.getRequestDispatcher("/update.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
UpdateServlet.java
package org.example.web;
@WebServlet("/updateServlet")
public class UpdateServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1. 接收表单中的数据
Brand brand = new Brand();
brand.setId(Integer.parseInt(request.getParameter("id")));
brand.setBrandName(request.getParameter("brandName"));
brand.setCompanyName(request.getParameter("companyName"));
brand.setOrdered(Integer.parseInt(request.getParameter("ordered")));
brand.setDescription(request.getParameter("description"));
brand.setStatus(Integer.parseInt(request.getParameter("status")));
service.update(brand);
//2. 转发到查询所有Servlet
request.getRequestDispatcher("/selectAllServlet").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
DeleteServlet.java
package org.example.web;
@WebServlet("/deleteServlet")
public class DeleteServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id = Integer.parseInt(request.getParameter("id"));
service.delete(id);
request.getRequestDispatcher("/selectAllServlet").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
总结
* 查询所有:index.html --> SelectAllServlet.java --> brand.jsp
* 新增:点击“新增” --> addBrand.jsp --> AddServlet.java --> SelectAllServlet.java --> brand.jsp
* 修改:点击“修改” --> SelectByIdServlet.jsp --> update.jsp --> UpdateServlet.java --> SelectAllServlet.java --> brand.jsp
* 删除:点击“删除” --> DeleteServlet --> SelectAllServlet.java --> brand.jsp
如果出现:Result Maps collection does not contain value for … 报错
检查BrandMapper.xml路径是否正确
代码重点
-
实体类和数据库列名不一致时,ResultMap的用法
-
修改数据时,要先回显数据,即显示该数据内容
-
修改数据时,update.jsp里面的隐藏域
11. 会话技术
会话: 用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应
会话跟踪: 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
- 浏览器和服务器之间使用的是HTTP请求来进行数据传输
- HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
- HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
- 请求与请求之间独立后,就无法实现多次请求之间的数据共享
实现会话跟踪的技术
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
Cookie是存储在浏览器端而Session是存储在服务器端
11.1 Cookie
概念
Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问
工作流程
服务端提供了两个Servlet,分别是ServletA和ServletB
浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
服务端ServletA在处理的过程中可以创建一个Cookie对象并将name=zs
的数据存入Cookie
服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享
实际上也是通过request和response来交换数据
但Cookie和Session允许持久化存储。在之前的过程中,例如brand,jsp在收到数据后必须通过请求发送出去,如果不再请求,结束本次服务,那么数据将丢失。通过Cookie和Session,数据会被保存在浏览器本地中,即便本次服务停止,也不会导致数据丢失
Cookie的基本使用
发送Cookie
//AServlet
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//发送Cookie
//1. 创建Cookie对象
//例如当客户端发起一个连接请求时,服务器端将为其创建一个Cookie,并返回给它
Cookie cookie = new Cookie("username", "zs");
//2. 发送Cookie
response.addCookie(cookie);
}
获取Cookie
以localhost为例,获取Cookie只能获取localhost下的所有Cookie,即获取客户端携带的所有Cookie
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Cookie
//1. 获取Cookie数组
//客户端已经获取到Cookie,再发出请求时,服务器端就可以从它的请求中获取Cookie,判断是否是已连接的客户端,如果不是,则拒绝服务
Cookie[] cookies = request.getCookies();
//2. 遍历数组
for(Cookie cookie:cookies){
String name = cookie.getName();
if("username".equals(name)){
String value = cookie.getValue();
System.out.println(name+":"+value);
break;
}
}
}
先访问AServlet.java,再访问BServlet.java,可以看到Cookie被成功获取
Cookie的原理分析
对于Cookie的实现原理是基于HTTP协议的, 其中涉及到HTTP协议中的两个请求头信息:
- 响应头:set-cookie
- 请求头: cookie
对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据
当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头
中添加一行数据Set-Cookie:username=zs
浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie
对应值username=zs
,并将数据存储在浏览器的内存
中
浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加Cookie: username=zs
发送给服务端BServlet
Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组
BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据
访问AServlet,可以看到
再访问BServlet,可以看到
Cookie的使用细节
-
Cookie的存活时间
默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁
Cookie持久化存储:setMaxAge
setMaxAge(int seconds)
-
正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
-
负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁
-
零:删除对应Cookie
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie //1. 创建Cookie对象 Cookie cookie = new Cookie("username", "zs"); //2. 设置存活时间 cookie.setMaxAge(60*60*24*7);//设置为一周,易阅读,需程序计算 //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算 //3. 发送Cookie response.addCookie(cookie); }
此时访问AServlet后,关闭浏览器,再访问BServlet依然可以获取到Cookie
-
-
Cookie存储中文
Cookie不支持中文,如果有中文,报500错误,修改方式如下
-
AServlet发送Cookie:编码
//发送Cookie //1. 创建Cookie对象 String value = "张三"; //URL编码 value = URLEncoder.encode(value, "UTF-8"); Cookie cookie = new Cookie("username", value); //2. 设置存活时间 cookie.setMaxAge(60*60*24*7);//易阅读,需程序计算 //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算 //3. 发送Cookie response.addCookie(cookie);
-
bServlet获取Cookie,解码
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取Cookie //1. 获取Cookie数组 Cookie[] cookies = request.getCookies(); //2. 遍历数组 for(Cookie cookie:cookies){ String name = cookie.getName(); if("username".equals(name)){ String value = cookie.getValue(); //URL解码 value = URLDecoder.decode(value, "UTF-8"); System.out.println(name+":"+value); break; } } }
可以使用encode()函数对字符串进行编码,转换成二进制字节数据
然后用decode()函数将字节解码成字符串 -
11.2 Session
概念
Session:服务端会话跟踪技术:将数据保存到服务端。
- Session是存储在服务端而Cookie是存储在客户端
- 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
- 存储在服务端的数据相比于客户端来说更安全
工作流程
在服务端的AServlet获取一个Session对象,把数据存入其中
在服务端的BServlet获取到相同的Session对象,从中取出数据
就可以实现一次会话中多次请求之间的数据共享了
现在最大的问题是如何保证AServlet和BServlet使用的是同一个Session对象?
Session的基本使用
在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能
HttpSession session = request.getSession();//获取Session对象,使用的是request对象
void setAttribute(String name, Object o);//存储数据到 session 域中
Object getAttribute(String name);//根据 key,获取值
void removeAttribute(String name);//根据 key,删除该键值对
存储数据到Session中
//SessionDemo1
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//存储数据到Session中
//1. 获取Session对象
HttpSession session = request.getSession();
//2. 存储数据
session.setAttribute("username", "zs");
}
获取Session
//SessionDemo2
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从Session中获取数据
//1. 获取Session对象
HttpSession session = request.getSession();
//2. 获取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
这里两个Servlet类中获取的Session对象是同一个
Session的原理分析
Session是基于Cookie实现的
Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个
原理
(1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
(2)demo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器
(3)Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10
当做一个cookie,添加Set-Cookie:JESSIONID=10
到响应头中,并响应给浏览器
(4)浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中
(5)浏览器在同一会话中访问demo2的时候,会把cookie中的数据按照cookie: JESSIONID=10
的格式添加到请求头中并发送给服务器Tomcat
(6)demo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10
的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象
(7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象
展示
SessionDemo1:第一次请求时,在请求中要求获取Session,服务器会响应一个sessionID
SessionDemo2:第二次请求时,会携带这个ID,告诉服务器要找哪一个session
Session的使用细节
-
Session钝化与活化
服务器重启后,Session中的数据是否还在?
结果是不存在,服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了。但实际上我们希望做到就算服务器重启了,数据也能保存下来,从而不影响客户端注意:这里的关闭和重启必须是正常的关闭和重启,即在项目路径如tomcat-demo2下执行 mvn tomcat7:run 和 ctrl+c
经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的
-
钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中
钝化的数据路径为:
项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser
-
活化:再次启动服务器后,从文件中加载数据到Session中
数据加载到Session中后,路径中的
SESSIONS.ser
文件会被删除掉
-
-
Session销毁
-
默认情况下,无操作,30分钟自动销毁
这个值是tomcat中写好的,如果要修改,可以在自己项目的web.xml文件中加入以下配置<session-config> <session-timeout>100</session-timeout> </session-config>
-
调用Session对象的
invalidate()
进行销毁//SessionDemo2.java @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //从Session中获取数据 //1. 获取Session对象 HttpSession session = request.getSession(); //2. 销毁数据 session.invalidate(); //3. 此时再获取数据,报错:HTTP Status 500 - getAttribute: Session already invalidated Object username = session.getAttribute("username"); System.out.println(username); }
-
11.3 小结
-
Cookie 和 Session 都是来完成一次会话内多次请求间数据共享的。
-
区别:
- 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
- 安全性:Cookie不安全,Session安全
- 数据大小:Cookie最大3KB,Session无大小限制
- 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
- 服务器性能:Cookie不占服务器资源,Session占用服务器资源
-
应用场景:
- 购物车:使用Cookie来存储
- 以登录用户的名称展示:使用Session来存储
- 记住我功能:使用Cookie来存储
- 验证码:使用session来存储
-
结论
- Cookie是用来保证用户在未登录情况下的身份识别
- Session是用来保存用户登录后的数据
具体用Cookie还是Session需要根据具体的业务进行具体分析
11.4 案例:登录注册
需求说明:
-
完成用户登录功能,如果用户勾选“记住用户” ,则下次访问登录页面自动填充用户名密码
-
完成注册功能,并实现验证码功能
前端界面
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand_demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg} ${register_msg}</div>
<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.jsp">没有账号?</a>
</div>
</form>
</div>
</body>
</html>
register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎注册</title>
<link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
<div class="reg-content">
<h1>欢迎注册</h1>
<span>已有帐号?</span> <a href="login.html">登录</a>
</div>
<form id="reg-form" action="/brand_demo/registerServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg" >${register_msg}</span>
</td>
</tr>
<tr>
<td>密码</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
</td>
</tr>
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img id="checkCodeImg" src="/brand_demo/checkCodeServlet">
<a href="#" id="changeImg" >看不清?</a>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 册" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
<script>
document.getElementById("changeImg").onclick = function () {
// 如果这里的src只写src = "/brand_demo/checkCodeServlet",那么因为浏览器会缓存静态资源而导致切换不了
// 使用new Data(),是保证每次路径都不一致
document.getElementById("checkCodeImg").src = "/brand_demo/checkCodeServlet?"+new Date().getMilliseconds();
}
</script>
</body>
</html>
brand.jsp
只需要在先前的brand.jsp中添加一句
<h1>${user.username},欢迎您</h1>
登录功能
基于第7.4节的登陆注册案例和第10节的brand_demo案例实现
准备:在brand_demo里添加之前写过的 User.java,UserMapper接口,UserMapper.xml,并对login.jsp和register.jsp做出修改
流程:login.jsp --> LoginServlet.java --> UserService.java --> selectAllServlet.java --> brand.jsp
UserService.java
package org.example.service;
public class UserService {
private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 登录方法login(),它将调用UserMapper里的select方法:根据用户名和密码查询用户对象
* @param username
* @param password
* @return
*/
public User login(String username, String password){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.select(username, password);
session.close();
return user;
}
}
LoginServlet.java
编写LoginServlet.java代码
package org.example.web;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
//2. 调用service查询
User user = service.login(username, password);
//3. 判断
if(user != null){
//登录成功,跳转到查询所有的brand.jsp
//将登录成功后的User对象,存储到session中
HttpSession session = request.getSession();
session.setAttribute("user", user);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/selectAllServlet");//使用重定向,表示这是两次请求
}else{
//登录失败,跳转回login.jsp
request.setAttribute("login_msg", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
在login.jsp中获取登录失败时的提示信息
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
代码重点
- 在LoginServlet.java使用session,从而在登录之后访问其他页面也可以获取到用户信息
“记住我”功能
需求: 如果用户勾选“Remember” ,则下次访问登陆页面自动填充用户名密码
实现:使用Cookie;
- 将用户名和密码写入Cookie中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
- 在页面获取Cookie数据后,设置到用户名和密码框中
- 何时写入Cookie?
- 用户必须登陆成功后才需要写
- 用户必须在登录页面勾选了
记住我
的复选框
LoginServlet.java
在login.jsp中设置提交地址和方式,以及remember复选框
<form action="/brand_demo/loginServlet" method="post" id="form">
...
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
在LoginServlet.java
中存储Cookie
package org.example.web;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
//获取复选框数据
String remember = request.getParameter("remember");
User user = service.login(username, password);
if(user != null){
//登录成功后,判断用户是否勾选记住我(如果登录失败,则不存储Cookie)
if("1".equals(remember)){
//如果没勾选,也不存储。只有2个条件都满足,才存储Cookie
//1. 创建Cookie对象
Cookie c_username = new Cookie("username", username);
Cookie c_password = new Cookie("password", password);
//2. 设置Cookie的存活时间
c_username.setMaxAge(60*60*24*7);//设置存活时间为一周
c_password.setMaxAge(60*60*24*7);
//3. 发送
response.addCookie(c_username);
response.addCookie(c_password);
}
HttpSession session = request.getSession();
session.setAttribute("user", user);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/selectAllServlet");//使用重定向,表示这是两次请求
}else{
request.setAttribute("login_msg", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
login.jsp
在login.jsp
中获取Cookie
<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
这样设置后,如果勾选了“Remember”,那么第二次再访问login.jsp即可自动填充
注册功能
UserService.java
在UserService.java中添加注册方法 register()
/**
* 注册方法register,它将调用UserMapper里的selectByUsername方法验证是否用户名重复,和add方法增加用户
* @param user
* @return
*/
public boolean register(User user){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
//1. 判断用户名是否存在
User u = mapper.selectByUsername(user.getUsername());
if(u == null){
//用户名不存在
mapper.add(user);
session.commit();
}
session.close();
return u == null;
}
RegisterServlet.java
在register.jsp中设置
<form id="reg-form" action="/brand_demo/registerServlet" method="post">
编写RegisterServlet.java代码
package org.example.web;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取用户名和密码
User user = new User();
user.setUsername(request.getParameter("username"));
user.setPassword(request.getParameter("password"));
//2. 调用service注册
boolean flag = service.register(user);
//3. 判断注册成功与否
if(flag){
request.setAttribute("register_msg", "注册成功,请登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}else {
request.setAttribute("register_msg", "用户名已存在");
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
在登录界面login.jsp中设置
<div id="errorMsg">${login_msg} ${register_msg}</div>
...
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.jsp">没有账号?</a>
</div>
在注册界面register.jsp中设置
<span id="username_err" class="err_msg" >${register_msg}</span>
编写完成后,访问login.jsp,再点击“没有账户?”,即可进行注册
验证码功能 - 展示
需求:生成验证码图片,点击图片切换验证码
- 验证码就是使用Java代码生成的一张图片
- 验证码的作用: 防止机器自动注册,攻击服务器
CheckCodeUtil.java
用于生成图片验证码,这里只需了解里面的outputVerifyImage()
方法
package org.example.util;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
import java.util.Random;
/**
* 生成验证码工具类
*/
public class CheckCodeUtil {
public static final String VERIFY_CODES = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static Random random = new Random();
//测试方法,将生成"e://a.jpg"验证码图片
public static void main(String[] args) throws IOException {
OutputStream fos = new FileOutputStream("d://a.jpg");
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);
System.out.println(checkCode);
}
/**
* 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多)
*
* @param width 图片宽度
* @param height 图片高度
* @param os 输出流
* @param verifySize 数据长度
* @return 验证码数据
* @throws IOException
*/
public static String outputVerifyImage(int width, int height, OutputStream os, int verifySize) throws IOException {
String verifyCode = generateVerifyCode(verifySize);
outputImage(width, height, os, verifyCode);
return verifyCode;
}
/**
* 使用系统默认字符源生成验证码
*
* @param verifySize 验证码长度
* @return
*/
public static String generateVerifyCode(int verifySize) {
return generateVerifyCode(verifySize, VERIFY_CODES);
}
/**
* 使用指定源生成验证码
*
* @param verifySize 验证码长度
* @param sources 验证码字符源
* @return
*/
public static String generateVerifyCode(int verifySize, String sources) {
// 未设定展示源的字码,赋默认值大写字母+数字
if (sources == null || sources.length() == 0) {
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for (int i = 0; i < verifySize; i++) {
verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
}
return verifyCode.toString();
}
/**
* 生成随机验证码文件,并返回验证码值 (生成图片形式,用的较少)
*
* @param w
* @param h
* @param outputFile
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, outputFile, verifyCode);
return verifyCode;
}
/**
* 生成指定验证码图像文件
*
* @param w
* @param h
* @param outputFile
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, File outputFile, String code) throws IOException {
if (outputFile == null) {
return;
}
File dir = outputFile.getParentFile();
//文件不存在
if (!dir.exists()) {
//创建
dir.mkdirs();
}
try {
outputFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outputFile);
outputImage(w, h, fos, code);
fos.close();
} catch (IOException e) {
throw e;
}
}
/**
* 输出指定验证码图片流
*
* @param w
* @param h
* @param os
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
int verifySize = code.length();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建颜色集合,使用java.awt包下的类
Color[] colors = new Color[5];
Color[] colorSpaces = new Color[]{
Color.WHITE, Color.CYAN,
Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.YELLOW};
float[] fractions = new float[colors.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
fractions[i] = rand.nextFloat();
}
Arrays.sort(fractions);
// 设置边框色
g2.setColor(Color.GRAY);
g2.fillRect(0, 0, w, h);
Color c = getRandColor(200, 250);
// 设置背景色
g2.setColor(c);
g2.fillRect(0, 2, w, h - 4);
// 绘制干扰线
Random random = new Random();
// 设置线条的颜色
g2.setColor(getRandColor(160, 200));
for (int i = 0; i < 20; i++) {
int x = random.nextInt(w - 1);
int y = random.nextInt(h - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
}
// 添加噪点
// 噪声率
float yawpRate = 0.05f;
int area = (int) (yawpRate * w * h);
for (int i = 0; i < area; i++) {
int x = random.nextInt(w);
int y = random.nextInt(h);
// 获取随机颜色
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}
// 添加图片扭曲
shear(g2, w, h, c);
g2.setColor(getRandColor(100, 160));
int fontSize = h - 4;
Font font = new Font("Algerian", Font.ITALIC, fontSize);
g2.setFont(font);
char[] chars = code.toCharArray();
for (int i = 0; i < verifySize; i++) {
AffineTransform affine = new AffineTransform();
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
}
g2.dispose();
ImageIO.write(image, "jpg", os);
}
/**
* 随机颜色
*
* @param fc
* @param bc
* @return
*/
private static Color getRandColor(int fc, int bc) {
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}
private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}
private static void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}
private static void shearX(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(2);
boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}
}
private static void shearY(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(40) + 10; // 50;
boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}
}
CheckCodeServlet.java
首先在register.jsp中设置验证码图片路径
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img id="checkCodeImg" src="/brand_demo/checkCodeServlet">
<a href="#" id="changeImg" >看不清?</a>
</td>
</tr>
编写CheckCodeServlet代码:生成验证码图片
package org.example.web;
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
还需要实现“看不清”功能,即换一张验证码,只需要在register.jsp中设置
<script>
document.getElementById("changeImg").onclick = function () {
// 如果这里的src只写src = "/brand_demo/checkCodeServlet",那么因为浏览器会缓存静态资源而导致切换不了
// 使用new Data(),是保证每次路径都不一致
document.getElementById("checkCodeImg").src = "/brand_demo/checkCodeServlet?"+new Date().getMilliseconds();
}
</script>
验证码功能 - 校验
需求:判断程序生成的验证码 和 用户输入的验证码 是否一样,如果不一样,则阻止注册
实现:验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入Session中
CheckCodeServlet.java
将验证码存入Session
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
//将验证码存入Session
HttpSession session = request.getSession();
session.setAttribute("checkCodeGen", checkCode);
}
RegisterServlet.java
package org.example.web;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = new User();
user.setUsername(request.getParameter("username"));
user.setPassword(request.getParameter("password"));
// 获取用户输入的验证码
String checkCode = request.getParameter("checkCode");
// 获取程序生成的验证码
HttpSession session = request.getSession();
String checkCodeGen = (String) session.getAttribute("checkCodeGen");
// 在注册之前进行比对
if(!checkCodeGen.equalsIgnoreCase(checkCode)){
//一般忽略大小写
//不允许注册
request.setAttribute("register_msg", "验证码错误");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
boolean flag = service.register(user);
if(flag){
request.setAttribute("register_msg", "注册成功,请登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}else {
request.setAttribute("register_msg", "用户名已存在");
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}