jsp实现前后端交互
编辑器:Idea 2020.2.3 notepad++
图形编辑器:mspaint
1 JSP技术概述
JSP全名为Java Server Pages
,中文名叫java服务器页面,其根本是一个简化的Servlet
设计,它是由Sun Microsystems公司倡导、许多公司参与一起建立的一种动态网页技术标准。JSP技术有点类似ASP技术,它是在传统的网页HTML(标准通用标记语言的子集)文件(*.htm
,*.html
)中插入Java程序段(Scriptlet
)和JSP标记(tag
),从而形成JSP文件,后缀名为(*.jsp
)。 用JSP开发的Web应用是跨平台的,既能在Linux下运行,也能在其他操作系统上运行。
它实现了Html语法中的java扩张(以 <%, %>形式)。JSP与Servlet一样,是在服务器端执行的。通常返回给客户端的就是一个HTML文本,因此客户端只要有浏览器就能浏览。
为了能够使用java作为后端并通过servlet实现前后端通信,不妨将所有html改成jsp,jsp中实现表单创建,并上传至后端指定类,后端对数据库进行某些操作是的最终实现前端和数据库的交互。
2 servlet方面
2.1 整体功能
在前端实现表单创建和发送请求,tomcat服务器接收到请求后,使用重写后的doGet(request,response)
方法对发送过来的请求进行处理,由于实现的是登录功能,故而在处理前端请求时需要访问数据库。
在serlet重写过程中需要重写doGet()
方法,故而新建Login
类继承HttpServlet
类,进而改写所需方法。
2.2 tomcat配置
如果想要使用前后端交互功能,那么需要使用服务器,这里选用的是tomcat
服务器。将前端请求发送至tomcat
服务器后,根据设定将请求转至相应的后端程序。
2.2.1 下载与安装
关于tomcat
配置,首先就是需要下载tomcat
服务器。这个很简单,直接去官网,速度还行。
tomcat
官方网址:https://tomcat.apache.org/download-90.cgi
如果下载的是zip
,就是免安装版的,直接解压到喜欢的路径即可。我的路径是D:\jarfile\
2.2.2 项目结构配置
安装完成后就是相应的项目配置了。首先需要做的就是将项目转为web
项目。点击F4
,进入项目结构界面。
点击加号后在下拉箭头选择web
。这样会在原来的项目根目录下生成一个web
目录,可以在这个目录下书写相关的jsp
文件。
ps:其实,除了本身功能,jsp
文件的书写和html文件的书写没有什么本质区别,只是jsp
文件可以用java语言书写部分代码实现更多功能。故而可以把web
作为html
的根目录,在里面进行网页编写,但是网页后缀需要改成jsp
。上图中也有我的项目结构,仅做参考。
其次就是对项目文件标记的说明。如果想要将某些文件夹作为某些用途,需要对此文件夹进行标记。例如,src
是源码文件夹,此文件夹需要创建的是class
、interface
、enum
,但是倘若在没有标记的情况下,编辑器并不会知道这是什么用途的文件夹。就可能会发生无法进行新建java
类的问题。按照下图标记
将src
标记为sources
,out
标记为Excluded
(out是输出文件夹,相当于代码回收场,是一次编译后的结果,再次编译项目是创建的索引不会包含out的),其余的标记可以自行百度,这里不做额外补充
点击Dependencies
再点击加号建立相关的jar
包依赖。点击加号之后会弹出一个下拉菜单,选择里面的library
,光标置于library
上时就会有另一个下拉菜单,选择里面的java
,进而去选择路径,此时的路径就是2.2.1步骤你解压的tomcat
路径,这样就把tomcat
的依赖添加进来啦。至于mysql
jar
包的导入,就按照咱们最原始的jar
包导入就好啦。我存放项目jar包的路径是项目路径下的lib
,然后再“add as library
"就可以啦。
ps:add as library,文件夹右键,里面就有,慢慢找不要慌。
2.2.3 Debug配置
这个时候基本配置就已经完成了,但是还是不能运行的。因为还没有对Debug
就行配置
选择tomcat server
的时候其实选择一个就够了,我选择的是local
,之后就是弹出来一个命名,根据自己口味酌情命名。
点击F4
,进入project structure
,选择artifacts
,然后如图选择就行,顺序是+
->web application
->from module
,再点击以此确定就好了。
截至目前为止,整个项目的配置就完成啦!
2.2.4 web文件夹配置
废话不多说,在解释之前,先贴上源码(解释在源码中了/呲牙/呲牙/呲牙)
<?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">
<!-- 欢迎文件,在项目运行时弹出的第一个界面 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 在服务器上运行的小程序设定 -->
<!-- 一个servlet对应一个servlet-mapping -->
<servlet>
<servlet-name>Login</servlet-name>
<!-- 小程序对应后端路径 -->
<!-- 这里指的是相对路径,src下的com.servlet.login.Login -->
<!-- Login即为我的实现登录的后端类 -->
<servlet-class>com.servlet.login.Login</servlet-class>
</servlet>
<!-- 小程序映射路径 -->
<servlet-mapping>
<servlet-name>Login</servlet-name>
<!-- 这个是编译运行时小程序对应的输出路径,位于out中 -->
<url-pattern>/servlet/login</url-pattern>
</servlet-mapping>
<!-- 与上同 -->
<servlet>
<servlet-name>Register</servlet-name>
<servlet-class>com.servlet.register.Register</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Register</servlet-name>
<url-pattern>/servlet/register</url-pattern>
</servlet-mapping>
</web-app>
如果没有解释清楚的话,可以对照我的项目树状图理解这一部分。
2.3 Login
在Account.jsp
文件中调用了Login
方法,作为登录表单请求的方法。当用户在网页上尝试登录时,请求的表单就会发送至后端,后端就收请求的类是Login
,Login
是HttpServlet
的子类,通过改写HttpServlet
中的doGet
方法,相应前端请求,根据不同的响应结果实现不同的页面跳转。
package com.servlet.login;
import java.io.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mysql.sqlExecute;
//需要说明网络服务器名称
//Account.jsp发送请求到名为Login的网络服务器
@WebServlet("/Login")
public class Login extends HttpServlet {
public Login(){
super();
}
@Override
protected void doGet(HttpServletRequest request,HttpServletResponse response){
try{
response.getWriter().append("Served at: ").append(request.getContextPath());
PrintWriter pw = response.getWriter();
pw.append("sss");
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("账号是:" + username);
System.out.println("密码是:" + password);
sqlExecute se = new sqlExecute();
if(password.equals(se.queryPassword(username))){
System.out.println("登陆成功");
//成功后跳转
response.sendRedirect("index.jsp");
}else{
System.out.println("登陆失败");
//失败后跳转
response.sendRedirect("views/Account.jsp");
}
}catch(IOException e){
e.printStackTrace();
}
}
}
2.4 Register
在Register.jsp
中调用了Register
方法,具体步骤基本和Login
一致,但是执行的数据库操作不同
Login
执行的是查询操作;Register
执行的是插入操作
package com.servlet.register;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import com.mysql.sqlExecute;
//指定网络服务器名称
//Register.jsp中的请求发送到的是名为Register的服务器
@WebServlet("/Register")
public class Register extends HttpServlet {
public Register(){
super();
}
@Override
public void doGet(HttpServletRequest request,HttpServletResponse response){
try{
response.getWriter().append("Served at: ").append(request.getContextPath());
PrintWriter pw = response.getWriter();
pw.append("sss");
String nation = request.getParameter("region");
String telephone = request.getParameter("phone");
System.out.println("国家为:"+nation);
System.out.println("电话为:"+telephone);
sqlExecute se = new sqlExecute();
se.insert(nation,telephone);
response.sendRedirect("index.jsp");
}catch(IOException e){
e.printStackTrace();
}
}
}
3 数据库方面
3.1 整体功能
在servlet配置完成后,前端就可以发送数据请求了,其后就是后端接受同时响应请求。在接受请求时,根据不同的操作对数据库进行不同的操作,例如登录就是查询数据库相关表单,注册就是在数据库相应表单中插入某些数据。在之后的代码优化中,用户修改个人信息即为数据库的update功能。
3.2 sqlDao
这一层是在数据库上的操作,通过sqlExecute
类的获取的Connection
对象conn
连接数据库,并在数据库上进行一系列操作。在这一层,包括查询和更新两个主函数,另外就是查询列名辅助函数,辅助sqlExecute
类中查询指定密码的方法实现。
package com.mysql;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Vector;
import java.sql.ResultSetMetaData;
public class sqlDao {
private Connection conn;
/**
* 构造函数
* */
public sqlDao(Connection n){
this.conn = n;
}
/**
* 更新操作
* */
public void update(String sql){
System.out.println(sql+"已执行");
try{
Statement stmt = conn.createStatement();
stmt.executeUpdate(sql);
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("请连接数据库");
}
}
/**
* 获取列名
* */
public Vector<String> selectTitle(String sql){
Vector<String> temp = new Vector<>();
try{
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//获取列数
ResultSetMetaData rsMd = rs.getMetaData();
int columnCount = rsMd.getColumnCount();
System.out.println(rs.next());
//获取列名
for(int i = 0; i < columnCount; i++){
temp.add(rsMd.getColumnName(i+1));
}
rs.close();
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("请连接数据库");
}
return temp;
}
/**
* 查询操作
* */
public Vector<Vector<String>> select(String sql){
Vector<Vector<String>> r = new Vector<>();
System.out.println(sql + "已执行");
try{
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
int columnCount = rs.getMetaData().getColumnCount();
while(rs.next()){
Vector<String> temp = new Vector<>();
r.add(selectTitle(sql));
for(int i = 0; i < columnCount; i++){
temp.add(rs.getString(i+1));
}
r.add(temp);
}
}catch(SQLException e){
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("请连接数据库");
}
return r;
}
}
3.3 sqlExecute
在这一层,根据名字也能判断出来这是一个与sql
语句相关的类,此类用来执行不同的sql
语句,不同的sql
语句对应sqlDao
里面的各种方法,sqlExecute
类中将引用sqlDao
的方法。另外就是,sqlExecute
还控制数据库的连接。
package com.mysql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DriverManager;
import java.util.Vector;
public class sqlExecute {
private static String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true";
private static String driver = "com.mysql.cj.jdbc.Driver";
private static String user;
private static String password;
private static Connection conn;
private int max_id;
//构造函数
public sqlExecute(){
this.user = "root";
this.password = "159357";
connListener cl = new connListener();
//在构造sqlExecute对象时连接数据库
cl.connect();
}
//连接监听
private class connListener{
public void connect(){
try{
Class.forName(driver);
conn = DriverManager.getConnection(url,user,password);
if(null!=conn){
System.out.println("成功连接数据库");
}
}catch(ClassNotFoundException | SQLException e){
e.getStackTrace();
}
}
}
//查询指定用户的密码
public String queryPassword(String username){
String sql = "select * from users where username = '" + username +"';";
sqlDao sd = new sqlDao(conn);
Vector<Vector<String>> temp = sd.select(sql);
int i,j,k;
//暂时不考虑重名问题
if(temp.get(1).contains(username)) {
int cow = temp.size();
int col = temp.get(0).size();
//确定用户名所在列
for(k = 0;k < col;k++){
if(temp.get(0).get(k).equals("username")){
break;
}
}
//确定密码所在列
for(j = 0;j < col;j++){
if(temp.get(0).get(j).equals("password")){
break;
}
}
//确定用户名所在行
for(i = 0;i<cow;i++){
if(temp.get(i).get(k).equals(username)){
break;
}
}
return temp.get(i).get(j);
}else{
System.out.println("此用户不存在");
return null;
}
}
//根据注册页面1进行数据库插入操作
public void insert(String nation,String phone){
max_id=findMax()+1;
String sql = "insert into users values(" + max_id + ",'"+nation+"',null,null,'"+phone+"',null);";
sqlDao sd = new sqlDao(conn);
sd.update(sql);
}
//由于userid为主键不可为空,故而插入时需要指定最大id
public int findMax(){
String sql = "select max(userid) as max_id from users;";
sqlDao sd = new sqlDao(conn);
Vector<Vector<String>> r = sd.select(sql);
int mid = Integer.parseInt(r.get(1).get(0));
return mid;
}
}
4 项目整体结构(树状图)
ps:由于图片比较多,目录会显得比较长,我把相关的图片目录都删除了,只要是
C:\LIXIANLEI\THEFOURTHWEEK\XIAOMI商城
│ Xiaomi商城.iml
│
├─.idea
│ │ .gitignore
│ │ misc.xml
│ │ modules.xml
│ │ uiDesigner.xml
│ │ vcs.xml
│ │ workspace.xml
│ │
│ ├─artifacts
│ │ Xiaomi_war_exploded.xml
│ │
│ └─libraries
│ mysql_connector_java_8_0_21.xml
│
├─doc
│ │ tree.txt
│ │ tree1.txt
│ │ 前后端交互.md
│ │
│ └─img
│ Debug配置-1.png
│ Debug配置-2.png
│ Debug配置-3.png
│ 项目配置-1.png
│ 项目配置-2.png
│ 项目配置-3.png
│
├─lib
│ mysql-connector-java-8.0.21.jar
│
├─out
│ ├─artifacts
│ │ └─Xiaomi_war_exploded
│ │ │ index.jsp
│ │ │
│ │ ├─.idea
│ │ │ ├─artifacts
│ │ │ └─libraries
│ │ ├─css
│ │ │ Account.css
│ │ │ base.css
│ │ │ index.css
│ │ │ load.css
│ │ │ register.css
│ │ │ reset.css
│ │ │ style.css
│ │ │
│ │ ├─img
│ │ │
│ │ ├─js
│ │ ├─lib
│ │ ├─META-INF
│ │ │ context.xml
│ │ │
│ │ ├─src
│ │ │ └─com
│ │ │ └─servlet
│ │ │ └─login
│ │ ├─views
│ │ │ Account.jsp
│ │ │ Register.jsp
│ │ │
│ │ ├─web
│ │ │ ├─css
│ │ │ ├─img
│ │ │ │ └─Router
│ │ │ ├─js
│ │ │ ├─META-INF
│ │ │ ├─views
│ │ │ └─WEB-INF
│ │ └─WEB-INF
│ │ │ web.xml
│ │ │
│ │ ├─classes
│ │ │ │ Account.jsp
│ │ │ │ Register.jsp
│ │ │ │
│ │ │ └─com
│ │ │ ├─mysql
│ │ │ │ sqlDao.class
│ │ │ │ sqlExecute$1.class
│ │ │ │ sqlExecute$connListener.class
│ │ │ │ sqlExecute.class
│ │ │ │
│ │ │ └─servlet
│ │ │ ├─login
│ │ │ │ Login.class
│ │ │ │
│ │ │ └─register
│ │ │ Register.class
│ │ │
│ │ └─lib
│ │ mysql-connector-java-8.0.21.jar
│ │
│ └─production
│ └─Xiaomi商城
│ │ Account.jsp
│ │ Register.jsp
│ │
│ └─com
│ ├─mysql
│ │ sqlDao.class
│ │ sqlExecute$1.class
│ │ sqlExecute$connListener.class
│ │ sqlExecute.class
│ │
│ └─servlet
│ ├─login
│ │ Login.class
│ │
│ └─register
│ Register.class
│
├─src
│ └─com
│ ├─mysql
│ │ sqlDao.java
│ │ sqlExecute.java
│ │
│ └─servlet
│ ├─login
│ │ Login.java
│ │
│ └─register
│ Register.java
│
└─web
│ index.jsp
│
├─css
│ Account.css
│ base.css
│ index.css
│ load.css
│ register.css
│ reset.css
│ style.css
│
├─img
│
├─js
├─META-INF
│ context.xml
│
├─views
│ Account.jsp
│ Register.jsp
│
└─WEB-INF
web.xml
5 常见问题汇总
下面的问题会出现是笔者一点点磨出来的,正所谓“久病成医”,但是我也没有生过全部的病,如果下面没有你想要的答案,别怪我哟,可以私信我咱们慢慢讨论(好吧,王婆卖瓜了一次)
5.1 404问题
5.1.1 jsp文件存放位置错误
我也是刚接触前后端交互方面编程,但是404和405我已经遇见无数次了。一般有很多种情况,例如找不到相关的资源。首先就是你要确定你新建的jsp
文件不能存放在WEB-INF
文件夹下,就是不能和web.xml
文件放在同一个文件夹。放在web
根目录下或者新建一个目录都可以。网上有相应的博客,这里就不贴链接了。
5.1.2 修改jsp文件后没有修改路径
另外就是在修改过jsp
文件位置后,并没有在其余与此jap
文件相关或者存在跳转的文件中的地址,例如,我的Account.jsp
文件中有两个跳转,但这时我将index.jsp
文件移动到了views
文件夹中,但是没有修改相应的路径,例如我的web.xml文件中欢迎界面就是index.jsp,但是根目录下的index.jsp
已经被我移走了,这样再就运行就会提示404
错误,找不到index.jsp
资源
5.1.3 相对路径问题
如果这两个地方都没有问题,那就是有一个小细节没有注意到。因为对于我们的项目而言。在运行时,前端的根目录就是web
文件夹,故而在书写相对路径的时候需要多留几个心眼。
5.2 点击登录无反应
5.2.1 jsp文件中问题
首先咱们先理清一下思路,首先我们需要在jsp
文件中创建表单,将此表单通过一定的方法发送到指定的网络服务器上,等待相关的网络服务器响应请求。既然是这样,这里面就有几个容易犯错的点。
- 单词拼写错误(不要笑,不要把form拼写成from,这两个词很像的,我有次因为这个调试了一下午,哭死在学习英语和实现前后端交互的路上)
- 登录按钮那一块类型一定不要写成"button",一定要写成"submit",即
type="submit"
- 然后就是看有没有对应的规则限制你的输入,如果有的话,查看你的输入是否符合规则,不符合规则并且没有任何错误提示在代码中说明的话,点击登录也是没有反应的
- 再一个就是检查from表单对应的服务器是否正确,是action属性,例如实现登录功能时,
action="Login"
(按照我的代码哈) - 最后一个就是选择的方法,如果在你的后端代码改写的是doPost方法,那么
method="post"
;如果改写的是doGet方法,那么method="get"
,这也是form表单的属性之一
5.2.2 后端代码问题
。因为对于我们的项目而言。在运行时,前端的根目录就是web
文件夹,故而在书写相对路径的时候需要多留几个心眼。