JSP进阶学习笔记

源码

EL

Expression Launguage:可以代替JSp页面中的Java代码

传统的在jsp代码中显示java代码 需要使用<%%><%=%> 需要处理null 类型转换 代码参杂 —》EL

EL示例

${域对象.域对象中的属性.对象属性的属性......级联属性}
${requestScope.student.sno}、
${requestScope.student.address.homeAddress}
${requestScope.student["address"]['schoolAddress']}

EL操作符:

  • 点操作符. 使用方便
  • [""] 或者['']操作符 功能强大:可以使用特殊字符(. 、 -) 获取变量值int a =10 ; [a]可以获取变量值10 加”“是常量,不加”“是变量,可以访问数组

    点操作符:$(requestScope.my-name)       不可以
    [""]操作符:$(requestScope['my-name'])  可以
    数组:$(requestScope。hobbies[0])
    数组:$(requestScope。hobbies[1])
    

获取 map属性 map

Map<String,Object> map = new HashMap<>();
map.put("ud","df");
map.put("dfs","dsfsdf");
request.setAttribute("map",map);

${requestScope.map.ud}
${requestScope.map["dfs"]

EL

${9>8}、${9 gt 8}

${3>2||3<2>}、${3>2 or 3<2>}

Empty运算符

判断一个值是否null,

不存在或null:true

存在:false

${empty requestScope["my-name"]}

EL表达式的隐式对象

不需要new就能使用的对象,自带的

  1. 作用域访问对象(EL域对象)

    pageScopeScope
    requestScope
    sessionScope
    applicationScope   
    

    如果没指定域对象则从小到大一次查找

  2. 参数访问对象

    获取表单数据\超链接地址栏的值 jsp :

    request.getParameter()request.getParameterValues()
    

    EL

    ${param}
    ${paramValues}
    

    ${param.uname}
    ${paramValues.hobbies[0]}
    ${paramValues.hobbies[1]}
    
  3. JSP隐式对象

    pageContext

    在 jsp中可以使用pageContext获取其他jsp的隐式对象: 因此如果在EL中使用JSP隐式对象,就可以通过pageContext间接获取 ${pageContext.方法名去掉get并且将首字母小写} ${pageContext.request} ${pageContext.session}

    可以使用此方法获取级联对象

    ${pageContext.request.serverPort}
    

JSTl

JSTl比EL更加强大比EL更加强大

需要引入jar包:

jstl.jar    
standard.jar    

复制到lib

引入tablib

在jsp中导包

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

其中prefix=“c”:前缀

核心标签库:通用标签库、条件标签库 迭代标签库

通用标签库

<c:set>赋值

  1. 某个作用域(4个范围对象)中给某个变量赋值

    <c:set var="属性名"   value="属性值"   scope="作用域"/>
    <c:set var="name"   value="aaa"   scope="request"/>
    
  2. 在某个作用域中给某个对象(和map)的属性进行赋值,不需要指定scope属性

    <c:set target="对象" property="对象的属性"  value="赋值" />
    ${requestScope.student.sname}
    <c:set target="${requestScope.student}"  property="sname"  value="fdsdf"/>
    

    给map对象赋值

    <c:set target="${requestScope.countries}" property="cn"  value="中国" />
    

    <c:set/>可以给不存在的变量赋值,不能给不存在的对象赋值

    <c:set var="x" values= "dfg" dcope="request"/>
    

<c:out> :显示

传统EL:${requestScope.student }<br/>
c:out方式:<c:out value="${requestScope.student }"   /><br/>
c:out显示不存在的数据:<c:out value="${requestScope.stu }"   default="zs-23"/><br/>

true:<c:out value='<a href="https://www.baidu.com">百度</a>' default="当value为空的,显示的默认值" escapeXml="true" />      原样输出

false:  <c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="false" />  解析标签

<c:remove >:删除属性

<c:remove var="a" scope="request"/>

选择:

if(boolean)

单重选择

<c:if test="${1>2}"></c:if>
<c:if test="${1>2}" var="result" scope="request" ></c:if>

将运算结果赋值为result并且放入scope域中

多重选择

if else if... esle if... else  /switch
<c:choose>
	<c:when test="${}">   </c:when>
	<c:when test="${}">   </c:when>
	<c:when test="${}">   </c:when>
	<c:otherwise>   </c:otherwise>
</c:choose>

判断语句使用el语法

在使用 test=“” 一定要注意后面是否有空格 例如:

test="${10>2 }"   true
test="${10>2 } "  非true

true+" "==String

任何类型遇到字符串变成字符串

循环(迭代标签库)

for(int i=0;i<=5;i++)
<c:forEach  begin="0"  end="5" step="1"  varStatus="status">
    ${status.index } 第几次
        test....
</c:forEach>
for(String str:names)
<c:forEach  var="student" items="${requestScope.students }" >
    ${student.sname }-${student.sno }
</c:forEach>

过滤器:

实现一个Filter接口

init()、destroy() 原理、执行时机 同Servlet

//要想将 一个普通的class 编程一个 具有特定功能的类(过滤器、拦截....),要么继承 父类、要么实现一个接口、要么增加一个注解
public class MyFilter implements Filter {//过滤器
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter..init..");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("拦截请求......");
        chain.doFilter(request, response);//放行
        System.out.println("拦截响应......");
    }
    @Override
    public void destroy() {
        System.out.println("filter..destroy..");
    }
}

配置过滤器,类似servlet

 <!-- 配置过滤器 -->
<filter>
    <filter-name>MyServlet</filter-name>
    <filter-class>org.student.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/MyServlet</url-pattern>
</filter-mapping>

通过doFilter()处理拦截,并且通过chain.doFilter(request, response);放行

filter映射

只拦截 访问MyServlet的请求

<url-pattern>/MyServlet</url-pattern>

拦截一切请求(每一次访问 都会被拦截)

