JSP与Servlet

1.JSP

1.1 JSP简介

JSP(全称Java Server Pages),是运行在服务端的动态网页开发技术,以java语言为脚本语言,Jsp网页为整个服务器端的java库单元提供了一个接口来服务于HTTP的应用程序。它使用Jsp标签在HTML网页中插入java代码,标签通常以<%开头以%>结束。

1.2 开发环境

下载java JDK并对环境变量进行配置,并下载tomcat服务器,配置相应的环境变量%CATALINA_HOME%。在cmd中用如下语句,开启和停止tomcat服务。

%CATALINA_HOME%\bin\startup.bat //开启
%CATALINA_HOME%\bin\shutdown  //停止

通过访问 http://localhost:8080/ 便可以使用 Tomcat 自带的一些 web 应用了

image.png

Tomcat的目录:bin存放可执行文件(startup.bat shutdown.bat),conf存放配置文件(如server.xml),lib存放tomcat依赖的jar文件,log存放日志文件,temp存放临时文件,webaspps存放可执行的项目(即我们的项目存放地),work存放由jsp翻译成的java以及编译成的class文件。

在Tomcat文件夹下的\webapps\ROOT文件夹下建一个jsp文件,内容如下,打开浏览器访问地址 http://localhost:8080/test.jsp即可看到输出内容tomcat test。

<%@ page contentType="text/html;charset=UTF-8" %> //为了使页面正常显示中文
<%
out.print("tomcat test");
%> 

如果使用IDEA进行项目开发,可参照https://www.jianshu.com/p/315697f8511c进行配置,但是要注意jdk和tomcat版本的匹配。

IDEA运行jsp项目

按照图中所有的过程都显示了finish,打开对应的url,即可看到"Tomcat test"字样。如果过程中遇见问题,除了Jdk和tomcat版本匹配问题,如果卡在编译过程中还需要考虑是否需要更改java.security。

1.3 jsp生命周期

JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet。

jsp生命周期

(1)编译阶段
浏览器请求Jsp页面,Jsp引擎先插是否需要编译该文件,如果需要就将jsp翻译成java ,即将其转换为servlet文件,servlet容器编译servlet源文件,生成servlet类(class文件)。
(2)初始化阶段
加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法——jspInit()方法
(3)执行阶段
当JSP网页完成初始化后,JSP引擎将会调用_jspService()方法。调用与JSP对应的servlet实例的服务方法。该方法对每个request请求产生与之对应的response,如GET、POST、DELETE等。

void _jspService(HttpServletRequest request, HttpServletResponse response){
   // 服务端处理代码
}

(4)销毁阶段
调用与JSP对应的servlet实例的销毁方法——jspDestroy()。初始化方法和销毁方法都可以进行复写。

1.4 jsp语法

脚本程序的语法格式如下,每种语法都有与其对应的xml格式。

<%-- 代码 --%>
<% 代码片段 %>   => <% out.println("你的 IP 地址 " + request.getRemoteAddr()); %>
//xml写法

   代码片段


<%-- 声明 --%>
<%! declaration;%>  =>  <%! int a, b, c; %> 
//xml写法

   代码片段


<%-- 表达式 --%>
<%= 表达式 %> =>  <%= (new java.util.Date()).toLocaleString()%>
//xml写法

   表达式


<%-- 指令--%>
<%@ directive attribute="value" %>
//xml写法


<%-- 条件语句--%>
<% if (day == 1 | day == 7) { %>
      

今天是周末

<% } else { %>

今天不是周末

<% } %> <%-- 循环语句--%> <%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> test
<%}%>

结合一个简单的注册表单,来说明一下上述jsp语法的具体应用。

//test.jsp
用户名:
密码:
年龄:
爱好: 足球 篮球 乒乓球
//deal.jsp
<%
        request.setCharacterEncoding("utf-8");//针对post方式设置编码方式,get方式在xml中设置URIEncoding
        String name=request.getParameter("username");//获取字段名为username的值
        String pwd=request.getParameter("password");
        Integer age=Integer.parseInt(request.getParameter("age"));
        String[] hobbies=request.getParameterValues("hobby");//获取字段名为hobby的多个值(多checkbox)

    %>
    注册成功,信息如下:
姓名:<%=name%>
//表达式形式输出获取的值 密码:<%=pwd%>
年龄:<%=age%>
爱好: <% for(String hobby:hobbies){//代码段循环输出获取的hobby out.print(hobby+" "); } %>

注册页点击注册前后的效果分别如图左和图右所示:


效果图

这个简单的例子并没有对传入的参数进行类型和值得判断,只作为简单demo便于理解。

1.5 隐式对象(九种)

JSP隐式对象也叫内置对象,是不需要new也能使用的自带对象。


JSP所支持的九大隐式对象

request用于请求对象、response用于响应对象,session是会话对象,application是全局对象,pageContext是JSP页面容器,config是配置对象(存放服务器配置信息)、out为输出对象、page输出当前JSP页面对象(相当于java中的this)、exception是异常对象。这里通过demo重点说一下前四个。

(1)request:存储客户端向服务器端发送的请求信息,常用方法如下,demo可见上节jsp语法部分

String getParameter(String name):根据请求的字段名key,返回字段值value
String[] getParameterValues(String name):根据请求的字段名key,返回多个字段值value(checkbox)
void setCharacterEncoding("UTF-8"):设置请求编码
getRequestDispatcher("b.jsp").forward(request.response):请求转发的方式跳转页面A->B
ServletContext getServerContext():获取项目的Servlet Context对象。

(2)response:响应对象,常用方法包含

void addCookie(Cookie cookie):服务器端向客户端增加cooide对象
void sendRedirect(String location) throws IOException:页面跳转的一种方法(重定向)
重定向:地址栏改变,第一次请求数据不保留(将跳转地址响应给客户端,客户端在向跳转地址发送请求(第二次))
void getRequestDispatcher(String location):页面跳转的另一种方法(请求转发)
请求转发:地址栏不变,保留第一次请求时的数据(直接将请求转发给跳转地址)
void setContentType(String type):设置服务器端响应的代码

登录示例:

//index.jsp
 
用户名:
密码:
//deal.jsp
<%
        request.setCharacterEncoding("utf-8");
        String name=request.getParameter("username");
        String pwd=request.getParameter("password");
        if(name.equals("admin")&&pwd.equals("123")){
//          response.sendRedirect("success.jsp");//页面跳转:重定向导致数据丢失
            request.getRequestDispatcher("success.jsp").forward(request,response);//页面跳转:请求转发,可以获取到数据并且地址栏没有改变
        }
        else {
            out.print("用户名或密码错误");
        }
%>
//success.jsp
    登录成功!
欢迎您: <% String name=request.getParameter("username"); out.print(name); %>

(3)Session
Cookie是由服务器端产生,发送给客户端保存,所以存在于客户端,不是内置对象。它相当于本地缓存的作用,可以提高访问服务器端的效率,但安全性较差。它由name=value组成,对应javax.servlet.http.Cookie类。内部包含,public Cookie(String name, String value)、String getName()获取name、 String getValue()获取value、void setMaxAge(int expiry)设置最长有效期,等函数。
服务器用response.addCookie(Cookie cookie)准备给客户端的cookie,页面跳转时同时发送cookie,客户端通过request.getCookie()获取cookie。服务器端增加cookie,利用response对象,客户端获取对象是利用request对象。不能直接获取某一个单独对象,只能一次将所有cookie拿到。

客户端登录,服务器端生成cookie发送给客户端,跳转页面显示相应内容的demo如下:

//index.jsp
  <%
      Cookie cookie1=new Cookie("name","admin");
      Cookie cookie2=new Cookie("password","123");
      response.addCookie(cookie1);
      response.addCookie(cookie2);
      response.sendRedirect("success.jsp");
  %>
//success.jsp
    <%
        Cookie[] cookies=request.getCookies();
        for(Cookie cookie:cookies){
            out.print(cookie.getName()+"--"+cookie.getValue()+"
"); } %>

session则被称为会话,从开启浏览器到关闭页面称为一次会话,所以存在同一会话共享机制(会话结束前,无论在该站点下的任何页面都可以取得session)。当客户端发送请求时,服务器端会产生session对象(用于保存该客户端的信息)和对应唯一的sessionID(用于区分其他的session),然后服务器端会产生一个cookie,并且该cookie的name=JSESSIONID,value=服务器sessionID的值,并在发送响应时传给客户端。 所以客户端的cookie中包含JSESSIONID,和服务器的session一一对应。客户端第二次或者第N次请求服务端时,服务端会先用客户端cookie中的JSESSIONID与服务器端sessionID进行匹配,如果匹配成功(cookie.jsessionID=session.sessionID)说明用户曾登陆过,可以直接默认登录。

Session方法如下

String getID():获取SessionID
boolean isNew():判断是否是新用户(第一次访问)
void invalidate():使session失效(退出登录、注销)
void setAttribute()、Object getAttribute()
void setMaxIncativeInterval(秒数):设置最大有效非活动时间
int getMaxIncativeInterval()
invalidate():使session失效
removeAttribute():使某一个属性失效

登录过后的用户信息被session记录,下次访问页面可以直接读取用户信息,不用登录的demo如下:

//index.jsp
用户名:
密码:
//deal.jsp
<%
   request.setCharacterEncoding("utf-8");
   String name=request.getParameter("username");
   String pwd=request.getParameter("password");
   if(name.equals("admin")&&pwd.equals("123")){
       session.setAttribute("username",name);
       session.setAttribute("password",pwd);
       session.setMaxInactiveInterval(20);
       request.getRequestDispatcher("success.jsp").forward(request,response);
   }
   else{
       response.sendRedirect("index.jsp");
   }
%>
//success.jsp
<%
           String name=(String)session.getAttribute("username");
           if (name!=null){
               out.print(name);
           }
           else{
               response.sendRedirect("index.jsp");
           }

       %>

简而言之,客户端第一次请求服务器端时,如果服务器端发现此请求没有JSESSIONID就会为其创建一个拥有JSESSIONID的cookie。服务器端在第一次响应客户端时,会发送一个JSESSION的cookie。虽然cookie不是内置对象,但服务器端会自动new一个JESSIONID的cookie。如果我们想获得JSESSIONID,可以用如下代码:

<%
            Cookie[] cookies=request.getCookies();
            for (Cookie cookie:cookies){
                if (cookie.getName().equals("JESSIONID")){
                    System.out.println("JESSIONID"+cookie.getValue());
                }
            }
%>

(4)application
application是全局对象,主要方法如下:
String getContextPath():虚拟路径
String getRealPath(String name):虚拟路径所对应的绝对路径

另外,pageContext(也称page对象,当前页面有效)、request(同义词请求有效)、session(同一次会话有效)、application(全局有效/整个项目有效)可以统称为四种范围对象(从小到大排列),这四个对象都有共同的方法:
Object getAttribute(String name)
void setAttribute(String name,Object obj),例如setAttribute("a","b")如果a对象之前不存在,则创建一个a对象,如果a已经存在,就将a的值改为b
void removeAttribute(String name)

1.6 JDBC

JDBC(Java DateBase Connectivity),可以为多种关系型数据库(DBMS,如oracle、mysql、sqlServer等)提供统一的访问方式,用Java来操作数据库。JDBC操控JDBC DriverManager从而可以操作其他数据库驱动程序,进而对数据库进行操作。
JDBC API:提供了各种操作访问接口,
(1)DriverManager(管理jdbc驱动)
(2)Connection(连接)(由DriverManager产生)
(3)Statement和PrepareStatement(增删改.executeUpdate()、查.executeQuery())、CallableSatatement(调用数据库中的存储过程/存储函数)(由Connection产生)
(4)ResultSet(返回的结果集)(由Statement产生),包含next()函数,光标下移判断是否有下一条数据,还有pervious()函数,光标上移。getXXX()函数,获取具体字段值。

JDBC访问数据库:首先导入驱动(加载具体的驱动类)、与数据库建立连接、发送sql并执行、处理结果集(查询)。不同类型数据库对应的驱动如下:

Oracle:ojdbc-x.jar,具体的驱动类为oracle.jdbc.OracleDriver
mySQL:mysql-connector-java-x.jar,具体的驱动类为com.mysql.jdbc.Driver
SqlServer:sqljdbc-x.jar,具体的驱动类com.microsoft.sqlserver.hdbc.SQLServerDriver

JDBC的流程demo如下:

import java.sql.*;

public class JDBCDemo {
   static final String URL="jdbc:oracle:thin:@localhost:1521:ORC";
   static final String USERNAME="scott";
   static final String PWD="tiger";

   public static void update(){//增删改
       //a.导入驱动,加载具体的驱动类
       Connection connection=null;
       Statement stmt=null;
       ResultSet resultSet=null;
       try{
           Class.forName("oracle.jdbc.OracleDriver");
           //b.与数据库建立连接
           connection=DriverManager.getConnection(URL,USERNAME,PWD);


           //c.发送sql并执行
           // (1)Statement方式
           stmt=connection.createStatement();
           String sql="insert into Users values('zhangsan',18)";
           String query="select age,name from Users";
           resultSet=stmt.executeQuery(query);//resultSet为指向行的数据,.getXXX()获取对应类型数据
           int count=stmt.executeUpdate(sql);//执行增删改,返回值表示增删改了几条

           // (2)PrepareStatement方式(sql语句提前,可用?替代值),推荐使用此方法
           //在多运行条件下,sql编译一次,数据被执行N次。而Statement方式方式sql也需要被执行N次
           //此方式可以防止sql注入
           String sql2="insert into Users values(?,?)";
           PreparedStatement pstmt=connection.prepareStatement(sql2);//预编译
           pstmt.setString(1,"zhangsan");//set方法替换占位符
           pstmt.setInt(1,18);
           int count2=pstmt.executeUpdate();

           //(3)CallableSatatement方式(调用存储过程(无返回值,out代替)或存储函数(有返回值))
           //(?,?,?)前两个参数相加,第三个参数为输出参数,输出两个的和
           CallableStatement cstmt=connection.prepareCall("call addTwoNum(?,?,?)");
           cstmt.setInt(1,10);//set处理参数
           cstmt.setInt(2,10);
           cstmt.execute();
           //设置输出参数的类型
           cstmt.registerOutParameter(3,Types.INTEGER);
           int result=cstmt.getInt(3);

           //d.处理结果
           if(count>0){
               System.out.println("操作成功");
           }
           while(resultSet.next()){//next表示下移,判断下移之后是否有元素,有则为true,否则为false
               int age=resultSet.getInt("age");
               String name=resultSet.getString("name");
               //上面两行数据等价于如下两行,此对应编号从1开始,但推荐使用字段名而不是下标查询
               //int age=resultSet.getInt(1);
               //String name=resultSet.getString(2);

               System.out.println(name+""+age);
           }
       }
       catch(ClassNotFoundException | SQLException e){
           e.printStackTrace();
       }
       finally{
           try{
               if(resultSet!=null) resultSet.close();
               if(stmt!=null)stmt.close();//对象.方法,如果对象为空会报空指针
               if(connection!=null) connection.close();
               //先开的后关,后开的先关
           }
           catch (SQLException e){
               e.printStackTrace();
           }
       }
   }
   public static void main(String[] args){
       update();
   }
}

一般对于大型数据,我们可以采取JDBC以字符串形式存储路径,然后IO流读取。对于Oracle的CLOB(大文本数据,如小说,在Mysql中称为text)/BLOB(二进制数据)类型数据,在发送sql时,先通过FileInputStream/File读取,然后通过InputStreamReader对参数处理,但要注意转换流可能需要处理编码方式,最后通过SetCharacterStream进行传值(此步可以指定file.length,设置存储小说的长度)

1.7 JSP访问数据库

结合上节所讲的JDBC内容,本节加入jsp,作为完整的登录判断demo进行演示。本文以Mysql数据库为例,所需驱动可以从官网下载相应版本https://downloads.mysql.com/archives/c-j/,需要注意的是在IDEA中需要引入该驱动,下载该驱动后,加入到工程的Lib中,并且需要加入到tomcat的lib文件下,否则依旧会报错 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver!

//登录页面index.jsp
   
用户名:
密码:

登录页面设置用户名和密码的输入框,点击登录调用deal.jsp,该文件会接收username和password的传参,并且利用JDBC驱动调用数据库进行查询,如果有返回结果则登录成功,否则登录失败。

//处理页面deal.jsp
<%@ page import="java.sql.*" %>
<%
       final String URL="jdbc:mysql://localhost:3306/javaweb";
       final String USERNAME="root";
       final String PWD="";
       Connection connection=null;
       Statement stmt=null;
       ResultSet resultSet=null;
       try{
           Class.forName("com.mysql.jdbc.Driver");
           //b.与数据库建立连接
           connection=DriverManager.getConnection(URL,USERNAME,PWD);
           stmt=connection.createStatement();
           String name=request.getParameter("username");
           String pwd=request.getParameter("password");
           String sql="select * from users where name='"+name+"' and password='"+pwd+"'";
           ResultSet rs=stmt.executeQuery(sql);
           if(rs.next()){
               out.println("登录成功");
           }
           else{
               out.print("登录失败");
           }
       }
       catch(ClassNotFoundException | SQLException e){
           e.printStackTrace();
       }
       finally{
           try{
               if(stmt!=null)stmt.close();//对象.方法,如果对象为空会报空指针
               if(connection!=null) connection.close();
               //先开的后关,后开的先关
           }
           catch (SQLException e){
               e.printStackTrace();
           }
       }
   %>

如果将连接数据库部分的代码放入一个java文件中,将deal.jsp简化成如下形式:

   <%
       String name=request.getParameter("username");
       String pwd=request.getParameter("password");
       LoginDao dao=new LoginDao();
       int result=dao.login(name,pwd);
       if(result>0){
           out.print("登录成功");
       }
       else{
           out.print("登录失败");
       }
   %>

而java部分代码类LoginDao部分代码如下(省略了数据库连接和查询部分,可见上述deal.jsp完整代码自行提炼):

public class LoginDao {
   public int login(String name, String password) {
       String URL = "jdbc:oracle:thin:@localhost:1521:ORC";
       String USERNAME = "scott";
       String PWD = "tiger";
   }
}

这样将Jsp登录操作的代码转移到LoginDao.java,其中的LoginDao.java就称为javaBean

1.8 JavaBean

通过上述的例子我们可以看出,javaBean能够减轻jsp的复杂度,提高代码的复用率(任何的登录操作都可以通过调用该类执行)。所以,如果满足,该类是一个Public修饰的类,且无参数构造,所有的属性都是private,并且提供set/get,如果是boolean则get可以替换成is,那么该类就是JavaBean。
在使用层面,javaBean也可以分为两类,
一种是封装业务逻辑的javaBean(LoginDao.java),将JDBC部分的代码封装进Login.java
一种是封装数据的javaBean(如Person.java、Student.java)。
Login login=new Login(name,pwd)用Login对象封装两个数据(用户名和密码),对应着数据库的表。
封装业务逻辑的javaBean主要用于操作封装数据的javaBean

1.9 EL(表达式语言)

EL,Expression Language,可以替代JSP页面中的java代码。jsp中使用java显示数据需要处理null或进行强制类型转换等,代码和html掺杂,而EL可以较好的解决这些问题。
EL操作符如下:

//点操作符(使用方便)
${域对象.域对象中的属性.属性.属性.级联属性}
如
${requestScope.student.address.schoolAddress}
//中括号操作符(功能强大,可以包含特殊字符(. 、 -),可以获取变量值,可以访问数组)
${requestScope.student["address"]["schoolAddress"]}

//获取map属性实例:
Mapmap=new HashMap<>();
map.put("cn","中国");
map.put("us","美国");
request.setAttribute("map",mao);

${requestScope.map.cn}

EL表达式的隐式对象
(1)作用域访问对象(EL域对象)
*Scope对象:pageScope,requestScope,sessionScope,applicationScope变量用来访问存储在各个作用域层次的变量。举例来说,如果需要显式访问在requestScope层的student变量,就可以参照上述demo。
如果不指定域对象,则会默认根据从小到大的顺序(pageScope->requestScope->sessionScope->applicationScope)依次取值。假如有个session对象,EL写为 ${sessionScope.sessionKey},我们写成 ${sessionKey}也可以找到正确的值。
(2)参数访问对象
参数访问对象用于获取表单数据(如,request.getParameter(等于 ${param})、request.getParameterValues(等于 ${paramValue})、超链接数据等。

   
用户名:
密码:

对于这个表单,我们就可以通过 ${param.username}来获取值。

(3)JSP隐式对象(pageContext)
在JSP中可以通过pageContext获取其他的JSP隐式对象。如果要在EL中使用JSP隐式对象可以通过此方式间接获取。例如 ${pageContext.request},同样,此方式也可以级联获取 ${pageContext.request.serverPort}

1.10 过滤器(拦截器)

在请求和响应时都会处理,需要过滤器放行。要想将一个普通的class变成一个具有特定功能的类(过滤器等),要么继承父类,要么实现一个接口,要么增加一个注解。
过滤器首先要实现一个Filter接口,其init()、destory()原理、执行机制类似Servlet,通过chain.doFilter()放行。要说明的一点是过滤器中的doFilter方法参数时ServletRequest,在Servlet中的方法参数是HttpServletRequest。设置拦截器还要配置相应的web.xml。

//过滤器TestFilter.java
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "TestFilter")
public class TestFilter implements Filter {
    public void destroy() {}

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("拦截请求");
        chain.doFilter(req, resp);//放行
        System.out.println("拦截响应");  
    }

    public void init(FilterConfig config) throws ServletException {}
}
//web.xml
    
        TestServlet
        TestFilter
    
    
        TestServlet
        /TestServlet//拦截TestServlet的请求
    

如果要拦截一切请求,可以写成/*

dispatcher:
还可以通过dispatcher限定拦截的对象。
REQUEST:拦截HTTP请求get\post
FORWARD:只拦截通过请求转发的请求
INCLUDE:只拦截通过request.getRequestDispatcher("").include() 、通过此种方式发出的请求
ERROR:只拦截发出的请求

1.11 监听器

上文提到过四个范围对象:PageContext、request、session、application。监听器主要监听后三个对象。其对应的监听器分别为:request:ServletRequestListenersession:HttpSessionListenerapplication:ServletContextListener,每个监听器各自提供监听开始和结束方法。属于监听对象的创建和销毁。另外,监听器也可以监听对象属性的变更。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.HttpSessionBindingEvent;

@WebListener()
public class TestListener implements ServletRequestListener,ServletContextListener,
        HttpSessionListener, HttpSessionAttributeListener {
    
    public TestListener() {
    }

    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized");
    }

    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed");
    }

    public void sessionCreated(HttpSessionEvent se) {
        /* Session is created. */
        System.out.println("sessionCreated");
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("sessionDestroyed");
    }

    public void attributeAdded(HttpSessionBindingEvent sbe) {
        System.out.println("attributeAdded");
    }

    public void attributeRemoved(HttpSessionBindingEvent sbe) {
        System.out.println("attributeRemoved");
    }

    public void attributeReplaced(HttpSessionBindingEvent sbe) {
        System.out.println("attributeReplaced");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println("requestDestroyed");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println("requestInitialized");
    }
}

