JAP&SERVLET_EL表达式

jefxff 153,616 2020-04-30

EL : Expression Language, 是可以替代JSP中的Java代码

传统的在JSP中用Java代码显示数据的弊端:

  • 类型转换
  • 需要处理null
  • 代码参杂

EL示例

  1. $
    ${域对象.域对象中的属性.属性.属性.级联属性}
  2. EL 操作符
    • 点操作符 . 使用方便
    • 中括号操作符 [] 功能强大; 可以包含特殊字符(. \ - ); 也可以获取变量名(如: 变量name, 则可以${requestScope[name]}); 可以访问数组
  3. 通过el操作符可以直接拿数组, Map的值
    • ${requestScope.hobbies[0]}; 即可拿到String数组的第一个元素的值
    • $ 即可拿到Map类型的key为cn的value值
  4. EL 关系运算符
    • 大于 >(或: gt); 大于或等于 >=(或: ge); 等于 == (或: eq); 小于或等于 <=(或: le); 小于 <(或: lt); 不等于 !=(或: ne)
  5. EL 逻辑运算符
    • 逻辑或 ||(或: or); 逻辑与 &&(或: and); 逻辑非 !(或: not)
  6. empty 运算符
    • 判断一个值是否为unull, 或者判断这个值不存在; 如果这个值为null, 或者不存在, 则返回true

EL表达式的隐式对象

  • 隐式对象就是不需要new就可以使用的对象, 自带的对象

  • 作用域访问对象(EL域对象)

    • pageScope, requestScope, sessionScope, applicationScope
    • 如果不指定域对象, 则默认会根据从小到大的顺序, 依次取值
  • 参数访问对象

    • 获取表单数据 $ 或 ${paramValues.hobbies[0]}, 即可拿到表单提交的数据
    • 获取超链接中传递的值: 如超链接: <\a href="el.jsp?fileName=jiefxff">jefxff</\a>; 使用 $ 即可拿到超链接中URL重写拿到的值
  • JSP隐式对象

    • pageContent: 在JSP中可以通过pageContent获取JSP的其他隐式对象
    • EL 的隐式对象含有一个JSP的一个隐式对象pageContent, 通过该隐式对象即可拿到JSP的其他隐式对象; 因此在EL中使用JSP隐式对象, 就可以通过pageContent间接获取
    • 模板: ${pageContent.方法名去掉()和get并且首字母小写}
    • ${pageContent.getSession()} --简化--> $
    • 也可以使用此方法级联获取对象: &

JSTL: 比EL更加强大

  • 需要引入两个jar包 jstl.jar(1.2), stardard.jar(1.1.2)
  • 在Intellij 中还要再settings->schemas and DTDs 中, 将URL映射到对应的Location(即点击右边加号再URL中填入"http://java.sun.com/jsp/jstl/core"; 再File中选择standard-1.1.2.jar中的c.tld); 这样才可以使用
  • 引入一个 taglib 指令(引入一个库, 并且加一个前缀): <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>; 其中: prefix="c" : 前缀

核心标签库

通用标签库(prefix="c" 那么前缀就是c)

  • 赋值语句: <c:set />
    • 在某个作用域中给变量赋值; <c:set var="变量名" value="变量值" scope="4个范围的作用域其中一个" />
    • 在某个作用域中给某个对象的属性赋值(不能指定scope="request");<c:set target="${对象}" propery="对象的属性" value="赋值"/>
  • 显示: <c:out /> 相当于EL, 但是比EL强大(结合例子实践)
    • c:out显示的好处就是, 不存在的数据, 直接显示空白; 也可以通过default="Xxx"来设置当数据不存在时显示default中的值
    • escaleXml属性为true可以使c:out中value里面的数据原封不动的显示出来; 而为false的话, value中的数据会被浏览器解析
  • 删除: <c:remove />

条件标签库

  • 单重if条件
    • <\c:if test="这里写条件判断语句" var="var变量是保存结果的" scope="这里指定域" > </\c:if>
  • 多重选择(类似与switch或多重if,else if, else结构)
    • <c:choose> <c:when test="..." > </c:when> <c:when test="..." > </c:when> <c:otherwise> </c:otherwise> </c:choose>
    • c:choose 标签是选择标签, 只能配合其子标签 c:when 和 c:otherwise 来使用; 当c:when 中test="${判断条件}"为真时执行其中的代码

迭代标签库

  • 循环
    • c:forEach 基础迭代标签, 接收多种集合类型

核心标签库代码实例

  • 通用标签库代码示例