<url-pattern>/*</url-pattern>

通配符

dispatcher请求方式:

REQUEST:拦截HTTP请求 get post
FORWARD:只拦截 通过 请求转发方式的请求
INCLUDE:只拦截拦截通过 request.getRequestDispatcher("").include()  、通过<jsp:include page="..." />此种方式发出的请求
ERROR:只拦截<error-page>发出的请求

过滤器中doFilter方法参数:ServletRequest

在Servlet中的方法参数:HttpServletRequest

<filter-mapping>
    <filter-name>MyServlet</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

过滤器链

可以配置多个过滤器,过滤器的先后顺序 是由 <filter-mapping>的位置 决定

监听器

一个类实现了三个接口

监听对象的创建销毁

—四个范围对象 PageContext request session application

监听对象request response application 各自一个监听器

request:ServletRequestListener
response:HttpSessionListener
application:ServletContextListener

每个监听器提供了两个方法 一个开始一个结束

ServletContext在Servlet启动时自动创建


//监听器实现接听接口    
public class ContextSessionRequestListner implements ServletRequestListener,HttpSessionListener,ServletContextListener{
    //application(ServletContext)
    public void contextDestroyed(ServletContextEvent arg0) {
        System.out.println("监听ServletConntext,创建Servlet对象........"+arg0);
    }
    public void contextInitialized(ServletContextEvent arg0) {
        System.out.println("监听ServletConntext,销毁Servlet对象........"+arg0);
    }
    //session
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("监听HttpSession,创建HttpSession对象........"+se);
    }
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("监听HttpSession,销毁HttpSession对象........"+se);
    }
    //request
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("监听ServletRequest,销毁ServletRequest对象........"+sre);
    }
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("初始化ServletRequest,创建ServletRequest对象........"+sre);
    }
}

web.xml配置监听器

<listener>
    <listener-class>org.ycit.listner.ContextSessionRequestListner</listener-class>
</listener> 

监听对象属性的变更

request:ServletRequestAttributeListener
response:HttpSessionAttributeListener
application:ServletServletContextAttributeListener

每个方法有三个方法(创建、删除、修改)


public class AttributeListener implements ServletRequestAttributeListener,HttpSessionAttributeListener,ServletContextAttributeListener{
    public void attributeAdded(ServletContextAttributeEvent arg0) {
        String attrName = arg0.getName();
        Object attrValue = arg0.getServletContext().getAttribute(attrName);
        System.out.println("ServletContex【增加】属性"+attrName+"属性值:  "+attrValue);
    }
    @Override
    public void attributeRemoved(ServletContextAttributeEvent arg0) {
        System.out.println("ServletContex【删除】属性"+arg0.getName());
    }
    public void attributeReplaced(ServletContextAttributeEvent arg0) {
        String attrName = arg0.getName();
        Object attrValue = arg0.getServletContext().getAttribute(attrName);
        System.out.println("ServletContex【替换】属性"+attrName+"属性值:  "+attrValue);
    }
    public void attributeAdded(HttpSessionBindingEvent arg0) {
    }
    public void attributeRemoved(HttpSessionBindingEvent arg0) {
    }
    public void attributeReplaced(HttpSessionBindingEvent arg0) {
    }
    public void attributeAdded(ServletRequestAttributeEvent srae) {
    }
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
    }
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
    }
}

配置 web.xml

<listener>
    <listener-class></listener-class>

</listener>     

session钝化

客户端访问服务器会分配session对象(在内存中) 当访问量很多的时候会使得内存放不下 我们可以将session放入硬盘中(钝化)

从硬盘取到内存中叫活化

session对象的四种状态: 不需要配xml

监听绑定和解绑HttpSessionBindingListener

监听Session对象的钝化和活HttpSessionActivationListener

  1. session.setAttribute(“aa”,“dfg”)对象aa绑定到session中(增加)

  2. session.removeAttribute(“aa”) 将对象aa从session中解绑(删除)

    每次访问时会先后创建session对象,然后再删除之前绑定的session对象

  3. 钝化

  4. 活化

    方法WillPa 监听钝化之前

    Did 刚刚进行活化之后

如何钝化和活化

钝化

配置 tomcat安装目录/conf/context.xml

添加; 最大空闲时间;5秒钟没访问就钝化

<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="5">
    <Store className="apache.catalina.session.FileStore" directory="aqa"/>
</Manager>
FileStore:拖过该类具体操作

directory:相对路径      

相对于tomcat/work/Catalina/localhost/项目名/aqa

活化:自动活化

两个本质:序列化反序列化 需要借助Serializable接口

类名 implements HttpSessionActivationListener,Serializable

总结: 钝化活化实际的执行是通过tomcat中的context.xml中进行配置

HttpSessionActivationListener只是负责session钝化和活化时予以监听

需要实现Serializable接口

活化:session中获取某一个对象时,如果该对象不存在时,则直接尝试从之前钝化的文件中去获取(活化)

Ajax

异步js 和 xml

异步刷新: 如果网页中某一个地方需要修改,异步刷新可以使:只刷新该需要修改的地方,而页面中其他地方 保持不变。例如:百度搜索框、视频的点赞

实现:

js: XMLHttpRequest对象

XMLHttpRequest对象的方法:

open(方法名(提交方式get|post),服务器地址,true) :与服务端建立连接true异步刷新,false全局刷新

send():

    get:    send(null)
    post:   send(参数值)

setRequestHeader(header,value):
    get:不需要设置此方法

    post:需要设置:
    a.如果请求元素中包含了 文件上传:

        setRequestHeader("Content-Type","multipart/form-data");
    b.不包含了 文件上传

        setRequestHeader("Content-Type","application/x-www-form-urlencoded")

XMLHttpRequest对象的属性:

readyState:请求状态   只有状态为4 代表请求完毕
status:响应状态  只有200 代表响应正常
onreadystatechange:回调函数
responseText:响应格式为String
responseXML:相应格式为XML

请求状态

响应状态

js

<script type="text/javascript">
    function register()
    {
        var mobile = document.getElementById("mobile").value;
        //通过ajax异步方式 请求服务端
        xmlHttpRequest = new XMLHttpRequest();
        //设置xmlHttpRequest对象的回调函数
        xmlHttpRequest.onreadystatechange = callBack  ;
        xmlHttpRequest.open("post","MobileServlet",true);
        //设置post方式的 头信息
        xmlHttpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    }
    function registerGet()
    {
        var mobile = document.getElementById("mobile").value;
        //通过ajax异步方式 请求服务端
        xmlHttpRequest = new XMLHttpRequest();
        //设置xmlHttpRequest对象的回调函数
        xmlHttpRequest.onreadystatechange = callBack  ;
        xmlHttpRequest.open("get","MobileServlet?mobile="+mobile,true);
        //设置post方式的 头信息  ,get不需要
        //xmlHttpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xmlHttpRequest.send(null);//k=v
    }
    //定义回调函数 (接收服务端的返回值)
    function callBack(){
        if(xmlHttpRequest.readyState ==4 && xmlHttpRequest.status   ==200){
            //接收服务端返回的数据
            var data = xmlHttpRequest.responseText ;//服务端返回值为string格式
            alert(data.length +"==="+data)
            if(data == "true"){
                alert("请号码已存在,请更换!");
            }else{
                alert("注册成功!");
            }
        }
    }
</script>
手机:<input  id="mobile"/>
    <br/>
    <input type="button" value="注册" onclick="registerGet()" />
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");
    response.setContentType("text/html; charset=UTF-8");
    String mobile =  request.getParameter("mobile") ;
    //假设此时 数据库中 只有一个号码:18888888888
    //if(mobile.equals("18888888888")) {
    PrintWriter out = response.getWriter();
    if("18888888888".equals(mobile)) {
        //return true ;
    //out.write("true");//servlet以输出流的方式  将信息 返回给客户端
        //out.write("此号码已存在!");
        //如果客户端是getJSON(),则需要以json格式返回数据
        out.write( "{\"msg\":\"true\"}"   );//   {"\"msg\":\"true\""}
    }else {
        //return false ;
    //out.write("false");
        //out.write("注册成功!");
        out.write( "{\"msg\":\"false\"}"   );// "\"msg\":\"false\"" 
    }
    out.close();
}

jquery:推荐

$.ajax({
    url:服务器地址,
    请求方式:get|post,
    data:请求数据,
    success:function(result,testStatus)
    {
    },
    error:function(xhr,errrorMessage,e){
    }
});
$.get(
    服务器地址,
    请求数据,
function (result){

},
预期返回值类型(string\xml)
);
$.post(
    服务器地址,
    请求数据,
function (result){

},
	"xml" 或 "json" 或 "text" 
);
$(xxx).load(
    服务器地址,
    请求数据
);

load:将服务端的返回值 直接加载到$(xxx)所选择的元素中 JSON

$.getJSON(
服务器地址,
JSON格式的请求数据,
function (result){

}

);
<script type="text/javascript">
    function register()
    {
        var $mobile = $("#mobile").val();
        /*
        $.ajax({
            url:"MobileServlet",
            请求方式:"post",
            data:"mobile="+$mobile, 
            success:function(result,testStatus)
            {
                if(result == "true"){
                    alert("已存在!注册失败!");
                }else{
                    alert("注册成功!");
                }
            },
            error:function(xhr,errrorMessage,e){
                alert("系统异常!");
            }
            });
        $.post(
                "MobileServlet",
                "mobile="+$mobile,
            function (result){
                if(result == "true"){
                    alert("已存在!注册失败!");
                }else{
                    alert("注册成功!");
                }
            },
            "text"
            );
        $.get(
                "MobileServlet",
                "mobile="+$mobile,
            function (result){
                if(result == "true"){
                    alert("已存在!注册失败!");
                }else{
                    alert("注册成功!");
                }
            }
            );
        $("#tip").load(
            "MobileServlet",
            "mobile="+$mobile
        );
        var student = {"name":"zs" ,  "age":23} ;
        //alert(student.name +"--" +student.age) ;
        //var name = ["xx","xx","xx"] ;
        var students =[
            {"name":"zs" ,  "age":23} ,
            {"name":"ls" ,  "age":24} ,
            {"name":"ww" ,  "age":25} 
        ];
        alert(students[1].name +"--" +students[1].age) ;
        */
        $.getJSON(
            "MobileServlet",
        //	"mobile="+$mobile,
            {"mobile":$mobile},
        function (result){//msg:true|false
                alert(123);
                if(result.msg == "true"){
                    alert("已存在!注册失败!");
                }else{
                    alert("注册成功!");
                }
            }
        );
    }