根据输出信息发现其执行顺序如下:

contextInitialized
requestInitialized
sessionCreated
requestDestroyed
requestInitialized

另外对于session的监听,还可以根据session的状态来进行
比如,session对象的绑定和解绑采用HttpSessionBindingListener,如session.setAttribute("a",xxx)将对象a绑定到session中。session.removeAttribute("a")将对象a从session中解绑。而session对象的钝化和活化采用HttpSessionActivationListener。钝化可以理解为将内存中的session存入到硬盘中,而活化则是从硬盘到内存的过程。示例如下:

//index.jsp
<%@ page import="test.BeanListener"%>
 <%
      BeanListener bean=new BeanListener();
      session.setAttribute("bean",bean);//绑定
  %>

要注意的是,直接在jsp文件中引入class会报错,需要在classes文件夹下先创建一个文件夹,再逐级引入。

//BeanListener 
package test;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class BeanListener implements HttpSessionBindingListener {
    public BeanListener() {
    }
    @Override
    public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {//绑定
        System.out.println("绑定Bean对象(将Bean对象增加到session域中),绑定的对象为:"+this+",sessionID"+ httpSessionBindingEvent.getSession().getId());
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {//解绑
        System.out.println("解绑Bean对象(将Bean对象从session域中移除),解绑的对象为:"+this+",sessionID"+ httpSessionBindingEvent.getSession().getId());
    }
}

1.12 JNDI

JNDI:java命名与目录接口,将某一个资源(对象),以配置文件(tomcat/conf/context.xml)的形式写入。

//web.xml

//index.jsp
  <%
      Context ctx=new InitialContext();
      String testjndi=(String) ctx.lookup("java:comp/env/jndiName");
      out.print(testjndi);
  %>

数据库连接池
打开、关闭数据库比较消耗性能,所以设置数据库连接池。服务器端的交互不再直接和数据库而是与数据库连接池。常见连接池包含:tomcat-dbcp、dbcp、c3p0、druid。而数据源(javax.sql.DataSource)可以管理数据库连接池。
tomcat-dbcp使用方式如下:
a.类似jndi,需要在context.xml中配置数据库,配置实例如下:


同时需要在web.xml中配置,指定数据源


    student
    javax.sql.DataSource
    Container

之前写JDBC写访问数据库的连接,在运用数据库连接池后,JDBC的connection指向数据源,那么首先我们要获得数据源,通过Context ctx=new InitialContext();,然后查找对应的数据源,ctx.lookup("java:comp/env/jndiName");

2. Servlet

2.1 Servlet简介

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。


Servlet架构

Servlet读取客户端(浏览器)发送得到显示数据(表单等)、隐式(cookie等),然后处理并产生结果,这个过程可能访问数据库,接着发送数据或HTTP响应到客户端(浏览器)

2.2 Servlet生命周期

Servlet 通过调用 init () 方法进行初始化->Servlet 调用 service() 方法来处理客户端的请求->Servlet 通过调用 destroy() 方法终止(结束)。最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。这和上面所述的Jsp生命周期类似。
其中。init 方法和destroy方法被设计成只调用一次。前者在第一次创建 Servlet 时被调用,后者在Servlet生命周期结束时被调用。而对于Service()方法,每次服务器接收到一个Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

2.3 Servlet实例

前端页面,设置一个链接,地址为TestServlet,因为链接的方式为get,为了更好的演示再创建一个空form,method为post。

//index.jsp
    TestServlet

首先编写一个类TestServlet继承HttpServlet,重写doGet()和doPost()方法。

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

public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        System.out.println("doGet");
    }
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        System.out.println("doPost");
    }
}