<body>
    <%-- 下面这句话的意思就是在request作用域里面赋了一个变量名:name, 变量值:zs
        相当于 reqest.setAtttibute("name", "zs")--%>
    <%-- 相当于给不存在的变量赋值 --%>
    ---------------------------给某个变量赋值---------------------------
    <c:set var="name" value="zs" scope="request"/>

    EL获取JSTL的值: ${requestScope.name}
    <br>
    ---------------------------给普通对象的属性赋值---------------------------
    <br>
    <%-- EL获取对象的属性 --%>
    ${requestScope.student.sname}
    <br>
    <%-- JSTL 通过target配合EL拿到student对象, 通过 propery拿到属性名, 通过 value进行新的赋值 --%>
    <c:set target="${requestScope.student}" property="sname" value="zxs"/>
    <br>
    <%-- 测试是否覆盖了sname的值 --%>
    ${requestScope.student.sname}
    <br>
    ---------------------------给Map对象的属性赋值---------------------------
    <br>
    <%-- 拿到map对象 --%>
    ${requestScope.map.cn}
    <br>
    <%-- JSTL 通过target配合EL拿到map对象, 通过 propery拿到key为cn, 通过 value进行新的赋值 --%>
    <c:set target="${requestScope.map}" property="cn" value="CHINA"/>
    <br>
    <%-- 拿到map对象 --%>
    ${requestScope.map.cn}
    <br>
    ================================<\c:out />===================================
    <br>
    传统的EL模式: ${requestScope.student}
    <br>
    c:out模式: <c:out value="${requestScope.student}"/>
    <br>
    <%-- c:out显示的好处就是, 不存在的数据, 直接显示空白; 也可以显示default的值--%>
    c:out显示不存在的数据: <c:out value="${requestScope.stud}" default="zs-23"/>
    <br>
    <a href="www.baidu.com">百度</a><br>
    <%-- escapeXml属性为true可以使c:out中value里面的数据原封不动的显示出来; 而为false的话, value中的数据会被浏览器解析 --%>
    <c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="true" />
    <br>
    <c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="false" />
    <br>
    ================================c:remove删除对象===================================
    <br>
    通过c:set设置一个值: <c:set var="a" value="b" scope="request"/>
    <br>
    通过EL显示这个值: ${requestScope.a}
    <br>
    通过c:remove删除这个值: <c:remove var="a" scope="request"/>
    <br>
    测试是否删除这个值: ${requestScope.a}
</body>
  • JSTL单重的if
<%--<c:if test="这里写条件判断语句" var="va变量是保存结果的" scope="这里指定域" >--%>
<c:if test="${10>2}" var="result" scope="request" >
        真
        <br>
    <c:out value="${requestScope.result}" default="没有赋值, 这是显示的默认值" />

    </c:if>

Filter 过滤器

FIlter实现

  • 要想将一个普通的Class变成一个具有功能的类(过滤器, 拦截器...), 要么继承父类, 要么添加一个注解
  • 实现 javax.servlet.Filter 接口后, 要重写其中的 init(), doFilter(), destory() 三个方法
  • 过滤器需要在web.xml中配置, 配置方法和web.xml配置Servlet是一样的
  • 通过配置后, 会使用doFilter()方法拦截请求和响应, 并且可以通过 servletChain.doFilter(request, resopnse)方法放行