</script>
手机:<input  id="mobile"/>
<br/>
<input type="button" value="注册" onclick="register()" />
<span id="tip"></span>

Ajax处理JSON数据

jar包

commons-beanutils-1.7.0.jar
commons-lang-2.6.jar
commons-collections-3.2.1.jar
commons-logging-1.1.3.jar
ezmorph-1.0.6.jar
json-lib-2.4-jdk15.jar
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");
    response.setContentType("text/html; charset=UTF-8");
    PrintWriter out = response.getWriter();
    //测试前端传来的数据
    String name2 = request.getParameter("name") ;
    String age2 = request.getParameter("age") ;
    System.out.println(name2+"--"+age2);
    Student stu1 = new Student();
    stu1.setAge(23);
    stu1.setName("zs");
    Student stu2 = new Student();
    stu2.setAge(44);
    stu2.setName("ls");
    Student stu3 = new Student();
    stu3.setAge(55);
    stu3.setName("ww");

    JSONObject json = new JSONObject() ;
    json.put("stu1",stu1 ) ;
    json.put("stu2",stu2 ) ;
    json.put("stu3",stu3 ) ;
    out.print( json);// 返回json对象     {"stu1":stu1, "stu2":stu2,"stu3":stu3 }
    out.close(); 
}
/* json中只有一个对象的情况
function  testJson()
{
    $.getJSON(
            "JsonServlet",
            {"name":"zs",  "age":24},
        function (result){
                //js需要通过eval()函数  将返回值 转为一个js能够识别的json对象
            var jsonStudent = eval(result.stu1) ;
            alert(jsonStudent.name  +"---"+ jsonStudent.age) ;
        }
    );
}
*/
/* json中有多对象的情况*/
function  testJson()
{
    $.getJSON(
            "JsonServlet",
            {"name":"zs",  "age":24},
        function (result){ 
                // result:  {"stu1":stu1, "stu2":stu2,"stu3":stu3 }
                //js需要通过eval()函数  将返回值 转为一个js能够识别的json对象
            var json = eval(result) ;
            $.each( json, function(i,element){
                alert( this.name +"---"+ this.age );
            } );
        }
    );
}

IDEA开发Web

配置tomcat

run–》Edit Configurations–>点击加号–>Tomcat Server

选择tomcat,jre

Deployment部署项目,设置项目名

idea中tomcat乱码:

  1. file - settings - 搜File Encodings,全部改为utf-8
  2. 打开idea安装目录/bin,在idea64.exe.vmoptions和idea.exe.vmoptions最后追加

    -Dfile.encoding=UTF-8
    
  3. 配置tomcat的页面中:VM option设置:

    -Dfile.encoding=UTF-8
    

热部署问题(jsp+java)

在配置tomcat的页面

  • Update:更新操作(经过测试,很多时候无效)
  • Frame:idea失去焦点时触发

推荐选项:

  • Update:任意
  • Frame:update classes and resources

idea:热部署

  • 如果是run启动,仅JSP等静态资源有效
  • 如果是debug启动,java和jsp等均有效

总结 热部署:

  1. Frame:update classes and resources
  2. 以debug模式启动

注意:编写servlet前 需要先加入tomcat环境

打开Project Structure –》Modules –>右边加号–>Library–》tomcat

Servlet3.0注解方式的参数设置访问路径的为url-pattern,也是默认值,值中要含有项目根目录/;例

@javax.servlet.annotation.WebServlet( "/MyServlet")

注意注解参数的值中有没有url-pattern,和值正不正确

自动导包

Settings–>Editor–>General–>Auto Import

JNDI

java命名与目录接口

pageContext < request< session< application(一个项目运行期间都有效)

jndi:将某一个资源(对象),以配置文件(tomcat/conf/context.xml)的形式写入;

实现步骤:

tomcat/conf/context.xml配置:

<Context>
    <Environment  name="jndiName"  value="jndiValue" type="java.lang.String" />

jsp中用:

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

DIEA JAR包

  • java项目

    复制粘贴jar包–》右键Add as Library

  • Web项目

    1. WEB-INF新建目录lib

    2. 将jar包复制到目录

    3. Project Structure–>Artifacts–>右边添加文件夹lib

Eclipse中的web项目引入jar:只需要 jar 放入web-context/lib/xx.jar,Eclipse就会将web-context/lib/中的所有jar存放到项目的全部生命周期中

IDEA:会将web-context/lib/中的所有jar包,只存在于运行阶段生效

开发(编写、编译)- 运行(commons-dbcp.jar )

即idea中Web-Content/lib/的jar,只在运行时生效,在其他阶段不生效。

解决方案: gradle/maven

手工解决(了解):原理-结论

  1. jar包本身只在 运行时有效(ojdbc.jar)

        Class.forName()
        DriverManager.getCOnnection()
        Presss
        Connection
    

    处理办法:

    1. 只需要将jar复制在Web-Content/lib/ojdbc7.jar

      问题:有时候延迟较长

    2. Structure - Artifacts - output

2.commons-dbcp.jar (开发时、运行均有效)

Web-Content/lib/commons-dbcp.jar  --只在运行时有效

手工解决: 将commons-dbcp.jar 在开发时也有效:

直接将jar复制到工程src中,右键-Add as Library...    

常见错误:

仅仅将jar引用在了Dependencies中,并且将jar的生命周期选择为了compile -错误

错误原因:

web项目结构:web约定jar是存放在lib中

总结论:

  1. java项目,和eclipse,在项目src中复制粘贴jar包右键 add as library
  2. web项目:

    1. jar本身就只在运行阶段有效ojdbc.jar

      只需要复制Web-Content/lib/中即可。

    2. jar在各个阶段均有效

      要复制Web-Content/lib/中即可

      再复制src中,右键-Add as library…

或Project Structure中Modules添加jar包

连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。

常见连接池:Tomcat-dbcp、dbcp、c3p0、druid

可以用数据源(javax.sql.DataSource)管理连接池

Tomcat-dbcp

resource

  1. 类似jndi,在context.xml中配置数据库

    <Context>    
        <Resource name="student" auth="Container" type="javax.sql.DataSource" 
            maxActive="400" 
            maxIdle="20"  maxWait="5000"  username="scott" password="tiger" 
    driverClassName="oracle.jdbc.driver.OracleDriver"  url="jdbc:oracle:thin:@127.0.0.1:1521:ORCL" />
    
  2. 在项目的web.xml中

    <resource-ref>
        <res-ref-name>student</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
    
  3. 使用数据源

    更改 连接对象Connection的获取方式 :

    • 传统Jdbc方式

      connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
      
    • 数据源方式:

      Context ctx = new InitialContext() ;//context.xml
      DataSource ds = (DataSource)ctx.lookup("java:comp/env/student") ;
      connection = ds.getConnection();
      