接着编写web.xml中servlet的映射关系。要注意的是这个TestServlet.java程序放在了src中,而在写Url-pattern的相对路径时,是相对于web文件夹目录的。因为web和src都是根目录,所以Url-pattern为/TestServlet。

//web.xml

        TestServlet
        TestServlet
    
    
        TestServlet
        /TestServlet
    

最终效果如下,点击链接会显示doGet,点击button会显示doPost。


Servletdemo演示图

上面这个demo说明了Servlet的流程,首先发送请求->寻找对应的,然后根据中的去匹配中的然后寻找,将请求交给该执行。

这里还有一个IDEA使用Servlet的技巧:
首先,假如我们想编写一个Servlet类名为TestServlet2.可以直接在src文件夹下new->Create New Servlet。设置好它的名称后,自动生成的代码如下:


IDEA自动生成TestServlet2

并且,它自动帮助我们在class前声明了@WebServlet(name="TestServlet2"),这样我们就不用再去web.xml里面声明,如果我们需要url-pattern,直接在后面加参数声明@WebServlet(name="TestServlet2",urlPatterns = "/TestServlet2"),然后就可以直接调用了。这也是Servlet3.0版本之后的一个区别,即Servlet3.0开始不需要再在web.xml中配置,可以通过在Servlet类的定义处上编写注解@WebServlet的方式调用。这个注解后面还可以增加初始化参数initParams等但是这个注解只隶属于某一个具体的Servlet,因此无法为整个web容器设置初始化参数,所以如果是3.0的方式想要设置web容器的初始化参数,扔需要配置web.xml。
简单来说demo中的这个地址会自动与@WebServlet中的值进行匹配,如果匹配成功就是该注解所对应的类。