Filter 映射

  • 通过使用通配符配置 web.xml中的url-pattern可以拦截任何请求; 如 /*
  • doFilter中方法参数是ServlertRequest, 而在Servlet中是HTTPServletRequest
  • dispatcher请求方式:
    • REQUEST: 拦截HTTP请求 get post
    • FORWAED: 只拦截通过请求转发方式的请求
    • INCLUDE: 只拦截通过 request.getRequestDispatcher("").include(),通过 <jsp: include page="" />此种 方式发出的请求
    • ERROR: 只拦截 发出的请求

FIlter 过滤器链条

  • 可以有多个 Filter 类
  • 在web.xml中 哪个Filtre-mapping在前, 那么那个拦截就在前; 即Filter拦截的顺序是由在web.xml中的顺序决定的
  • 拦截顺序: 客户端发出请求 --> Filter1拦截请求 --(放行)--> Filter2拦截请求 --(放行)--> 服务器响应 --> Filter2拦截响应 --(放行)--> Filter1拦截响应 --(放行)--> 客户端页面

监听器

  • 每个监听器各自提供了两个方法: 监听开始, 监听结束
  • ServletContext在Servlet容器创建时创建, 在容器关闭时销毁

监听对象的创建和销毁

  • request: 实现 ServletRequestListener 接口
  • response: 实现 HttpSessionListener 接口
  • applocation: 实现 ServletContextListener 接口

监听对象中属性的变更

  • request: 实现 ServletRequestAttributeListener 接口
  • response: 实现 HttpSessionAttributeListener 接口
  • applocation: 实现 ServletContextAttributeListener 接口

监听器开发步骤

  • 编写监听器, 实现接口
  • 配置 web.xml

Session 的钝化和活化

  • 钝化: 内存 --> 硬盘
  • 活化: 硬盘 --> 内存

Session对象的四种状态

监听绑定和解绑: HttpSessionBindingListener (不需要配置web.xml)

  • session.setAttribute("a", Xxx) ; 将对象a绑定到session中
  • session.removeAttribute("a") ; 将对象a从session中解绑

监听session的钝化和活化: HttpSessionActivationListener

  • 钝化活化的本质就是序列化和反序列化

  • 钝化

    • 编写类实现HttpSessionActivationListener和Serializable接口, 创建该类的实例, 然后将对象添加到Session中,
    • 配置Tomcat 安装目录下的/conf/context.xml
    • 运行项目, 当超过设定的时间之后, 添加到session中的属性会随着Session钝化到指定的目录中
  • 活化

    • 活化不需要什么实际的操作, 直接通过 EL 取值就可以
    • session中获取某一个对象时, 如果该对象在内存中不存在, 则直接尝试从之前该对象钝化的文件中去获取
  • 总结

    • 钝化活化实际执行是通过 context.xml中进行配置而进行的
    • HttpSessionActivationListener 只负责在session钝化和活化时予以监听
    • 需要实现Serializable接口

如何钝化, 活化

  • 配置 Tomcat 安装目录下的/conf/context.xml
<!-- 通过配置实现钝化和活化
	className="org.apache.catalina.session.PersistentManager" : 固定写法
	MaxIdleSwap: 最大空闲时间, 如果超过该时间, 将会被钝化
	FileStore: 通过该类是实现具体的钝化操作
	directory: (相对路径)绝对路径(相对路径是相当于Tomcat中work里面此次项目的路径); 但是最好是自己配置的专门存放Sessiond的路径
-->
<Manager className="org.apache.catalina.session.PersistentManager" MaxIdleSwap="1" >
	<Store className="org.apache.catalina.session.FileStore" directory="jeff" />
	</Manager>

Ajax: 异步js和xml

  • 异步刷新技术: 如果网页中某一个地方需要修改, 异步刷新可以使, 只刷新需要修改的地方, 而页面的其他地方保持不变,

实现:

js : 依赖 XMLHttpRequest对象

  • XMLHttpRequest对象的方法:
    • open(方法名(提交方式get|post), 服务器的地址, true) : 与服务端建立连接(为true: 异步刷新, false:全局刷新)
    • send()
      • get : send(null)
      • post : send(参数值)
    • setRequestHeader(header, value)
      • get: 不需要设置此方法
      • post: 需要设置
        • 如果请求元素中包含了文件上传: setRequestHeader("Content-Type", "multipart/form-data")
        • 如果没有文件上传: setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  • XMLHttpRequest对象的属性
    • readystate: 请求状态(只有状态为4标识完成)
      • 0 : 表示 XMLHttpRequest 对象没有初始化
      • 1 : 表示 XMLHttpRequest 对象开始发送请求, 已经执行了 open() 方法并完成了相关资源的准备
      • 2 : 表示 XMLHttpRequest 对象已将请求发送完毕, 已经执行了send()方法来发送请求, 但是还没有收到响应
      • 3 : 表示 XMLHttpRequest 对象开始读取响应信息, 已经接收到HTTP响应的头部信息, 但是还没有将响应体接收完毕
      • 4 : 表示 XMLHttpRequest 对象将响应信息全部读取完毕
    • status: 响应状态(只有200代表正常)
      • 200 : 服务器正常响应
      • 400 : 无法找到请求的资源
      • 403 : 没有访问权限
      • 404 : 访问的资源不存在
      • 500 : 服务器内部错误(服务器内部代码错误)
    • onreadystatechange: 回调函数
    • responseText / responseXML : 响应格式为Text或者XML

jquery: 推荐

通用方式既可以是post, 也可以是get

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

	}
	error:function(xhr,errorMessage, e){

	}
	});

get方式

$.get(
	服务器地址,
	请求数据,
	function (result) {
		// body...
	}
	"text"| "xml"| "json");// 直接写括号里的

post方式

$.post(
	服务器地址,
	请求数据,
	function (result) {
		// body...
	}
	"text"| "xml"| "json");// 直接写括号里的

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

$(选择器).load(
	服务器地址,
	请求数据
	);
	

JSON方式

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

	});

js 代码示例

  • post请求方式
<script type="text/javascript">
        // index.jsp 是通过post方式将值传递给服务端
      function register() {
         var mobile =  document.getElementById("mobile").value;
         // 通过Ajax异步的方式提交服务端
         // 创建 XMLHttpRequest 对象
          xmlHttpRequest  = new XMLHttpRequest();

          // 设置xmlHttpRequest的回调函数
          xmlHttpRequest.onreadystatechange = callBack;

          // 建立链接
          xmlHttpRequest.open("post", "mobileServlet", true);
          // 设置post方式的头消息
          xmlHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

          // 注意:
          // 注意send方法: send("mobile= " + mobile);和 send("mobile=" + mobile);是有大区别, 这个方法中不能有空格
          xmlHttpRequest.send("mobile=" + mobile); // 发送的方式为: k=v
      }
      // 定义回调函数(接收服务端的返回值)
      function callBack(){
        if(xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200){
          // 接收服务端返回的数据
            var data = xmlHttpRequest.responseText; // 服务端返回值为String格式
            if(data == "true"){
                alert("此号码已存在, 请更换!")
            } else {
                alert("注册成功!")
            }
        }
      }
    </script>

Jquery 方式


<script type="text/javascript">
    // index.jsp 是通过post方式将值传递给服务端
    function register()
    {
        var $mobile =  $("#mobile").val();
        // ajax 方式
    /*    
        $.ajax({
            url:"mobileServlet",
            请求方式:"post",
            data:"mobile="+$mobile,
            success:function(result, testStatus)
            {
                if(result == "true"){
                    alert("号码已存在, 注册失败!");
                }else{
                    alert("注册成功!");
                }
            },
            error:function(xhr, errorMessage, e) {
                alert("系统异常!");
            }
        });
    */
    	// get 方式
	/*
    	$.get(
    		"mobileServlet",
    		"mobile="+$mobile,
    		function(result){
				if(result == "true"){
                    alert("号码已存在, 注册失败!");
                }else{
                    alert("注册成功!");
                }
    		},
    		"text"
    		);
	*/
		// post 方式
		$.post(
                "mobileServlet",
                "mobile="+$mobile,
                function (result) {
                    if(result == "true"){
                        alert("号码已存在, 注册失败!");
                    }else{
                        alert("注册成功!");
                    }
                },
            "text"
        );
    }