tomcat-dbcp数据源总结:

  1. 配置数据源(context.xml)
  2. 指定数据源(web.xml)
  3. 用数据源 :通过数据库获取Connection

dbcp连接池

Jar包

commons-dbcp-1.4.jar
commons-pool.jar

连接池:

怎么用? * 不用连接池

    Class.forName();
    Connection connection = DriverManager.getConnection();//连接指向数据库
  • 用连接池的核心:将连接的指向改了,现在指向的是数据源 而不是数据库。

    ....-> DataSource ds = ....
    Connection connection =ds.getConnection();//指向的是数据源的连接
    

数据库访问核心 -> pstmt/stmt ->connection

  1. 直接数据库
  2. 数据源 ds.getConnection()

    PreparedStatemnt pstmt = connection.preparedStatement();
    

dbcp获取ds:

1. BasicDataSource
2. BasicDataSourceFactory

BasicDataSource方式

(硬编码):DasicDataSource对象设置各种数据

BasicDataSource

//获取dbcp方式的ds对象
public static DataSource getDataSourceWIthDBCP(){
    BasicDataSource dbcp = new BasicDataSource();
    dbcp.setDriverClassName("oracle.jdbc.driver.OracleDriver");
    dbcp.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:ORCL");
    dbcp.setUsername("scott");
    dbcp.setPassword("tiger");
    dbcp.setInitialSize(20);
    dbcp.setMaxActive(10);
    return dbcp;
}
getDataSourceWIthDBCP().getConnection() 

BasicDataSourceFactory方式

配置方式(.properties文件, 编写方式key=value)

dbcpconfig.properties

driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:ORCL
username=scott
password=tiger
initialSize=10
public static DataSource getDataSourceWIthDBCPByProperties() throws Exception{
    DataSource dbcp = null ;
    Properties props = new Properties();
    InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
    props.load(input);
    //只需要记住以下一句
    dbcp = BasicDataSourceFactory.createDataSource(props) ;
    return dbcp;
}
getDataSourceWIthDBCPByProperties().getConnection();

报错:NoClassDefFoundError异常 说明少jar

DataSource 是所有sql数据源的上级类。BasicDataSource是dbcp类型的数据源,ComboPooledDataSource是c3p0类型的数据源;XxxDataSource…

###c3p0连接池

两种方式

  • 硬编码
  • 配置文件

->合二为一 ,通过ComboPooledDataSource的构造方法参数区分:

  • 如果无参,硬编码;
  • 有参,配置文件

jar包

c3p0.jar  c3p0-oracle-thin-extras.jar

ComboPooledDataSource

无参,硬编码

public static DataSource getDataSourceWithC3P0(){
    ComboPooledDataSource c3p0 = new ComboPooledDataSource();
    try {
        c3p0.setDriverClass("oracle.jdbc.driver.OracleDriver");
    } catch (PropertyVetoException e) {
        e.printStackTrace();
    }
    c3p0.setJdbcUrl("jdbc:oracle:thin:@127.0.0.1:1521:ORCL");
    c3p0.setUser("scott");
    c3p0.setPassword("tiger");
    return c3p0 ;
}

有参,配置文件

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <!-- 如果要研究某个xml中可以设置哪些属性。找相关类的 属性 或者setXxx()-->
        <property name="user">scott</property>
        <property name="password">tiger</property>
        <property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
        <property name="jdbcUrl">jdbc:oracle:thin:@127.0.0.1:1521:ORCL</property>
        <property name="checkoutTimeout">30000</property>
    </default-config>
    <named-config name="yanqun">
        <property name="user">scott</property>
        <property name="password">tiger</property>
        <property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
        <property name="jdbcUrl">jdbc:oracle:thin:@127.0.0.1:1521:ORCL</property>
        <property name="checkoutTimeout">20000</property>
    </named-config>
</c3p0-config>
public static DataSource getDataSourceWithC3P0ByXml(){
    ComboPooledDataSource c3p0 = new ComboPooledDataSource("yanqun");
    return c3p0 ;
}

oralce修改密码:

管理员状态 sqlplus / as sysdba

alter user scott identified by tiger ;

所有连接池的思路:

  • 硬编码,某个连接池数据源的

    对象 ds = new XxxDataSource(); ds.setXxx();  return ds ;
    
  • 配置文件

    ds = new XxxDataSource();加载配置文件  ,return ds ;
    

数据源工具类

dbcp、c3p0, druid

public class DataSourceUtil {
    //获取dbcp方式的ds对象
    public static DataSource getDataSourceWIthDBCP(){
        BasicDataSource dbcp = new BasicDataSource();
        dbcp.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        dbcp.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:ORCL");
        dbcp.setUsername("scott");
        dbcp.setPassword("tiger");
        dbcp.setInitialSize(20);
        dbcp.setMaxActive(10);
        return dbcp;
    }
    public static DataSource getDataSourceWIthDBCPByProperties() throws Exception{
        DataSource dbcp = null ;
        Properties props = new Properties();
        InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
        props.load(  input );
        //只需要记住以下一句
        dbcp = BasicDataSourceFactory.createDataSource(props ) ;
        return dbcp;
    }
    public static DataSource getDataSourceWithC3P0(){
        ComboPooledDataSource c3p0 = new ComboPooledDataSource();
        try {
            c3p0.setDriverClass("oracle.jdbc.driver.OracleDriver");
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        c3p0.setJdbcUrl("jdbc:oracle:thin:@127.0.0.1:1521:ORCL");
        c3p0.setUser("scott");
        c3p0.setPassword("tiger");
        return c3p0 ;
    }
    public static DataSource getDataSourceWithC3P0ByXml(){
        ComboPooledDataSource c3p0 = new ComboPooledDataSource("yanqun");
        return c3p0 ;
    }
}

Apache DBUtils

jar

commons-dbutils-1.7.jar

DbUtils常见方法

其中包含以下几个重点类:

DbUtils、QueryRunner、ResultSetHandler
  1. DbUtils:辅助
  2. QueryRunner:增删改查

    update()
    query()
    

    oracle:dml,commit

    mysql:dml自动提交

  3. 如果是查询,则需要ResultSetHandler接口,有很多实现类,一个实现类对应于一种 不同的查询结果类型

实现类ArrayHandler :返回结果集中的第一行数据,并用Object[]接收

//select *from student ;  -> List<Student>
//查询单行数据
public static void testArrayHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    Object[] student = runner.query("select * from student where id > ? " ,new ArrayHandler(),1) ;
    System.out.println( student[0]+","+student[1]);
}

实现类ArrayListHandler:返回结果集中的多行数据, List<Object[]>

//查询单多数据
public static void testArrayListHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    List<Object[]> students = runner.query("select * from student where id > ? " ,new ArrayListHandler(),1) ;
    for(Object[] student:students){
        System.out.println( student[0]+","+student[1]);
    }
}

BeanHandler :返回结果集中的第一行数据,用对象(Student)接收

//查询单行数据(放入对象中)
public static void testBeanHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    Student student = runner.query("select * from student where id > ? " ,new BeanHandler<Student>(Student.class),1) ;
    System.out.println( student.getId()+","+student.getName());
}

BeanListHandler:返回结果集中的多行数据, List<Student> students, stu stu2 stu3

 //查询多行数据(放入对象中)
public static void testBeanListHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    List<Student> students = runner.query("select * from student where id > ? " ,new BeanListHandler<Student>(Student.class),1) ;
    for(Student student:students){
        System.out.println( student.getId()+","+student.getName());
    }
}

BeanMapHandler

1:stu1,2:stu2.3:stu3

//查询多行数据(放入map中)
public static void testBeanMapHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务     --   java中对应oracle默认的数值类型  BigDecimal
    Map<BigDecimal,Student> students = runner.query("select * from student where id > ? " ,new BeanMapHandler<BigDecimal,Student>( Student.class,"id" ),1) ;

    // 2: ls,  3:ww
    Student stu = students.get(new BigDecimal(2)) ;
    System.out.println(stu.getId()+","+stu.getName());
}