2.4 Servlet AP

Servlet由两个软件包组成,一个对应HTTP协议,一个对应非HTTP协议的其他软件包。Servlet适用于任何通信协议。我们所用的一般是位于javax.servlet.http包中的类和接口,是基础的HTTP协议。
Servlet传递链如下:Servlet+ServletConfig+Serializable=>GenericServlet=>httpServlet=>自定义Servlet。
一些重点概念如下:
ServletConfig:接口
ServletContext getServletContext:获取Servlet上下文对象,可以创建application。
String getInitParameter(String name):在当前Servlet范围内,获取名为name的参数值(初始化参数)
ServletContext中的常见方法(application):
getContextPath():相对路径
getRealPath():绝对路径
setAttribute()、getAttribute()
String getInitParameter(String name):在当前Web容器范围内,获取名为name的参数值(初始化参数)

3. JSP与Servlet

3.1 二者关系——MVC模式

Servlet通过HttpServletResponse对象在java代码中动态输出HTML内容,JSP则是在HTML静态页面中嵌入Java代码,Java代码被动态执行后生成HTML内容。鉴于各自特点,Servlet能够很好地组织业务逻辑代码,但其生成的动态HTML可读性差,JSP则避免了这种劣势,但HTML中掺杂了太多业务逻辑。所以二者基于MVC模式进行了结合。MVC(Model-View-Controller),这种架构模式把系统分为三部分,模型、视图和控制器。MVC的运作流程如下:


MVC模式

Web浏览器发送HTTP请求到服务端,被Controller(Servlet)获取并进行处理(例如参数解析、请求转发)Controller(Servlet)调用核心业务逻辑—Model部分,获得结果Controller(Servlet)将逻辑处理结果交给View(JSP),动态输出HTML内容动态生成的HTML内容返回到浏览器显示。

3.2 MVC模式实现登录demo

index.jsp位于View视图层展示登录页,TestServlet(即为controller)实现登录控制,LoginDao位于模型层,实现登录功能。前面说到模型分为封装逻辑的模型(实现功能)和封装数据的模型(实体类)。整体流程为:index.jsp调用TestServlet,TestServlet再调用LoginDao。

//index.jsp登录表单
    
用户名:
密码:

表单的action为TestServlet控制器,该控制器要调用LoginDao模型实现登录功能。登录功能实现的基础就是JDBC,仿照上文所讲的例子,LoginDao的代码如下。可以看到我们需要从View中接收username和password,将二者进行数据封装,封装类称为Login。

//LoginDao.java
import java.sql.*;

public class LoginDao {
    //模型层:用于处理登录(登录功能,即查询数据)
    public static int login(Login login){
        int result=-1;
        final String URL="jdbc:mysql://localhost:3306/javaweb";
        final String USERNAME="root";
        final String PWD="";
        Connection connection=null;
        PreparedStatement pstmt=null;
        ResultSet resultSet=null;
        try{
            Class.forName("com.mysql.jdbc.Driver");
            connection=DriverManager.getConnection(URL,USERNAME,PWD);
            String sql="select count(*) from users where name=? and password=?";
            pstmt=connection.prepareStatement(sql);
            pstmt.setString(1,login.getName());
            pstmt.setString(2,login.getPassword());
            resultSet=pstmt.executeQuery();
            if(resultSet.next()){
                result=resultSet.getInt(1);
            }
            if (result>0){
                return 1;
            }
            else{
                return 0;
            }

        }
        catch (ClassNotFoundException e){
            e.printStackTrace();
            return -1;
        }
        catch (SQLException e){
            e.printStackTrace();
            return -1;
        }
        catch (Exception e){
            e.printStackTrace();
            return -1;
        }
        finally {
            try{
                if (resultSet!=null)resultSet.close();
                if (pstmt!=null)pstmt.close();
                if (connection!=null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            catch (Exception e){
                e.printStackTrace();
            }

        }
    }
}

程序登录可能有多种情况,登录成功和登录不成功。其中不成功的情况分为用户名和密码错误、系统错误。所以因为有三种可能,设置LoginDao函数类型为int,根据返回值判断最终情况。
Login类,有name和password两个参数,分别设置它的构造函数(有参和无参两种),再设置set和get。

//Login.java
public class Login {
    //用于传递用户名和密码,属于封装数据的模型(实体类)
    String name;
    String password;

    public Login(String name, String password) {
        this.name = name;
        this.password = password;
    }
    public Login(){
    }
    public void setName(String name) {
        this.name = name;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }
}

最后完成控制器TestServlet的代码。它先将数据进行封装,然后调用名为LoginDao的Model进行校验,然后接收LoginDao的返回值(该值代表登录结果),最后根据登录结果进行处理。

//TestServlet.java
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

//控制器层(Controller),用于接受view请求,并分发给Model处理
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //接收用户名密码
        req.setCharacterEncoding("utf-8");
        String name=req.getParameter("username");
        String pwd=req.getParameter("password");
        Login login=new Login(name,pwd);//存放用户名密码=>javaBean
        //调用模型层的登录功能
        int result=LoginDao.login(login);
        if(result>0){
            resp.sendRedirect("success.jsp");
        }
        else if (result==0){
            System.out.println("用户名或密码错误");
            resp.sendRedirect("index.jsp");
        }
        else{
            System.out.println("系统错误");
            resp.sendRedirect("index.jsp");
        }
    }
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req,resp);
    }
}

3.3 三层架构

三层架构与MVC设计模式目标一致,都是为了解耦合并提高代码复用,但是二者对于项目的理解角度不同。三层分别为:表示层(USL,User Show Layer:视图层)、业务逻辑层(BLL,Business Logic Layer:Service层)、数据访问层(DAL,Data Access Layer:Dao层)。上层依赖于下层(持有成员变量或需要有下层才能成立)

MVC vs 三层架构

参考资料

Jsp完整参数:
https://www.runoob.com/jsp/jsp-tutorial.html
具体的web.xml配置:
https://www.cnblogs.com/hafiz/p/5715523.html
demo来源:
https://www.bilibili.com/video/av29086718?

你可能感兴趣的:(JSP与Servlet)