</script>

idea中Tomcat乱码:

  1. file - settings - 搜索 File Encodings, 全部改为UTF-8
  2. 打开IEDA安装目录, 在 idea64.exe.vmoptions 和 idea.exe.vmoptions 最后追加 -Dfile.encoding=UTF-8
  3. 配置tomcat页面中, VMoptions设置: -Dfile.encoding=UTF-8

idea热部署问题

  1. 还是在run下面的部署页面操作
    • On 'Update' action: 选择 Update classes and resources
    • on frame deactovation: 选择 Update classes and resources
  2. java 代码页生效
    • run启动: 仅JSP等静态资源有效
    • debug启动: java和jsp等均有效

idea中jar包问题

问题

  • Java项目: 直接将jar复制到工程中, 右键 -Add as Library
  • web项目:
    • Eclipse: Web-Content/lib/*.jar, Eclipse会将 Web-Content/lib/中的所有jar存放再项目的全部生命周期中
    • idea: 会将web/lib/*.jar 中的所有jar,只在项目运行阶段生效, 其他阶段不生效

解决方案

  • 如果通过gradle/maven, 则不用管这些
  • 如果不是, 则需要手工解决
    1. jar包本身只在运行阶段有效(如: 数据库连接jar):
      • ①只需要将jar复制到web/lib/中(可能会延迟)
      • ② 在 Structure -> Artifacts -> output 中在创建一个lib, 将 jar放到里面
    2. jar包再各个阶段都有效(如: commons-dbcp.jar 在开发, 运行均有效)
      • ① 将 commons-dbcp.jar 粘贴在 web/lib中
      • ② 将jar复制到工程src中, 右键 -Add as Library

总结:

  1. Java项目: 直接将jar复制到工程中, 右键 -Add as Library
  2. web项目:
    • jar包本身只在运行阶段有效(如: 数据库连接jar):
      • ①只需要将jar复制到web/lib/中(可能会延迟)
    • jar包再各个阶段都有效
      • ①只需要将jar复制到web/lib/中(可能会延迟)
      • ②将jar复制到工程src中, 右键 -Add as Library

JNDI : Java命名目录接口

  • 多项目共享同一个变量
  • pageContext(当前页面) < request(一次请求) < session(一次会话) < application(项目运行期间)
  • JNDI, 就是将某一类资源(对象), 以配置文件(tomcat/conf/cotext.xml)的形式写入
  • 固定前缀: 通过上下文对象的lookup()方法查找变量是, 方法名参数有个固定写法: lookup("java:comp/env/xxxxName")

使用步骤

  • 配置文件(tomcat/conf/cotext.xml)中添加:
  • JSP中通过获取上下文对象, 调用上下文对象的lookup()方法就可以

连接池

  • 普通的数据库连接在打开和关闭连接时比较损耗性能, 而且是和数据库的直接交互, 连接池就是一个维护数据库连接的管理员, 手中经常有数据库的连接, 当需要连接的时候, 直接从管理员手中拿, 用完了之后还给管理员, 而且不需要直接和数据库交互
  • 常见的连接池: Tomcat-dbcp, dbcp, c3p0, druid
  • 数据源(javax.sql.Datasource)包含了数据池, 数据源可以管理数据池
  • 以前需要Class.forName()加载驱动, 通过DriverManager直接从数据库获取连接; 而用连接池的核心就是: 将连接的指向改了, 现在指向的是数据源而不是数据库
  • 数据库访问的核心 --> pstmt/stmt -> Connection -> 1. 直连数据库 2. 数据源 (ds.getConnection())

Tomcat-dbcp:

  • 1.类似于 jndi, 在Context.xml中配置下面的MySQL Resource
  • 2.MySQL配置
	<Resource
		<!-- name指定Resource的JNID名字 -->
		name="jdbc/mysql"
		<!-- 指定Resource的管理者, 有两个可选: Container和Applocation;Container:由容器来创建Resource; Application: 由Web应用来创建和管理Resource -->
		auth="Container"
		<!-- type: 指定Resource的类型 -->
		type="javax.sql.DataSource"
		<!-- 指定连接池中, 处于活动状态的数据库连接的最大数量, 如果值为0, 标识不受限制 -->
		maxActive="100"
		<!-- 指定连接池中, 处于空闲状态的数据库连接的最大数量, 如果值为0, 标识不受限制  -->
		maxIdle="30"
		<!-- 指定连接池中,连接处于空闲状态的最长时间(单位为毫秒), 如果超出此最长时间将会抛出异常; 如果值为-1, 表示允许无限制等待 -->
		maxWait="10000"
		<!-- 指定数据库访问名 -->
		username="root"
		<!-- 指定数据库访问密码 -->
		password="123456"
		<!-- 指定连接数据库的驱动程序的类名 -->
		driverClassName="com.mysql.cj.jdbc.Driver"
		<!-- 指定连接数据库的RUL -->
		url="jdbc:mysql://127.0.0.1:3306/blog?useSSL=false&amp;serverTimezone=UTC"
		/>
  • 3.在项目里面的web.xml中指定context.xml里面配置的数据源; 就是指定name,type, auth三个属性的值
  • 4.在DBUtils.java 中将之前通过DriverManager.getConnection(URL, NAME, PWD)获取连接改为通过 Context ctx = new InitialContext();先获取Context.xml; 再通过 DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mysql");获取DataSource对象, 再通过DataSource对象获取连接 Connection conn = ds.getConnection();

dbcp 连接池

  • 需要引入jar包 commons-dbcp-1.4.jar, commons-pool.jar
  • 获取ds的核心类(二选一): BasicDataSource , BasicDataSourceFactory

BasicDataSource 方式(硬编码方式)

  • 1.创建 BasicDataSource dbcp = new BasicDataSource();
  • 2.通过下面方法设置各种属性即可拿到 dbcp
    1. 通过 dbcp.getConnrction()获取数据库连接即可
常用方法
  • void serDriverClassName(String driverClassName) : 设置连接数据库的驱动名
  • void setUrl(String url) : 设置连接数据库的URL
  • void setUsername(Stirng Username) : 设置连接数据库的用户名
  • void setPassword(Stirng Password) : 设置连接数据库的木马
  • void setInitialiSize(int initialSize) : 设置初始化时, 连接数据池中的连接数量
  • void setMaxActive(int maxActive) : 设置连接池中, 处于活动状态的数据库连接最大数量
  • void setMinIdle(int minIdle) : 设置连接池中, 处于空闲状态的数据库连接的最小数量
  • Collection getConnection() : 从连接池中获取一个数据库连接

BasicDataSourceFactory 配置方式(dbcpconfig.properties配置文件; 全部是k=v)

  • 编写dbcpconfig.properties配置文件(格式k=v中间不要由空格)
  • 通过创建 Properties 类的实例, 调用该实例的load()方法将配置文件加载进来; 需要注意的是load()方法的参数是流的形式
    • 创建 Properties 实例: Properties props = new Properties();
    • 将字符串编程输入流: InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
    • 调用load()方法: props.load(input);
  • 通过 DataSource dbcp = BasicDataSourceFactory.createDataSource(prop); 方法即可创建dbcp

C3P0 连接池

  • 核心类 ComboPooledDataSource
  • C3P0是将DBCP的两种方式(硬编码和配置文件)合二为一; 通过ComboPooledDataSource的构造参数来区分, 无参数的就是硬编码; 而有参数的就是通过配置文件的方式
  • 无参数构造方法步骤:
    • 创建 ComboPooledDataSource c3p0 = new ComboPooledDataSource(); 对象
    • 通过 c3p0的set方法设置Driver,Url,User,Passwd等
    • return c3p0; 即可
    • 连接时 通过 c3p0.getConnection()即可
  • 有参数构造方法, 配置文件(c3p0-config.xml)
    • 在src中编写c3p0-config.xml文件, 注意的点: Url中出现分号(;)时, 应该使用(amp;)这种形式
    • 使用时直接 return new ComboPooledDataSource("jefxff"); 即可返回 c3p0

C3P0的核心类ComboPooledDataSource常用方法

  • public ComboPooledDataSource() : 无参构造方法(硬编码发方式)
  • public ComboPooledDataSource(String configName) : 加载配置文件的构造方法
  • void setDriverClass(String driverClass) : 设置数据库连接的驱动
  • void setJdbcUrl(String jdbcUrl) : 设置连接数据库的URL
  • void setUser(Stirng User) : 设置数据库连接的用户名
  • void setPassword(String password) : 设置数据库连接的密码
  • void setMaxPoolSize(int maxPoolSize) : 设置连接池的最大连接数目
  • void setMinPoolSize(int minPoolSize) : 设置连接池的最小连接数目
  • void setInitiaPoolSize(int initiaPoolSize) : 设置初始化时, 连接池中的连接数量
  • Connection getConnection() : 从连接池中获取一个数据库连接, 该方法由ComboPooledDataSource 的父类 AbstractPoolBackedDataSource提供

连接池总结

  • 硬编码: 获取某个连接池数据源的对象 ds = new XxxDataSource(); ds.setXxx(); return ds;
  • 配置文件: 编写配置文件, ds = new XxxDataSource(); 加载配置文件; return ds;

Apache DButils

  • 下载 commons-dbutils-1.7-bin.zip 文件, 解压, 主要使用 commons-dbutils-1.7.jar 文件, 包括几个重点类: DbUtils, QueryRunner, ResultSetHandler
  • DbUtils: 打开关闭连接, 提交事务
  • QueryRunner : 增删改查方法的基础, 必须创建该类的实例, 通过该类的实例来调用query或者update方法来执行增删改查; 其中该实例参数可以是一个DataSource实例, 又DS实例后是自动提交事务
    • updte()
    • query()
  • ResultSetHandler接口 : 有很多的实现类, 一个实现类对应于一种不同 的查询结果类型

通过 ResultSetHandler接口的实现类来实现查询

  • ArrayHandler实现类: 返回结果集中的第一行数据, 并用Object[]接收
  • ArrayListhandler实现类: 返回结果集的多行数据, 接收类型是 List<Obkect[]>
  • BeanHandler实现类: 返回结果集的第一行数据, 并将结果集放在Bean里,即对象Student里面. (反射会通过无参构造来创建对象)
  • BeanListHandler实现类: 返回结果集的多行数据,接收类型为 List
  • BeanMapHandler实现列: 返回结果集的多行数据, 接收类型为 Map<>(如果是Oracle数据库, Java中对应的Oracle默认的数值类型 BigDecimal(数字通过 new BigDecimal(int) 方法转换为BigDecimal))
  • MapHandler实现类: 返回一行以Map形式包装的数据
  • MapListHandler实现类: 返回多行以List<\map>形式包装的数据
  • KeyedHandler: 返回多行数据, 并且给每个添加数据添加字段
  • ColumnListHandler : 把结果集的 某一列保存在List中
  • ScalarHandler: 查询单值结果型

增删改

  • 自动提交事务; QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());即创建QueryRunner时指定数据源; 直接通过runner调用update方法, 调用时具体的实现和之前学习PreparedStatement的增删改操作一样, 即写SQL语句, 通过 Object[] 数组指定参数即可
    • runner.update(sql); 和 runner.update(sql, 参数);
  • 手动提交事务, QueryRunner runner = new QueryRunner(); 即创建QueryRunner实例时并没有传入ds, 那么通过runner调用update()方法时,第一个参数需要传入一个Connention对象
    • runner.update(Connection, sql, 参数);

关于数据库Connection连接的问题

  • 问题: 如果创建Connection的方法是static修饰的, 那么conn是共享的, 就会存在线程不安全问题, 即A刚拿到连接, 而B刚好关闭连接, 这样就会线程不安全; 解决这种现象的方法:如果不用static修饰, 每个Connecttion都是私有的, 每次使用都需要new一个, 那么性能太低; 如果加锁, 或者synchronized修饰, 就会存在排队获取连接的现象, 造成性能下降;
  • 解决: ThreadLocal : 既可以保证数据安全, 又可以保证性能
    • ThreadLocal: 可以为每个线程复制一个副本, 每个线程可以访问自己内部的副本, 所以有个别名: 线程本地变量
    • 常用方法:
      • get(); 从ThreadLocal中获取一个变量(副本)
      • set(); 给ThreadLocal中放入一个变量
      • remove(); 删除副本

关于事务安全的问题

  • 问题:

    • 对于数据库来说, 一个连接对用于一个事务, 一个事务可以包含多个DML(增删改)操作
    • 如果给每个DAO操作都创建一个Connection, 则多个DAO操作对应于多个事务, 但是一般来讲, 一个业务(Servuce)中的多个DAO操作应该包含在一个事务中
  • 解决: ThreadLocal : 在第一次DAO操作时, 真正的创建一个Connection对象, 然后在其他几次DAO操作时, 借助于ThreadLocal的自身特性, 自动将改Connection复制多个(Connection只创建了一个,因此该Connection中的所有操作必然对应于同一个事务; 并且ThreadLocal在使用层面复制了多个, 因此可以完成多个DAO操作)

  • 事务流程: 开启事务(将自动提交关闭, 变成手动提交) -> 进行各种DML操作 -> 正常: 将刚才的所有DML全部提交 (全部成功)
    -> 失败(异常), 将刚才的所有DML操作回滚 (全部失败)

元数据 (描述数据的数据)

元数据分类 (主要记怎么来的)

  • 数据库元数据 DataBaseMeteData dmd = conn.conn.getMetaData();
  • 参数元数据 ParamentMeteData pmd = pstmt.getParamentMeteData();
    • mysql使用参数元数据需要在URL后面追加: ?generateSimpleParamentMetedate=true
  • 结果集元数据 ResultSetMeteData = rs.getResultSetMeteData();

自定义标签

  • 传统方式(JSP1.0) : 实现Tag接口
  • 简单方式(JSP2.0) : 实现SimpleTag接口

传统方式自定义标签步骤

  • 编写标签处理类
    • 传统方式(JSP1.0) : 实现Tag接口: 实现 javax.servlet.jsp.tagext.Tag接口; 主要方法: doStartTag()
    • 简单方式(JSP2.0) : 实现SimpleTag接口 实现 javax.servlet.jsp.tagex.SimpleTag 接口; 主要方法: doTag()
    • 如果JSP在编译阶段发现了自定义标签xx:yyy, 他就交给doStartTag()或者doTag()方法
  • 编写标签描述符(tld文件)
    • 找一个别人的标签语言(el, jstl)的tld文件抄一下
  • 导入并使用
    • 位置: myTag.tld只能放在WEB-INF目录或者其子目录下(除了 lib, classes目录之外)

    • 使用: 引入具体要使用的tld文件: <%@ taglib uri="..." prefix="c" %>; uri:每个tld文件的唯一描述符; prefix:使用tld文件的前缀

    • 具体的使用:

      • 空标签:没有标签体<c:foreach></c:foreach>
      • 带标签体: 带标签体<c:foreach>xxx</c:foreach>
      • 代属性: <c:foreach collection="$">xxx</c:foreach>
    • Tag接口中的所有方法的执行逻辑

      • 当JSP容器(Tomcat, jetty)在将jsp文件翻译成.java文件并编译执行时, 如果遇到JSP中有标签, 就会依次执行 setPageContext(), setParent(), doStartTag(), doEndTag(), realease()方法用于解析标签中的执行逻辑
    • IterationTag接口(Tag接口的子接口)

      • doAfterBody(): 方法有两个返回值:
        • 返回 (EVAL_BODY_AGAIN = 2): 重复执行
        • 返回 (SKIP_BODY = 0) : 结束执行
    • BodyTag: 在标签体被显示之前, 进行一些其他"额外"的操作

      • 属性 int EVAL_BODY_BUFFER = 2; 是 doStartTag()方法的第三个返回值, 代表一个缓冲区(BodyContent); BodyContent是一个抽象类, 具体的实现类是 BodyContentImpl(Tomcat包下的jasper.jar, 需要导入此包)
      • BodyContent的用途:
        • 如果返回值是 EVAL_BODY_BUFFER, 则服务区会自动将标签体需要显示的内容放入缓冲区(BodyContent), 因此, 如果要更高改显示结果, 只需要从缓冲区获取原来的数据进行修改即可
        • 通过缓冲区获取数据: getBodyContent().getString();自己通过.toUpperCase()方法转大写, 通过bodyContent.getEnclosingWriter().write(content);输出即可

实例

  1. 编写自定义标签, 将hello循环输出指定次数
  2. 编写自定义标签, 将hello转换大写输出

简单方式自定义标签核心

  • 最大的简化: 将传统的的 doStartTag(), doEndTag(), doAfterBody() 等方法简化为一个通用的 doTag()方法
  • doTag(): 传统方式可以对标签的最终结果显示进行修改, 核心是 缓冲区(BodyContent); 而简单方式没有缓冲区, 是通过 javax.servlet.jsp.tagext.JspFragment类: 代表一块JSP元素(该块不包含scriptlet, 因此简单方式的tld文件中不能选JSP, 而是选 scriptless)
  • JspFragment中有一个invoke(writer var1)方法, 参数是流, 如果要修改显示内容, 只需要修改次流即可
  • invoke(Writer var1): doTag()每调用一次invoke()方法, 标签体就执行一次
  • 简单标签的核心是 SimpleTag接口, 其实现类是 SimpleTagSupport, 而SimpleTag接口中的核心是 JspFragment; JspFragment是通过SimpleTagSupport实现类获得的

部署实施时使用的集群

  • Apache Nginx
  • Tomcat: 理论上单节点Tomcat能够稳定的处理请求并发量200-300
  • 使用集群: 负载均衡, 失败迁移
  • 服务端集群:
    • 水平集群(失败迁移)将服务器安装在各个不同的计算机上;
    • 垂直集群(负载均衡): 一台计算机上安装多个服务

1. 搭建Apache集群:

  • apache: 特点是处理静态资源)(HTML, img, js)
  • tomcat: 特点是处理动态资源
  • apache + tomcat: 实现动静分离

1.1 Apache 请求的分流操作

  • 下载Apache服务器工具: 网址, 或者官网
  • 将下载的Apache解压到D:/dev/cluster目录下

1.2 配置 Apache

    1. 打开 conf/httpd.conf 文件; 修改: Define SRVROOT "D:\dev\cluster\Apache24"
    1. 将Apache配置成Windows服务
    • 以管理员身份打开CMD, 执行命令
    • "D:\dev\cluster\Apache24\bin\httpd.exe" -k install -n apache24
    • 可能报错丢失 VCRUNTIME140.DLL, 需要下载安装 vc_redist.x64.exe 下载地址
    • 删除Apache服务: sc delete apache24
    1. 注册成功后, 查看localhost是否注册成功

1.3 配置tomcat

  • 将Tomcat复制两份, 分别命名tomcat-a, tomcat-b 均放在D:\dev\cluster
  • 规划端口(/server.xml):
    • Tomcat-a:server端口: 1005; http端口:1080; ajp端口: 1009
    • TOmcat-b:server端口: 2005; http端口:2080; ajp端口: 2009
  • 配置Engine(/server.xml):
    • Tomcat-a:
    • Tomcat-b:
  • 打开集群开关(/server.xml)
    • 将以下注释打开:

1.4 将配置好的Apache和Tomcat结合起来

-需要下载mod_jk.so, 下载后解压将mod_jk.so文件复制到Apache的modules目录下

  • 在Apache/conf目录下新建 workers.properties文件
  • workers.properties文件内容
worker.list=controller,tomcat-a,tomcat-b
# tomcat-a
# 配置端口号
worker.tomcat-a.port=1009
# 配置ip地址, 部署时就是IP地址, 本机演示就是localhost
worker.tomcat-a.host=localhost
# 配置 
worker.tomcat-a.type=ajp13
# 配置负载均衡的比重
worker.tomcat-a.lbfactor=1

# tomcat-b
# 配置端口号
worker.tomcat-b.port=2009
# 配置ip地址, 部署时就是IP地址, 本机演示就是localhost
worker.tomcat-b.host=localhost
# 配置 
worker.tomcat-b.type=ajp13
# 配置负载均衡的比重
worker.tomcat-b.lbfactor=2

# controller 
# 配置分流器的类型(负载均衡)
worker.controller.type=lb
# 配置两个需要分流的列表
worker.controller.balanced_workers=tomcat-a,tomcat-b
# true: session使用sticky策略(固定在一个服务器); false: session使用session广播的策略
worker.controller.sticky_session=false
1.4.1 配置worker.properties是session策略:
  • sticky策略: 固定将每一个用户的请求分配个特定的服务器, 后期的请求不会分给其他的服务器; 弊端是无法失败迁移(理解就是x第一次通过Apache访问了tomcat-a, tamcat-a保存了x的session, 后期x再来时, 指定是配分配到tomcat-a, 而不会分给别的服务器)
  • session广播: 自动同步session; 弊端是容器造成广播风暴(理解就是x第一次通过Apache访问了tomcat-a, tamcat-a保存了x的session,tomcat-a会自动将x的session信息广播给其他的tamcat服务器, 结果就是每个服务器都保留的x的session信息, 这样x下次在来时, controller会按照指定的策略分配x到对应的服务器, 而不是分配给tomcat-a)
  • 集中管理方式(重点): 理解就是将各个服务器的session集中到一个数据库或者容器进行管理, 下次来访问时, 服务器去这个专门管理session的数据库或容器去验证session是否存在, 以此来做判断

1.5 在Apache/conf下面配置mod_jk.conf(用于加载mod_jk.so和workers.properties)

  • 内容
# 加载mod_jk.so
LoadModule jk_module modules/mod_jk.so
# 加载workers.properties
JkWorkersFile conf/workers.properties
# /* 表示分流所有的(jsp, servlet, html)  /*.jsp 表示只分流jsp, /*.html 表示拦截.html文件, servlet可以自加后缀来拦截
JkMount /* controller

1.5 将mod_jk.conf加载到httpd.conf

  • 这就会使apache启动时加载mod_jk.conf, mod_jk.conf再连接Apache和tomcat, z最终完成配置
  • 再httpd.conf最后面加一句代码: include conf/mod_jk.conf

注意:

    1. 需要再项目的web.xml中加一句
    1. CATALINA_HOME 会使启动tomcat时, 自动启动CATALINA_HOME指定的tomcat, 而集群中需要开启多个不同的tomcat, 因此再单机环境下, 需要删除 CATALINA_HOME 环境变量
    1. 依次启动Apache, tomcat-a, tomcat-b
    1. Apache中: worker.list-controller,tomcata,tomcatb; Tomcat配置中: jvmRoute="'tomcat-a",jvmRoute="'tomcat-a"; 以及tomcat服务器的目录名tomcata,tomcatb他们可以不一样, 之间没有什么关系

# JavaWeb