反射会通过无参构造来创建对象

– Map

MapHandler::返回结果集中的第一行数据

{id=1 ,name=zs}

//查询单行数据 map
public static void testMapHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    Map<String,Object> student = runner.query("select * from student where id > ? " ,new MapHandler(),1) ;
    System.out.println( student);
}

MapListHandler:返回结果集中的多行数据

{{id=2 ,name=ls},{id=3 ,name=ww}}

//查询多行数据 map
public static void testMapListHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    List<Map<String,Object>> students = runner.query("select * from student where id > ? " ,new MapListHandler(),1) ;
    System.out.println( students);
}

KeyedHanlder:

{ls={id=2 ,name=ls},ww={id=3 ,name=ww}}

//查询多行数据 keyed
public static void testKeyedHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    Map<String,Map<String,Object>> students = runner.query("select * from student where id > ? " ,new KeyedHandler<String>("name"),1) ;
    System.out.println( students);
}

ColumnListHander :把结果集中的某一列 保存到List中

//查询多行数据中的某一列
public static void testColumnListHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
    List<String> names = runner.query("select * from student where id > ? " ,new ColumnListHandler<String>("name"),1) ;
    System.out.println( names);
}

ScalarHandler :单值结果

//查询单值数据
public static void testScalarHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());//自动提交事务
//        BigDecimal result = runner.query("select name from student where id = ? " ,new ScalarHandler<BigDecimal>(),2) ;
    String result = runner.query("select name from student where id = ? " ,new ScalarHandler<String>(),2) ;
    System.out.println( result);
}

问题:查询的实现类的参数问题

query(...,  Object... params ) 

其中Object… params代表可变参数: 既可以写单值,也可以写一个数组

runner.query("... where id = ? and name like ?  " ,new ArrayHandler(),new Object[]{1,"%s%"}) ;

runner.query("... where id = ?" ,new ArrayHandler(),1) ;

QueryRunner类的常用方法

ResultSetHandler实现类

apache dbutils 增删改

自动提交事务 update(sql,参数);update(sql);

手动提交事务 update(connection ,sql,参数);

public static void add() throws SQLException {
    QueryRunner runner =  new QueryRunner(DataSourceUtil.getDataSourceWithC3P0()) ;
    int count = runner.update( "insert into student(id,name) values(?,?)",new Object[]{4,"zl"} ) ;
    System.out.println(count);
}
public static void delete() throws SQLException {
    QueryRunner runner =  new QueryRunner(DataSourceUtil.getDataSourceWithC3P0()) ;
    int count = runner.update( "delete from student where id = ?",4 ) ;
    System.out.println(count);
}
public static void update() throws SQLException {
    QueryRunner runner =  new QueryRunner(DataSourceUtil.getDataSourceWithC3P0()) ;
    int count = runner.update( "update student set name = ? where id = ?",new Object[]{"xx",3} ) ;
    System.out.println(count);
}

手动提交事务

基础知识:

如果既要保证数据安全,又要保证性能,可以考虑ThreadLocal

ThreadLocal:可以为每个线程 复制一个副本。每个线程可以访问自己内部的副本。 别名 :线程本地变量

set():给tl中存放一个 变量
get():从tl中获取变量(副本),
remove();删除副本

对于数据库来说,一个连接 对应于一个事务 ,一个事务可以包含多个DML操作

Service(多个原子操作) -> Dao(原子操作)

如果给每个 dao操作 都创建一个connection,则 多个dao操作对应于多个事务;

但是 一般来讲,一个业务(service) 中的多个dao操作 应该包含在一个事务中。

->解决,ThreadLocal, 在第一个dao操作时 真正的创建一个connection对象,然后在其他几次dao操作时,借助于tl本身特性 自动将该connection复制多个(connection只创建了一个,因此该connection中的所有操作 必然对应于同一个事务; 并且tl将connection在使用层面复制了多个,因此可以同时完成多个dao操作)

事务流程: 开启事务(将自动提交->手工提交) ->进行各种DML ->正常,将刚才所有DML全部提交 (全部成功)

->失败(异常),将刚才所有DML全部回滚(全部失败)

事务的操作 全部和连接Connection密切相关

public class JDBCUtil {
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>() ;
    public static Connection getConnection() throws SQLException {
        Connection conn = threadLocal.get();//获取tl中的变量,如果不够,会自动创建副本
        if(conn == null){
            conn = DataSourceUtil.getDataSourceWithC3P0().getConnection();
            threadLocal.set(conn);
        }
        return conn;
    }
    //开启事务
    public static void beginTransaction() throws SQLException {
       //连接
        Connection conn =  getConnection() ;
        conn.setAutoCommit(false);//开启事务
    }
    //正常。提交事务
    public static void commitTransaction() throws SQLException {
        //连接
        Connection conn =  getConnection() ;
        //所有的 对象.方法()  全部排空
        if(conn !=null)
            conn.commit();
    }
    //失败。回滚事务
    public static void rollbackTransaction() throws SQLException {
        //连接
        Connection conn =  getConnection() ;
        if(conn !=null)
            conn.rollback();
    }
    public static void close() throws SQLException{
        Connection conn =  getConnection() ;
        if(conn !=null)
           conn.close();
        threadLocal.remove();
        conn = null ;
    }
}
public class AccountDaoImpl implements AccountDao {
    @Override
    public Account queryAccountByCard(int cardId) throws SQLException {
        QueryRunner runner = new QueryRunner() ;
        Connection conn = JDBCUtil.getConnection() ;
        Account account = runner.query(conn, "select * from account where carid = ?", new BeanHandler<Account>(Account.class), cardId);//如果是手动提交:  QueryRunner()无参;query()update()必须传入connection
        return account;
    }
    //carid   余额
    @Override
    public void updateAccount(Account account) throws SQLException{
        QueryRunner runner = new QueryRunner() ;
        Connection conn = JDBCUtil.getConnection() ;
        runner.update(conn,"update account set balance = ? where carid = ?",new Object[]{account.getBalance(),account.getCardId()} );
    }
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer(int fromCardId, int toCardId, int money) {
        AccountDaoImpl accountDao = new AccountDaoImpl();
        //开启事务
        try {
            JDBCUtil.beginTransaction();
            //各种DML操作
            //a. -1000   b.+1000
            //根据cardid查询相应的账户
            Account fromAccount = accountDao.queryAccountByCard(fromCardId) ;//付款方
            Account toAccount =  accountDao.queryAccountByCard(toCardId) ;//收款方
            //转账
            if(fromAccount.getBalance() >   money){
                //付款方 -1000
                int fromBalance = fromAccount.getBalance() - money;
                fromAccount.setBalance( fromBalance    );
                fromAccount.setCardId(fromCardId );
                accountDao.updateAccount(fromAccount);
                System.out.println(1/0);
                //收款方+1000
                int toBalance = toAccount.getBalance() + money ;
                toAccount.setBalance(toBalance);
                toAccount.setCardId(toCardId);
                accountDao.updateAccount(toAccount);
                System.out.println("转账成功!");
                //正常提交事务
                JDBCUtil.commitTransaction();
            }else{
                System.out.println("余额不足!");
            }
        } catch (Exception e) {
            try {
                JDBCUtil.rollbackTransaction();
                System.out.println("转账失败,回滚操作!");
            } catch (SQLException e1) {
                e1.printStackTrace();
            }catch (Exception e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally{
            try {
                JDBCUtil.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //结束事务(正常、失败)
    }
}
public class Test {
    public static void main(String[] args) {
        AccountServiceImpl accountService = new AccountServiceImpl();
        accountService.transfer(1234,1111,100);
    }
}

元数据(MetaData)

DataBaseMetaData接口方法

ParameterMetaData接口方法

ResultSetMetaData接口的常见方法 描述数据的数据

三类:

  • 数据库元数据
  • 参数元数据
  • 结果集元数据

1.数据库元数据 DataBaseMetaData

Connection -> DataBaseMetaData -> .

2.参数元数据 ParameterMetaData pstmt -> ParameterMetaData -> .

3.结果集元数据 ResultSetMetaData ResultSet -> ResultSetMetaData


public class MeteDataDemo {
    private  final static String  DRIVER="oracle.jdbc.OracleDriver";
    private  final static String  URL="jdbc:oracle:thin:@localhost:1521:orcl";
    private  final static String  USERNAME="scott";
    private  final static String  PASSWORD="tiger";
    public static void databaseMetaData(){
        try {
            Class.forName(DRIVER);
            Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
            //数据库元信息
            DatabaseMetaData dbMetadata = connection.getMetaData() ;
            String dbName =  dbMetadata.getDatabaseProductName() ;
            System.out.println("数据库名"+dbName);
            String dbVersion = dbMetadata.getDatabaseProductVersion() ;//11.1.22
            System.out.println("数据库版本"+dbVersion);
            String driverName = dbMetadata.getDriverName();
            System.out.println(driverName);
            String url = dbMetadata.getURL();
            System.out.println(url);
            String userName = dbMetadata.getUserName();
            System.out.println(userName);
            System.out.println("-----------");
            ResultSet rs = dbMetadata.getPrimaryKeys(null, userName, "STUDENT");
            while(rs.next()){
                Object tableName = rs.getObject(3);
                Object columnName = rs.getObject(4);
                Object pkName = rs.getObject(6);
                System.out.println(tableName+"--"+columnName+"--"+pkName);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static void parameterMetaData(){
        try {
            Class.forName(DRIVER);
            Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
            PreparedStatement pstmt = connection.prepareStatement("select * from student where id = ? and name = ?");
            //通過pstmt獲取參數元数据
            ParameterMetaData metaData = pstmt.getParameterMetaData();
            int count = metaData.getParameterCount() ;
            System.out.println("参数个数:"+count);
            for(int i=1;i<=count ; i++){
                String typeName =  metaData.getParameterTypeName(i) ;
                System.out.println(typeName);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static void resultSetMetaData(){
        try {
            Class.forName(DRIVER);
            Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
            PreparedStatement pstmt = connection.prepareStatement("select * from student ");
            ResultSet rs = pstmt.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int count = metaData.getColumnCount();
            System.out.println("列的个数:"+count);
            System.out.println("---");
            for(int i=1;i<=count ;i++){
                String columnName = metaData.getColumnName(i);
                String columnTypeName = metaData.getColumnTypeName(i);
                System.out.println( columnName+"\t"+columnTypeName);
            }
            while(rs.next()){
                for(int i=1;i<=count;i++){
                    System.out.print( rs.getObject(i)+"\t");
                }
                System.out.println();
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

很多数据库对元数据的支持问题:

Oralce目前必须使用ojdbc7.jar作为驱动包;

MySql必须在url中附加参数配置:

jdbc:mysql://localhost:3306/数据库名?generateSimpleParameterMetadata=true

自定义标签

自定义标签结构

Tag

步骤:

  1. 编写标签处理类
  2. 编写标签描述符
  3. 导入并使用

编写标签处理类

  • 传统方式(JSP1.1):实现

    javax.servlet.jsp.tagext.Tag接口      doStartTag()
    
  • 简单方式(JSP2.0):实现

    javax.servlet.jsp.tagext.SimpleTag接口   doTag()
    

如果jsp在编译阶段 发现了自定义标签<xx:yyy>,就会交给doStartTag()或doTag()

编写标签描述符 tld

编写建议:可以仿照一个 其他标签语言(el jstl ) 的tld文件

<?xml version="1.0" encoding="UTF-8"?>

<!-- 标签库的头文件-->
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <description>标签库描述</description>
    <tlib-version>1.0</tlib-version>
    <short-name>xxxxxx</short-name>
    <uri>标签库的url</uri>
    <!-- -自定义标签的相关信息 -->
    <tag>
        <description>标签描述</description>
        <name>标签名</name>
        <tag-class>标签的处理</tag-class>
        <body-content>标签体的类型</body-content>
    </tag>
</taglib>

body-content

scriptlet:

<%   ...  %>  <%!   ...  %>   <%=   ...  %>

导入并使用

位置:myTag.tld导入 WEB-INF或子目录下WEB-INF/xxx/(特例排除,不能是WEB-INF/lib、WEB-INF/classes )

使用:

引入具体要使用的.tld文件

<%@ taglib uri="..."  prefix="d"  %>
  • uri:每个tld文件的 唯一描述符
  • prefix:使用tld标签时 的前缀

具体的使用:

  1. 空标签(没有标签体的标签)

    <d:foreach></d:foreach>
    <d:foreach />
    
  2. 带标签体

    <d:foreach> xxx </d:foreach>
    
  3. 带属性

    <d:foreach  属性名="属性值" > xxx </d:foreach>
    <d:foreach  collection="${students}" > xxx </d:foreach>
    
  4. 嵌套

    <d:foreach> 
        <d:foreach> xxx </d:foreach>
        </d:foreach>
    <d:foreach>
    

实际开发步骤

编写标签处理类

先确保项目有tomcat环境

Tag接口:

doStartTag():标签处理类的核心方法 (标签体的执行逻辑)

该方法有以下2个返回值:0/1

int SKIP_BODY = 0;  标签体不会被执行
int EVAL_BODY_INCLUDE = 1; 标签体会被执行

doEndTag():标签执行完毕之后 的方法.例如可以让 标签在执行完毕后,再执行一次

int SKIP_PAGE = 5;后面的JSP页面内容不被执行
int EVAL_PAGE = 6;后面的JSP页面内容继续执行

Tag接口中的所有方法执行顺序:

jsp - servlet

当JSP容器(Tomcat、jetty)在将.jsp翻译成.servlet(.java)的时候 ,如果遇到JSP中有标签,就会依次执行 setPageContext() setParent()doStartTag() doEndTagrealease(),用于解析标签的执行逻辑。

BodyTag

javax.servlet.jsp.tagext.IterationTag接口:(是Tag的子接口)

  • 如果有循环:IterationTag,

  • 没有循环:Tag

IterationTag接口中存在以下方法:

doAfterBody():当标签体执行完毕之后的操作 ,通过返回值决定 :

(EVAL_BODY_AGAIN = 2)要么重复执行 ;
(SKIP_BODY=0):要么不再执行

目标:遍历3次 (hello)

<xxx>hello<xxx>

执行一次,并重复两次

public class MyIteration extends TagSupport {
    private int num  ;//执行次数  3
    public  void setNum(int num){
        this.num = num ;
    }
    //执行标签
    @Override
    public int doStartTag() throws JspException {
        return EVAL_BODY_INCLUDE ;//先执行一次
    }
    //执行完毕后
    @Override
    public int doAfterBody() throws JspException {
        num-- ;
        return num>0 ? EVAL_BODY_AGAIN :SKIP_BODY; //重复执行
    }
}

编写标签描述符

myTag.tld

<?xml version="1.0" encoding="UTF-8"?>

<!-- 标签库的头文件-->
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <description>这是我的迭代器标签库</description>
    <tlib-version>1.0</tlib-version>
    <short-name>mytaglib</short-name>
    <uri>http://www.yanqun.com</uri>
    <!-- -自定义标签的相关信息 -->
    <tag>
        <description>这是我的迭代器标签</description>
        <name>mytag</name>
        <tag-class>com.yanqun.tag.MyIteration</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>num</name>
            <required>true</required>
        </attribute>
    </tag>
</taglib>

导入并使用

位置:myTag.tld导入 WEB-INF或子目录下WEB-INF/xxx/(特例排除,不能是WEB-INF/lib、WEB-INF/classes )

<%@ taglib uri="http://www.yanqun.com"  prefix="yq"%>
  <body>
    <yq:mytag num="3">
      hello
    </yq:mytag>
  </body>

BoyTag接口:如果在标签体 被显示之前,进行一些其他的“额外”操作

hello hello hello -> HELLO HELLO HELLO

包含属性:

int EVAL_BODY_BUFFERED = 2

是doStartTag()的第三个返回值 ,代表一个缓冲区(BodyContent)。

BodyContent是abstract,具体使用时需要使用实现类 BodyContentImpl(再引入jasper.jar)

缓冲区(BodyContent)的使用用途:hello ->HELLO

如果返回值是EVAL_BODY_BUFFERED ,则服务器会自动 将标签体需要显示的内容 放入缓冲区中(BodyContent)

。因此,如果要更改最终显示结果,只需要从 缓冲区 获取原来的数据 进行修改即可。

如何修改、获取 缓冲区:详见BodyContent的方法,具体就是 通过getXxx()获取原来的数据(hello),自己修改(HELLO),输出getEnclosingWriter();

BodyContent类的方法

传统标签处理类执行流程

public class ToUpperCase extends BodyTagSupport {
    //第一步,告诉程序 我要将原来的hello变成大写,即将 doStartTag()的返回值设置成 EVAL_BODY_BUFFER (此步骤,已经在父类默认实现)
    //第二步:获取,并修改
    @Override
    public int doEndTag() throws JspException {
        try {
            String content = getBodyContent().getString();//hello
            content = content.toUpperCase();//修改
            bodyContent.getEnclosingWriter().write(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.doEndTag();//默认6,代表标签执行完毕后 是否执行其他的JSP元素
    }
}
<tag>
    <name>myToUpperCase</name>
    <tag-class>com.yanqun.tag.ToUpperCase</tag-class>
    <body-content>JSP</body-content>
</tag>

简单方式

SimpleTag

SimpleTag接口中的方法

最大的简化:

将传统方式的doStartTag() doEndTag() doafterBody()等方法 简化成了一个通用的 doTag()方法。

doTag() :传统方式 可以对标签的最终显示 进行修改, hello ->HELLO ,核心是有一个缓冲区。

但是简单方式 没有“缓冲区”。 如何修改显示内容? 流

javax.servlet.jsp.tagext.JspFragment类 :代表一块JSP元素(该块 不包含scriptlet,因此简单方式的tld文件中<body-content>不能是JSP )

JspFragment中有一个invoke(Writer var1) 方法,入参是 “流”,即如果要修改显示内容,只需要修改此 流

invoke(Writer var1) :每调用一次invoke()方法,会执行一次 标签体。

SimpleTagSupport的 getJspBody()可以获取JspFragment对象。

SimpleTagSupport的 getJspContext()方法 可以获取 jsp一些内置对象:getJspContext()的返回值是JspContext对象 是JSP内置对象的入口对象PageContext 的父类。

简单标签方式 ,获取JSP内置对象: getJspContext() -> JspContext ->转成子类PageContext ->PageContext就是所有JSP内置对象的入口,即可以获取一切JSP内置对象

public class MySimpleTagIterator extends SimpleTagSupport {
    private int num ;//3
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
/*
    <aa>
            xxx
    </aa>
 */
    @Override
    public void doTag() throws JspException, IOException {
        JspFragment jspFragment = getJspBody();
        for(int i=0;i<num ;i++){
            jspFragment.invoke(null);
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<!-- 标签库的头文件-->
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <description>这是我的简单迭代器标签库</description>
    <tlib-version>1.0</tlib-version>
    <short-name>mysimpletaglib</short-name>
    <uri>http://www.yanqunsimple.com</uri>
    <!-- -自定义标签的相关信息 -->
    <tag>
        <description>这是我的简单迭代器标签</description>
        <name>mysimpletag</name>
        <tag-class>com.yanqun.tag.MySimpleTagIterator</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>num</name>
            <required>true</required>
        </attribute>
    </tag>
</taglib>
<%@ taglib uri="http://www.yanqunsimple.com" prefix="yqs"%>
<yqs:mysimpletag num="5">
nihao
</yqs:mysimpletag>

条件选择:

当某一个条件满足时,再执行某个标签体

传统标签:如果条件不满足,让doStartTag()的返回值为0;

简单标签(不允许写scriplet <% …%>): 如果条件不满足,不调用invoke()即可。

实例使用简单标签实现登录功能

<tag>
    <name>login</name>
    <tag-class>com.yanqun.tag.LoginTag</tag-class>
    <body-content>scriptless</body-content>
</tag>

input.jsp

<form action="LoginServlet">
    用户名 <input  name="uname"/><br/>
    密码 <input type="password" name="upwd"/><br/>
    <input type="submit" value="登录"/>
</form>

input.jsp

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String name =  request.getParameter("uname") ;
    String pwd =  request.getParameter("upwd") ;
    //zs  ,abc
    if(name.equals("zs") && pwd.equals("abc")){
        //登录成功,将当前用户名  保存到session
        request.getSession().setAttribute("name",name); //User user = new User();
        request.getRequestDispatcher("show.jsp").forward(request,response);
    }else{
        //跳转到失败页面
        response.sendRedirect("error.jsp");
    }
}

show.jsp

<%@taglib uri="http://www.yanqunsimple.com" prefix="yqs"%>
<yqs:login>
        ${sessionScope.name},已登录
        ${name}
</yqs:login>

LoginTag.java

public class LoginTag extends SimpleTagSupport {
    @Override
    public void doTag() throws JspException, IOException {
        //判断 是否已经登录成功
        PageContext pageContext = (PageContext)getJspContext() ;
        HttpSession session = pageContext.getSession();
        String name = (String)session.getAttribute("name") ;
        if(name !=null){
            //执行一次标签体
            getJspBody().invoke(null) ;
        }
        //如果成功,执行标签体
        //如果失败,不执行
    }
}

验证码

强制刷新:除了禁止缓存以外,还需要给服务端传递一个唯一的参数值(没有实际用处)。随机数、时间

img.jsp

<%@ page import="java.awt.*" %>
<%@ page import="java.util.Random" %>
<%@ page import="java.awt.image.BufferedImage" %>
<%@ page import="javax.imageio.ImageIO" %>
<%@ page contentType="image/jpeg;charset=UTF-8" language="java" %>

<%!
     //随机产生颜色值
        public Color getColor(){
            Random ran = new Random() ;//Math.random()  0-1
            int r = ran.nextInt(256) ;
            int g = ran.nextInt(256) ;
            int b = ran.nextInt(256) ;
            return new Color(r,g,b) ;//red green blue  0-255
        }
        //产生验证码值
        public String getNum()
        {
                //  0-8999   1000-9999
            int ran = (int)( Math.random()*9000) +1000 ;
            return String.valueOf(ran) ;
        }
%>
<%
    //禁止缓存,防止验证码过期
    response.setHeader("Pragma","no-cache");
    response.setHeader("Cache-Control","no-cache");
    response.setHeader("Expires","0");
    //绘制验证码
    BufferedImage image = new BufferedImage(80,30,BufferedImage.TYPE_INT_RGB) ;
    //画笔
    Graphics graphics = image.getGraphics();
    graphics.fillRect(0,0,80,30);
    //绘制干扰线条
    for(int i=0;i<60;i++)
    {
        Random ran = new Random() ;
        int xBegin = ran.nextInt(80) ;//55
        int yBegin = ran.nextInt(30) ;
        int xEnd = ran.nextInt(xBegin +10 ) ;
        int yEnd = ran.nextInt(yBegin + 10) ;
        graphics.setColor( getColor());
        //绘制线条
        graphics.drawLine(xBegin,yBegin,xEnd,yEnd);
    }
    graphics.setFont(new Font("seif",Font.BOLD,20));
     //绘制验证码
     graphics.setColor(Color.BLACK);
     String checkCode = getNum() ; //2 1 3 4
    StringBuffer sb = new StringBuffer() ;
     for(int i=0;i<checkCode.length();i++){
         sb.append(checkCode.charAt(i)+" "  )  ;//验证码的每一位数字
     }
    graphics.drawString( sb.toString(), 15,20 );//绘制验证码
    //将验证码真实值 保存在session中,供使用时比较真实性
    session.setAttribute("CKECKCODE"  ,checkCode );
    //真实的产生图片
    ImageIO.write(image,"jpeg",  response.getOutputStream()) ;
    //关闭
    out.clear();
    out = pageContext.pushBody() ;  //<input type="image" src="xxx" />
%>

CheckCodeServlet.java

@WebServlet("/CheckCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      String resultTip = "imgs/wrong.jpg";
        //获取用户输入验证码
       String checkcodeClient =  request.getParameter("checkcode");
       //真实的验证码值
       String checkcodeServer = (String) request.getSession().getAttribute("CKECKCODE");
       if(checkcodeServer.equals(checkcodeClient)){
           resultTip = "imgs/right.jpg";
       }
       response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();//输出流
        writer.write(resultTip);
        writer.flush();
        writer.close();
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <script type="text/javascript" src="jquery-3.3.1.js" >
    </script>
    <script type="text/javascript" >
      function reloadCheckImg()
      {
          $("img").attr("src", "img.jsp?t="+(new Date().getTime()));  //<img src="...">
      }
      $(document).ready(function(){
        $("#checkcodeId").blur(function(){
          var $checkcode = $("#checkcodeId").val();
          //校验   :文本框中输入的值 发送到服务端。
           //服务端: 获取文本框输入的值 ,和真实验证码图片中的值对比 ,并返回验证结果
          $.post(
                "CheckCodeServlet",//服务端地址
                "checkcode="+$checkcode ,
                function(result){//图片地址(imgs/right.jpg   imgs/wrong.jpg)
                //result:  imgs/right.jpg
                var resultHtml =  $("<img src='"+result+"' height='15' width='15px'   />") ;
                $("#tip").html(resultHtml);
                }
            );
        });
      });
    </script>
    <title>验证码</title>
  </head>
  <body>
        验证码:
          <input type="text" name="checkcode" id="checkcodeId" size="4"  />
          <!-- 验证码-->
        <a href="javascript:reloadCheckImg();"> <img src="img.jsp"/></a>
        <span id="tip">  </span>
  </body>
</html>

集群:

apache、 nginx

tomcat:理论上 单节点tomcat能够稳定的 处理请求并发量200-300;

负载均衡

失败迁移

服务端集群:

  1. 水平集群 :将服务器安装在 各个不同的计算机上 (失败迁移)

  2. 垂直集群 :将多个服务器,安装在同一个计算机上 (负载均衡)

  3. 水平+垂直

搭建集群:

apache :特点是处理静态资源(html 图片 js) .这里的apache是一个服务工具,不是 之前理解的 基金组织。

tomcat:特点 可以处理动态资源

apache+tomcat:动静分离

apache:请求的分流操作

下载apache服务器工具下载地址

配置:

conf/http.conf(37行左右)

Define SRVROOT "D:\dev\cluster\Apache24"

看一看将apache配置成windows服务,既可以在服务中启动停止服务:

管理员身份打开cmd,通过命令注册apache服务

"C:\workplace\jar\cluster\httpd-2.4.41-win64-VS16\Apache24\bin\httpd.exe" -k install -n apache24

删除服务

sc delete 服务名

如果在注册apache服务时,提示“丢失VCRUNTIME140.DLL”,则需要下载并安装vc_redist.x64.exe。下载地址

注册成功后,启动,浏览器访问

localhost

查看是否成功访问

准备tomcat:复制两份,改端口

  1. 规划并修改端口: server.xml

            22行                63行          108行
            server端口号   http协议端口    ajp协议端口号
    tomcat-a:   1005        1080            1009
    tomcat-b:   2005        2080            2009
    
  2. 配置引擎Engine : server.xml

    增加jvmRoute(128行)

    <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat-b">
    
  3. 打开集群开关 server.xml(133行)

    打开以下注释

    <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
    

tomcat集群配置搞定,apache启动

->结合apache+tomcat :mod_jk.so

下载地址

配置mod_jk.so:

  1. 存放位置,\Apache24\modules\mod_jk.so

  2. 配置\Apache24\conf\workers.properties

  3. 配置\Apache24\conf\mod_jk.conf (用于加载mod_jk.so和workers.properties)

  4. 配置httpd.conf: 在apache启动时 自动加载mod_jk.conf:

    追加(最后)

    include conf/mod_jk.conf
    

    mod_jk.so,workers.properties ->mod_jk.conf -> apache程序会自动加载httpd.conf

  5. 在项目web.xml中添加

    <distributable/>
    

    表示开启项目同步

部署项目将项目分别部署到tomcat中

依次启动apache、tomcata、tomcatb 访问

http://localhost/ClusterProject/

workers

workers.properties

worker.list=controller,tomcata,tomcatb
#tomcata
worker.tomcata.port=1009
worker.tomcata.host=localhost
worker.tomcata.type=ajp13
#负载均衡的权重
worker.tomcata.lbfactor=1
#tomcatb
worker.tomcatb.port=2009
worker.tomcatb.host=localhost
worker.tomcatb.type=ajp13
#负载均衡的权重
worker.tomcatb.lbfactor=2
#controller
worker.controller.type=lb
worker.controller.balanced_workers=tomcata,tomcatb
worker.controller.sticky_session=false

分布式session策略:

  1. sticky:固定将每一个用户的请求 分给特定的服务器,后期的请求不会分给其他服务器

    弊端:无法失败迁移

  2. session广播(自动同步session): 自动同步session,

    弊端:如果服务器太多,可能造成广播风暴(将一个服务器的session,需要同步到其他所有的服务器中)

  3. 集中管理方式(推荐):将各个服务器的session集群存储到一个 数据库中

mod_jk.conf

#加载mod_jk.so
LoadModule jk_module modules/mod_jk.so
#加载workers.properties
JkWorkersFile conf/workers.properties
JkMount /* controller

路径以apache的根目录开始找

其中JkMount /* controller,表示拦截一切请求。也可以只拦截jsp: /*.jsp

集群

应用阶段、部署实施

CATALINA_HOME会使 启动tomcat时 自动开启CATALINA_HOME指定的tomcat。而集群中 需要开启多个不同的tomcat,因此 在单机环境下,需要删除CATALINA_HOME。

依次启动apache、tomcata、tomcatb

原因:

apache之前已经成功运行,但是重启时 失败

分析思路:

成功运行的时机:第一把apche下载完后

失败时机: apache+tomcat整合

得出结论: 整合

测试:失败迁移 负载均衡 session共享

细节:

  • apache:

    worker.list=controller,tomcata,tomcatb
    

    流程:apache->workers.properties中配置的 ip:端口 找到具体的tomcat服务(与tomcat中的jvmRoute=“tomcat-a”的没有关系)

  • tomcat配置:(只需要保证 所有tomcat的jvmRoute不能重名即可 ,可以和apache中的命名不一致)

    jvmRoute="tomcat-a"
    jvmRoute="tomcat-b"
    
  • tomcat服务的目录名:(任意)

    tomcata
    tomcatb