Java web笔记

一、HTTP协议

HTTP(超文本传输协议),它是一种主流B/S架构中应用的通信协议。具有以下特点:

1、无状态

服务端不会记录客户端每次提交的请求,服务器一旦相应客户端之后,就会结束本次的通信过程。客户端下一次的请求是一个新的 连接,和上一次通信没有任何关系。

2、简单灵活

HTTP是基于请求(request)和响应(response)的模型

3、支持客户端与服务端

支持主流的B/S架构的通信以及C/S架构的通信。

注意:C/S架构可选的协议有多种,例如:TCP/IP,UDP,HTTP

​ 而B/S架构通常只支持HTTP协议

二、服务器

1、概念

服务器通常由硬件和软件部分构成,统一对用户提供多种不同的服务。

1、硬件:包括响应的CPU、内存、磁盘等等

2、软件:包括操作系统、运行环境、服务器软件、数据库等等

2、web服务器

web服务器是提供服务端程序运行的一个环境,它本身也是一个软件。

例如:将我们编写HTML文件放入到web服务器中,那么外界就可以通过浏览器访问我们的html页面

常见的web服务器有Apache,Tomcat、Jetty、Nginx等等。

而Tomcat、Jetty这些web服务器更准确的说是一个Servlet容器。

三、JavaWeb项目结构

项目根目录,例如:myweb、ch01     通常存放静态资源文件(如:html等等)
  WEB-INF   这个目录是当前项目私有的一个文件夹,只能提供给项目内部访问,对于客户端来说是访问不到了,通常这个目录下存放的是Java源代码、编译后的字节码文件以及Servlet的核心配置文件web.xml
    src 存放java源代码的目录
    classes 存放编译后的字节码文件
    lib lib目录存放当前项目所需要的jar文件
    JSP 用于存放JSP动态页面
    web.xml 项目的配置文件,用于配置Servlet的请求映射、过滤器、监听器等等信息。每一个web项目都对应一个web.xml配置文件
  META-INF   配置应用程序、扩展程序、类加载服务等等

四、Servlet基础

1、什么是Servlet

Servlet是JavaEE中标准组件,专门用于处理客户端提交的HTTP请求。并且它必须依赖于Servlet容器才可以运行(Tomcat就是一个标准的Servlet容器),Servlet容器给Servlet提供一个运行环境,所以Servlet组件必须要这个环境中可以运行,而不能脱离这个环境而单独执行。因为Servlet的实例是由容器创建和销毁的,并不是通过我们平常使用的new关键创建出来。

2、开发一个Servlet的步骤

1.编写一个类,然后继承HttpServlet这个父类

2.重写父类的service方法,这个就是专门处理客户端请求的方法,这个方法有两个参数(HttpServletRequest,HttpServletResponse),同时这个方法会抛出两个异常(ServletException,IOException)

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*; //要让当前的类是一个Servlet,必须继承HttpServlet
public class HelloServlet extends HttpServlet{ //重写父类的service方法,处理客户端请求,
//这个方法私有servlet容器去调用,
//并且request和response参数都是由servlet容器传递进来的
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("Hello Servlet");
//响应一些信息回馈给客户端浏览器
//1.设置要响应的类型,这里就响应简单的html文本类型
//通过response参数来进行设置,如:text/html,text/plain
response.setContentType("text/html;charset=utf-8");
//2.获取输出流并写回html数据
response.getWriter().println("<h1>Hello Servlet</h1>");
}
}
 

3.编译Servlet,需要依赖servlet-api.jar文件

4.编写web.xml,为servlet配置请求映射的URL

<?xml version="1.0" encoding="utf-8"?>
<!-- 配置根节点 -->
<web-app>
<!-- 配置servlet类 -->
<servlet>
<!-- 指定servlet的别名 -->
<servlet-name>hello</servlet-name>
<!-- 指定Servlet的完整类名-->
<servlet-class>HelloServlet</servlet-class>
</servlet>
<!-- 配置请求映射-->
<servlet-mapping>
<!-- 这里的servlet-name和上面的servlet-name要一一对应 -->
<servlet-name>hello</servlet-name>
<!-- 配置请求映射的url,必须以“/”开头-->
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>

5.将项目部署到Tomcat的webapps目录中

3、servlet处理请求的流程

1.浏览器发起http的请求,这个请求首先会被servlet容器(Tomcat)截获,然后容器会根据web.xml文件中配置servlet的<url-pattern>来找到相应的<servlet-name>这个别名,然后再根据这个别名找到具体Servlet的类,然后容器会创建这个Servlet类的实例并调用Servlet方法来处理这个请求。

请求网页地址http://127.0.0.1:8080/ch02/test

4、Servlet的生命周期

所谓的生命周期,就是从Servlet的创建一直到它销毁的整个过程。并且它的 整个生命周期都是由Servlet容器(Tomcat)负责管理和维护的。(补充:在Tomcat中,Servlet是以单实例多线程的方式处理客户端请求)

4.1 Servlet对象创建的过程

当第一次请求某个Servlet的时候,容器会先查找之前有没有创建过这个Servlet的实例,如果没有则创建一个实例并缓存起来。后续 所有请求这个Servlet的时候,都会使用这个缓存的对象来处理客户端请求。(注意“这里说的是第一次请求时创建。另外一种情况则是在容器启动的时候就创建Servlet的实例,在web.xml中为Servlet指定<load-on-startup>配置,这个配置的值是一个整形,数值越小,则初始化 的优先级别越高)

4.2 生命周期方法

方法名 描述
init 在Servlet对象创建之后立即执行的初始化方法,且只执行一次
service 核心的请求处理方法,这个方法可以执行多次
destroy 容器准备销毁Servlet实例之前执行的方法,也是执行一次

5、HTTP请求报文

5.1 请求报文

请求行:请求报文的第一行就是请求行。包括请求方法、请求URL地址、HTTP协议版本

请求头:请求行之后的信息就是请求头,它是以“名称:内容”的格式体现。主要包括服务器主机地址及端口号、连接状态、接收的数据类型、编码、语言等等

请求体:请求头结束之后会有一个空行,空行之后就是请求体的内容。通常使用POST提交的数据信息会存放在请求体中,然后传递给服务器。

5.2 响应报文

状态行:主要包括HTTP协议、响应状态码(例如:200表示OK,成功响应)

响应头:主要包括服务器信息、响应的类型及编码、内容的长度、响应的时间等

响应体:服务器将信息携带到响应体中,带回客户端。

6、HTTP请求方法

在HTTP/1.1协议中,请求方法主要包括8个,下面列举常用的请求方法进行说明。

请求方法 说明
GET 向服务器请求指定的资源,并返回响应主体。一般来说GET方法应该只用于数据的读取(类似于查询)
POST 向指定的服务器提交数据(例如:表单数据的提交、文件上传等),并且提交的数据会放入请求体中(类似于新增)
PUT 向服务器提交数据,但是和POST有所区别。如果服务器不存在此资源的时候,则执行新增,如果存在则执行修改。(类似于修改)
DELETE 根据uri的表示删除服务器上的某个资源(类似于删除)
... ...

备注:GET与POST区别:

1.GET主要用于获取数据,POST用于提交数据。

2.GET请求所带的参数是放在请求行的url地址后面,而POST这是放在请求体中。

3.通常浏览器会对GET请求的url长度有所限制 ,而POST通常在请求体中,可以提交更多的数据信息。

4.浏览器会对GET请求进行缓存。

7、Servlet的请求处理方法

方法 说明
service 可以处理任何的请求类型
doGet 处理对应的GET请求
doPOST 处理对应的POST请求
doPut 处理对应的PUT请求
doDelete 处理对应的DELETE请求

说明:通过HttpServlet的源代码得知,默认的所有请求都会先经过service方法,然后service方法根据请求的方法类型判断来决定交给doGet或者是doPOST方法来处理请求。如果子类重写了service方法同时还重写了其他的doXxx的方法,那么只有service方法会处理请求,其他方法将失效。

8、请求和响应对象

当web容器调用某个Servlet的Service方法时,会创建一个HTTPServletRequest和HTTPServletResponse对象作为参数传入到这个方法中,那么我们可以通过HTTPServletRequest来获取相关的请求内容等,而响应客户端可以利用HttpServletResponse对象来完成。

8.1 HttpServletRequest常用API

方法 说明
getParameter(String name) 获取请求参数的值,根据请求参数的name指定
getParameterValues(String name) 获取相同name的请求参数,返回的是字符串数组
getParameterMap() 获取所有请求参数,包括参数名称和值
getMethod() 获取请求方法的类型
getHeader(Stirng name) 根据请求头的名称获取响应的信息
getRemoteAddr() 获取远程客户端的IP地址
getServletPath() 获取Servlet的请求地址,也就是url-pattern
getRequestURL() 获取请求完整的URL地址
getRealPath(String path) 获取项目的绝对路径。(这个方法在request对象中已废弃,建议通过ServletContext对象获取)
其他 ...
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
System.out.println("---------------获取参数值----------------");
//获取请求参数,根据请求的参数名
String name = request.getParameter("uname");
String age = request.getParameter("age");
System.out.println(name);
System.out.println(age);
//获取相同参数名的参数
String[] addrs = request.getParameterValues("address");
for (String addr : addrs) {
System.out.println(addr);
}
System.out.println("--------------获取参数名---------------");
//获取所有的参数名
Enumeration<String> es = request.getParameterNames();
//枚举使用迭代器来循环遍历
while(es.hasMoreElements()) {
String paramName = es.nextElement();
System.out.println(paramName);
}
System.out.println("---------------获取所有的参数名和参数值-------------------");
//map泛型的第一个参数表示请求的参数名称,第二个参数是请求的参数值,值可以有多个,所以是数组
Map<String,String[]> map = request.getParameterMap();
//map的key对应的是参数名,value就是参数的值
for (String key : map.keySet()) {
System.out.println("参数名:" + key);
System.out.println("参数值: ");
String[] values = (String[])map.get(key);
for (String value : values) {
System.out.println(value);
}
System.out.println("~~~~~~~~~~~");
}
System.out.println("-------------获取客户端的请求方法--------------");
String method = request.getMethod();
System.out.println("请求方法:" + method);
System.out.println("--------------获取请求头部的信息---------------");
String header = request.getHeader("Host");
System.out.println("请求头信息:" + header);
System.out.println("--------------获取远程客户端的IP地址--------------");
String addr = request.getRemoteAddr();
System.out.println("客户端的IP地址" + addr);
System.out.println("------------获取Servlet的url-pattern---------------");
String servletPath = request.getServletPath();
System.out.println("url-pattern: " + servletPath);
System.out.println("------------获取请求的完整URL---------------------------");
String url = request.getRequestURL().toString();
System.out.println(url);
System.out.println("-------------获取项目的绝对路径------------------");
String path = request.getServletContext().getRealPath("/");
System.out.println(path);
}

8.2 HttpServletResponse常用API

方法 说明
setContentType(String str) 设置响应内容的类型及编码
getWriter() 获取响应字符输出流
getOutputStream() 获取字节输出流
setHeader(String name,String value) 设置响应头信息,如果存在响应头信息,则执行更新
addHeader(String name,String value) 设置响应头,不管存不存在都会新加入一个
setStatus(int code) 设置响应状态码
其他 ...

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的内容类型及编码,包括(text/html,text/plain,application/json)
response.setContentType("text/html;charset=utf-8");
//设置响应头信息
response.addHeader("myheader", "hello header");
response.addHeader("myheader", "my servlet");
//设置响应的状态码
response.setStatus(200);
//获取字节输出流
OutputStream os = response.getOutputStream();
//获取响应的字符输出流
PrintWriter pw = response.getWriter();
pw.println("<html>");
pw.println("<head><title>index</title></head>");
pw.println("<body>");
pw.println("<h3>Hello Servlet</h3>");
pw.println("</body>");
pw.println("</html>"); }
 

8.3 常见的响应状态码

状态码 说明
200 请求成功
401 禁止访问,未授权
404 找不到请求的资源
405 请求的行的方法不被支持
500 服务器内部错误
其他 ...

9、Servlet之间的通信

9.1 转发

所谓转发,就是在多个Servlet之间共享请求和响应对象,所有参与转发过程的Servlet都可以获取同一个请求对象的信息。在Servlet的API中,转发的操作是有HttpServletRequest对象完成的。

示例代码:

public class ServletA extends HttpServlet{

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//获取页面提交的参数
String name = request.getParameter("userName"); //转发由HttpServletRequest完成
//第一步先获取一个请求转发器RequestDispatcher
//获取请求转发器的同时要告诉转发器转发到哪里,转发给谁
//如果要转发给ServletB,那么就是对应ServletB的url-pattern
RequestDispatcher rd = request.getRequestDispatcher("servletB");
//调用转发器的forward方法执行转发,同时将request和response对象一并转发ServletB
rd.forward(request, response); }
}
public class ServletB extends HttpServlet{

    @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //这里在同一个请求中再次获取页面的参数
String name = request.getParameter("userName");
System.out.println("ServletB获取请求参数:"+name);
}
}

转发的特点:

1、URL地址栏不会发生改变

2、转发的过程是在服务端自动完成

请求作用域:

每一个请求对象都有一个独立的空间,这个空间我们称之为请求作用域(RequestScope),可以为当前这个请求携带额外的一些数据信息,这些信息同样可以在多个Servlet之间进行共享。

示例代码:


public class ServletA extends HttpServlet{

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{

        //在ServletA中为request对象设置请求作用域
request.setAttribute("age", 35)
}
}
public class ServletB extends HttpServlet{

    @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在ServletB中获取统一个请求对象作用域的值域的值
Integer age = (Integer)request.getAttribute("age");
}
}

9.2 重定向

重定向的机制和转发不同,一次重定向的过程中会有两次请求和两次响应,服务端在接收第一次请求后会先做一次302的响应(302表示重定向状态码),告诉客户端浏览器必须发起一个新的地址,服务端再次接收这个请求处理,最后再次响应客户端。

重定向特点:

1、URL地址栏会发生改变

2、重定向的操作是在客户端浏览器完成的

示例代码:

方式一:

public class ServletC extends HttpServlet {

    @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//执行重定向
//方式一:设置302响应状态码,并在响应头中添加location属性指定重定向的地址
response.setStatus(302);
response.addHeader("location", "http://localhost:8080/ch06/servletD");
}
}

方式二:


public class ServletC extends HttpServlet {

    @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("请求到达ServletC");
System.out.println("ServletC获取请求参数:"+request.getParameter("userName"));
//执行重定向 //方式二:使用response的sendRedirect方法
response.sendRedirect("servletD");
}
}

10、会话跟踪

由于HTTP协议是无状态的,服务端并不会记录每一个客户端的状态,因此,如果想要实现服务器能记录客户端的状态的话,那么就需要会话跟踪技术。

10.1 cookie

cookie是客户端浏览器的内部的一个文本文件,专门用于记录服务器发送过来的一些文本信息,那么在每次请求的时候,客户端都把这个cookie信息由提交回给相应的服务器,那么服务器就可以获取cookie的信息,达到会话跟踪的目的。使用cookie的机制是基于客户端浏览器来维护与服务器的状态跟踪。

示例代码:

设置cookie

public class SetCookieServlet extends HttpServlet{

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个Cookie的实例
Cookie cookie = new Cookie("userId","10001");
//将cookie对象设置到响应对象中
response.addCookie(cookie);
System.out.println("成功设置cookie");
}
}

获取cookie

public class GetCookieServlet extends HttpServlet{

    @Override
protected void service(HttpServletRequest request, HttpServletResponse repsonse) throws ServletException, IOException {
//cookie是通过request对象来得到的
//从请求中可以获取多个cookie对象
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
//判断cookie,只获取name为userId的cookie对象
if("userId".equals(cookie.getName())) {
System.out.println(cookie.getValue());
}
}
}
}

cookie保存中文:

在保存cookie的时候如果需要保存中文,那么中文信息需要经过编码后才可以写入cookIe

示例代码:

编码使用URLEncoder


String str=URLEncoder.encode("张三","utf-8");
Cookie cookie = new Cookie("userName",str);

解码使用URLDecoder


String str=URLDecoder.decode(cookie.getValue(),"utf-8");
System.out.println(str);

cookie的生命周期:

默认cookie只会保存在浏览器进程的内存中,并不会写入cookie文件,如果关闭了浏览器,那么浏览器的进程也就消失了,那么对应的内存就会释放空间,因此cookie的也就销毁。如果想将cookie写入文件,那么就必须设置cookie的生命时长,一旦设置了生命时长,那么就表示这个cookie会在文件中保留多长时间,到了这个时间之后,浏览器就会自动销毁这个cookie。

设置cookie存活时间:


//设置为0表示立即删除cookie
cookie.setMaxAge(0);
//设置为正数表示cookie在cookie文件的存活时间,单位:秒
cookie.setMaxAge(5);
//设置为-1表示cookie只保留在浏览器的进程中,关闭浏览器之后会销毁cookie
cookie.setMaxAge(-1);

10.2 Session

Session是基于服务端来保存用户的信息,这个是和cookie的最大区别。不同的客户端在请求服务器的时候,服务器会为每一个客户端创建一个Session对象并保存在服务器端,这个Session对象是每个客户端所独有的,相互之间不能访问。服务器为了区别不同的Session属于哪一个客户端,因此Session对象也有一个唯一标识,叫做SessionID。而这个SessionID是以cookie的机制保存在客户端浏览器。每次请求的时候,浏览器都会把这个SessionID带回服务端,服务端根据这个SessionID就可以找到对应的Session对象。

示例代码:

public class SessionServlet extends HttpServlet{

    @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//HttpSession对象是在第一次调用request的getSession()方法时才会创建
//注意:getSession()的方法会先判断之前是否为客户端创建了session实例,
//如果创建了,则使用之前创建好的Session对象,没有则创建一个新的Session
HttpSession session = request.getSession();
//创建HttpSession的同时,会创建一个唯一的标识SessionID
//这个sessionId会保存在浏览器的cookie中,每次请求会带回这个id找到相应的session对象
String sessionId = session.getId();
System.out.println(sessionId);
}
}

session的生命周期:

1. SessionId是保存在浏览器的cookie中,但是不会写入cookie文件,这也就表示当关闭浏览器之后SessionId就会销毁。SessionId销毁以后,服务端的Session就没有任何作用了。但是服务器并不会立刻销毁这个Session对象,至于什么时候销毁是由服务器自己决定的。除非我们手动调用了session.invalidate()方法,服务器就会立即销毁这个session实例。

示例代码:


HttpSession session=request.getSession();
//立即销毁session
session.invalidate();

2.Session默认也有存活时间,服务器在创建Session的时候为Session设置默认的存活时间为30分钟,如果在30分钟之内,客户端没有发起任何到服务器,那么服务器就会销毁这个Session对象。我们也可以设置Session的存活时间。可以为当前的Session设置,也可以为全局(服务器端的所有Session)的Session设置。

设置当前Session的存活时:


HttpSession session=request.getSession();
//设置当前Session的存活时间,单位:秒
session.setMaxInactiveInterval(3600);

设置 全局的Session的存活时间 :

在web.xml中进行设置


<!-- 设置全局Session的存活时间,单位:分钟 -->
<session-config>
<session-timeout>60</session-timeout>
</session-config>

Session的作用域:

当我们需要将一些数据信息存入Session的时候,就需要操作Session作用域(SessionScope),它和请求作用域类似,也有相应setAttitude和getAttribute方法,只不过Session作用域的范围要比请求作用域更宽。请求作用域在一次请求响应只有就会消失(因为响应之后请求就会销毁)。而Session对象只要浏览器不关闭或者未超时,那么Session对象会一直驻留在服务器端,因此不管重新请求 多少次还是转发和重定向,都可以从Session中获取之前保存的数据信息。

示例代码:

User user = new User();
user.setUid("1001");
user.setUserName("wangl");
HttpSession session = request.getSession();
//将数据保存在会话作用域中
session.setAttribute("user", user);

从会话作用域取值

HttpSession session = request.getSession();
//取值
User user = (User)session.getAttribute("user");

URL重写:

浏览器是可以禁用cookie的,一旦禁用了cookie,那么SessionID将无法写入cookie的缓存中,这样就导致无法实现会话跟踪了,因此解决办法就是使用URL重写。URL重写的目的就是把SessionID放在请求URL地址的后面提交回服务器(类似GET请求后面带上参数,而这个参数就是一个SessionID),服务器会解析和这个URL的地址并得到SessionID。

示例代码:

//重写请求的URL地址,这个地址后面会自动带上SessionId
String url = response.encodeRedirectURL("getSession");
//重定向URL
response.sendRedirect(url);

浏览器地址演示

 http://localhost:8080/ch07/getSession;jsessionid=6F1 BA8C92D7E5D7CC479ED8DD30D3ED0

注意:“;”后面跟着就是SessionID

11、Servlet上下文

web容器在启动时会为每一个web应用创建唯一的上下文对象,这个对象就是Servlet上下文对象(ServletContext),这个上下文对象可以理解为是当前项目的一个共享内存空间,为项目中的所有Servlet提供一个共享的区域。

常用API:

方法 说明
getContextPath() 获取项目的相对路径
getRealPath(String path) 获取项目的绝对路径
getInitParameter(String name) 获取上下文的初始化参数(web.xml中配置的)
setAttribute(String name, String value) 将数据放入上下文作用域
getAttribute(String name) 从上下文作用域中去获取数据

上下文作用域:

上下文作用域是为当前项目所有Servlet提供的一个共享内存区域,可以将需要的数据信息保存在作用域中。这个作用域的的范围是最大的,只要容器没有停止,它就会一直存在。

三种作用域:

结合前面所学的作用域,那么一共有三个,分别是:请求作用域,会话作用域,上下文作用域。

范围从小到大来划分:

请求作用域<会话作用域<上下文作用域

12、过滤器

过滤器可以在请求到达Servlet之前和Servlet响应客户端之前进行拦截,相当于一个拦截器,主要用于进行一些请求和响应的预处理操作。常用的场景有权限的控制、统一字符编码等等。

12.1 编写过滤器

要实现一个过滤器,必须实现一个Filter接口,只有实现了这个接口的类才称之为过滤器。

示例代码


public class DemoFilter implements Filter{
...
}

web.xml配置过滤器:


 <filter>
<filter-name>demoFilter</filter-name>
<filter-class>edu.nf.ch09.filter.DemoFilter</filter-class>
<!-- 初始化参数 -->
<init-param>
<param-name>param</param-name>
<param-value>hello</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>demoFilter</filter-name>
<!-- 什么请求可以经过此过滤器,/*表示所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>

12.2 过滤器的生命周期

与Servlet类似,Filter同样也是有容器负责创建和销毁,与Servlet的区别在于,容器会在启动的时候最先创建所有的过滤器,并执行init方法进行初始化。

生命周期方法:

方法 说明
init 初始化方法,容器启动时执行一次
doFilter 请求过滤方法,决定请求是否放行
destroy 容器销毁过滤器之前执行的方法

示例代码:


public class DemoFilter implements Filter{

@Override
public void destroy() { System.out.println("准备销毁DemoFilter");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//FilterChain表示一个过滤链对象,因为过滤器可能会存在多个
//同时这个对象将决定是否放行当前请求,
//放行的话则请求会继续到达下一个过滤器或者servlet中
System.out.println("请求经过DemoFileer..放行");
chain.doFilter(request, response);
System.out.println("响应前经过DemoFilter...");
}

@Override
public void init(FilterConfig config) throws ServletException {
String name = config.getInitParameter("param");
System.out.println("初始化DemoFilter,获取初始化参数:"+name);
}

}

12.3 过滤链

在一个web项目中可能存在多个过滤器,当有多个过滤器存在的时候就会形成一个过滤链。请求会按照过滤器链的顺序一直传递下去,最终到达某个Servlet来处理请求。(注意:过滤链的顺序是按照web.xml中的先后配置顺序决定的)

配置示例:


<!-- 按先后顺序配置 -->
<!-- 配置第一个过滤器 -->
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>edu.nf.ch09.filter.FirstFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置第二个过滤器 -->
<filter>
<filter-name>secondFilter</filter-name>
<filter-class>edu.nf.ch09.filter.SecondFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>secondFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

13、监听器

监听器用于监听对象的上的事件发生,在Servlet中监听器主要监听请求对象、会话对象、上下文对象以及监听这些对象的作用操作。JavaEE为我们提供了一系列的监听器接口,开发时按需实现响应的接口即可。

13.1 监听作用域对象的创建于销毁

监听器 说明
ServletRequestListener 监听请求对象的创建和销毁
HttpSessionListener 监听会话对象的创建和销毁
ServletContextListener 监听Servlet上下文对象的创建和销毁

代码实例:

1.请求对象监听器


public class DemoRequestListener implements ServletRequestListener{

/**
* 当请求对象销毁后容器执行此方法
* 销毁方法中同样也有一个ServletRequestEvent事件对象
*/
@Override
public void requestDestroyed(ServletRequestEvent event) {
//通过这个事件对象就可以获取当前的请求对象
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
System.out.println("销毁请求对象..."+request);
}

/**
* 当请求对象创建之后容器调用此方法
* ServletRequestEvent这个参数就是一个事件对象
*/
@Override
public void requestInitialized(ServletRequestEvent event) {
//通过这个事件对象就可以获取当前的请求对象
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
System.out.println("初始化了请求对象..."+request);
}

}

web.xml 配置


 <!-- 配置监听器 -->
<listener>
<!-- 指定监听器的完整类名 -->
<listener-class>edu.nf.ch10.listener.DemoRequestListener</listener-class>
</listener>

2.会话对象监听器


public class DemoSessionListener implements HttpSessionListener{

/**
* 监听HttpSession对象的创建
* HttpSessionEvent参数是一个事件对象
* 通过它可以获得当前的HttpSession
*/
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
System.out.println("创建了Session对象"+session);
}

/**
* 监听HttpSession对象的销毁
*/
@Override
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
System.out.println("销毁了Session对象"+session);
}
}

注意:当第一次调用了request.getSession()方法创建Session时,监听器才会起作用。

web.xml配置


<listener>
<!-- 指定监听器的完整类名 -->
<listener-class>edu.nf.ch10.listener.DemoSessionListener</listener-class>
</listener>

3.Servlet上下文监听器


public class DemoContextListener implements ServletContextListener{

/**
* 监听ServletContext的销毁
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
//通过事件对象获取ServletContext
ServletContext sc = event.getServletContext();
System.out.println("销毁了ServletContext对象..."+sc);
}

/**
* 监听SerlvetContext的创建
*/
@Override
public void contextInitialized(ServletContextEvent event) {
//通过事件对象获取ServletContext
ServletContext sc = event.getServletContext();
System.out.println("创建了ServletContext对象..."+sc);
}
}

web.xml

  
<listener>
<!-- 指定监听器的完整类名 -->
<listener-class>edu.nf.ch10.listener.DemoContextListener</listener-class>
</listener>

13.2 监听作用域的操作

监听器 说明
ServletRequestAttributeListener 监听请求作用域的操作
HttpSessionAttributeListener 监听会话作用域的操作
ServletContextAttributeListener 监听Servlet上下文作用域的操作

示例代码:

这里以HttpSessionAttributeListener说明,其他作用域监听器用法相似。


public class DemoSessionAttributeListener implements HttpSessionAttributeListener{

/**
* 当有数据添加到会话作用域时,执行此方法
*/
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
//获取存入作用域的键和值
System.out.println("存入会话作用域..."+event.getName() + " : " + event.getValue()); }

/**
* 当从会话作用域移除数据时,执行此方法
*/
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("移除会话作用域..."+event.getName() + " : " + event.getValue()); }

/**
* 当替换了会话作用域中的某个数据时,执行此方法
*/
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
System.out.println("替换的值: "+session.getAttribute("userName").toString());
//注意:这里event.getValue()获取到的是被替换的值
System.out.println("替换会话作用域..."+event.getName() + " : " + event.getValue());
}
}

web.xml


<listener>
<!-- 指定监听器的完整类名 -->
<listener-class>edu.nf.ch10.listener.DemoSessionAttributeListener</listener-class>
</listener>

14、注解配置

Servlet3.0开始提供了一系列的注解来配置Servlet、Filter、Listener等等。这种方式可以极大的简化在开发中大量的xml的配置。从这个版本开始,web.xml可以不再需要,使用相关的注解同样可以完成相应的配置。

注解 说明
@WebServlet 这个注解标识在类上,用于配置Servlet。例如:@WebServlet(name="hello",urlPatterns="/hello")也可以简化配置@WebServlet("/hello")
@WebFilter 这个注解标识在类上,用于配置Filter。例如:@WebFilter(filterName="encode",urlPatterns="/*")也可以简化配置@WebFilter("/*")
@WebListener 这个注解标识在类上,用于配置监听器

15、文件上传

从Servlet3.0开始提供了文件上传的功能,操作起来更加的简单和方便。

15.1

想要使用这个功能,首先必须在web.xml或者使用注解开启Servlet的上传功能,否则无效

xml配置:

<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>edu.demo.UploadServlet</servlet-class>
<!-- 开启上传功能 -->
<multipart-config/>
</servlet>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>

注解配置:

@WebServlet("/login")
@MultipartConfig //开启上传功能
public class LoginServlet extends HttpServlet{
...
}

参数说明:

参数 说明
location 指定文件上传的目录
maxFileSize 限制单个文件上传的大小
maxRequestSize 限制一次请求上传总文件的大小
fileSizeThreshold 设置缓存的大小,当达到缓存大小的时候,会将内存的数据写入location指定的目录中(也就是写入磁盘)

15.2

文件上传的核心接口是Part,通过HTTPServletRequest对象可获得该接口的实体类对象

//获取单个文件的Part
Part part = request.getPart("name");
//获取多个上传文件的Part
Collection<Part> cool=request.getParts();

常用API:

方法 说明
getContentType() 获取上传的文件类型
getSize() 获取上传文件的大小
getSubmittedFileName() 获取上传文件的文件名
write() 将文件上传(写入)到指定位置

5.13

当客户端使用form表单来上传文件时,必须将表单的enctype属性设置为"multipart/form-data"

 <form method="post" action="upload" enctype="multipart/form-data">
Username:<input type="text" name="userName"/><br/>
<!-- input使用file类型 -->
File:<input type="file" name="file"/><br/>
<input type="submit" value="submit"/>
</form>

案例:

@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//指定上传的路径
String uploadPath = "/Users/wangl/uploads";
//创建文件夹,如果不存在的情况下
File dir = new File(uploadPath);
if(!dir.exists()){
dir.mkdir();
}

//上传单个文件,参数对用input的name属性的值
//Part part = req.getPart("file");
//uploadPath = uploadPath + "/" + part.getSubmittedFileName();
//执行上传
//part.write(uploadPath);
//获取文件的类型
//System.out.println(part.getContentType());
//获取文件的大小
//System.out.println(part.getSize());
//获取上传的文件名
//System.out.println(part.getSubmittedFileName());

//上传多个文件
Collection<Part> parts = req.getParts();
for (Part p : parts) {
p.write(uploadPath + "/" + p.getSubmittedFileName());
}
}
}

upload.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>文件上传</h2>
<form method="post" action="upload" enctype="multipart/form-data">
Username:<input type="text" name="userName"/><br/>
File1:<input type="file" name="file"/><br/>
File2:<input type="file" name="file"/><br/>
<input type="submit" value="submit"/>
</form>
</body>
</html>

五、JSP基础

1、简介

JSP全名为Java Server Pages,中文名叫java服务器页面,是一种动态页面技术,而HTML是属于静态页面JSP可以在HTML中嵌入java脚本代码,因为JSP本质上还是一个Servlet,因此JSP也必须依赖于web容器才能运行。JSP的出现并不是为了取代Servlet,而是简化了Servlet的工作,将Servlet中繁琐的视图呈现代码脱离出来,交给JSP来完成,让Servlet专注于请求的处理,所以在开发中通常将JSP和Servlet结合一起使用。

2、JSP引擎

由于JSP本质上就是一个Servlet,那么JSP引擎主要负责将JSP文件转义成一个Servlet的java源文件,然后再通过javac将这个源文件,编译成class字节码文件并装载到JVM中执行。JSP引擎的核心类是JSPServlet,位于Jasper.jar文件中,并且在Tomcat的web.xml中也默认就配置好了这个类(JspServlet也是一个Servlet)。因此,凡是以".jsp"结尾的请求都会先经过JspServlet,那么这个引擎就可以开始工作。通常引擎转义和编译后的文件放在容器的work工作目录中。

注意:如果第一次访问Jsp文件的时候,由于work目录中并不存在源文件和字节码文件,JSP引擎就必须完成这两个工作,因此,有可能在第一次访问JSP时会比较缓慢。当字节码编译出来加载后,第二次访问时速度就会很快了。

3、JSP的三大元素

3.1 指令元素

语法:<%@ %>

指令 说明
page指令 用于设置JSP页面的相关信息以及编码
include指令 用于静态包含其他的JSP页面代码,所谓静态包含,就是在编译期,将另外的JSP页面的代码合并到当前的JSP中,最终只会产生一个java源文件
taglib 这个指令用于引入标签库

3.2 动作元素

语法:< jsp:xxx >

动作 说明
include 动态包含其他JSP页面的内容,所谓的动态包括是指在编译期将不同的JSP文件转义成不同java源文件,然后在运行时在将目标内容包含到当前的JSP页面中
forward 相当于Servlet中的转发,转发到其他的JSP页面或者Servlet
param 用于传递参数,通常结合其他的动作一起使用,例如转发时需要提交一些额外的参数
useBean 用于在JSP页面中使用JavaBean对象,通常结合setProperty和getProperty来使用,完成bean对象的赋值和取值操作

3.3 脚本元素

脚本元素主要就是在JSP中嵌入java脚本代码,包括声明、表达式、java脚本

声明语法:<%! %>

表达式:<%= %>

java脚本:<% %>

示例代码:


<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%-- 声明变量和方法,这里声明的变量a是实例变量 --%>
<%!
int a = 10;
public void say(){
System.out.println("hello");
}
%> <%-- 表达式,注意:表达式后面是不允许有;号结束的 --%>
3 + 1 = <%=3+1%><br/> <%-- Java脚本,脚本代码最终会生成在servlet中的service方法中作为代码片段 --%>
<table border="1">
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<% for(int i=0;i<5;i++){%>
<tr>
<td>user<%=i%></td>
<td><%=10+i%></td>
</tr>
<%}%>
</table>
</body>
</html>

4、JSP内置对象

内置对象,是在容器运行时将创建好的9个对象内嵌在JSP中,在JSP里可以直接拿来使用的对象。

对象 说明
out 字符流输出对象
config 等同于Servlet中的ServletConfig
page 表示当前JSP页面,类似于Java类中的this关键字
request 等同于 Servlet中的HTTPServletRequest
response 等同于 Servlet中的HTTPServletResponse
session 等同于Servlet中的HTTPSession
application 等同于Servlet中的Servlet
pageContext 表示当前JSP页面的上下文对象
exception 表示当前JSP中的异常对象

示例代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%-- 常用内置对象 --%>
<%
//使用request,API使用同HttpServletRequest一样
request.getParameter("userName");
request.setAttribute("user", "user1");
request.getAttribute("user"); //使用session,API使用等同于HttpSession
session.setAttribute("user", "user2");
session.getAttribute("user");
session.getId(); //使用response
//response.sendRedirect("demo.jsp"); //out对象,等同于字符输出流对象,在JSP页面输出相关内容
out.println("hello world");
//这个是输出在控制中
System.out.println("hello"); //使用application,等同于ServletContext
application.setAttribute("userName", "user3"); //pageContext使用
//从pageContext中获取相关的其他对象
HttpServletRequest req = (HttpServletRequest)pageContext.getRequest();
HttpServletResponse res = (HttpServletResponse)pageContext.getResponse();
HttpSession ses = pageContext.getSession();
ServletConfig conf = pageContext.getServletConfig();
ServletContext sc = pageContext.getServletContext();
//也可以通过pageContext来统一设置不同的作用域
//第三个参数表示要放入到哪个作用域,是一个int类型的参数
//1代表page作用域(当前页面有效)PageContext.APPLICATION_SCONPE
//2代表请求作用域 PageContext.SESSION_SCOPE
//3代表会话作用域 PageContext.REQUEST_SCOPE
//4代表上下文作用域 PageContext.PAGE_SCOPE
pageContext.setAttribute("userName", "zhangsan", 2);
//也可以指定中哪个作用域取出相应的值
String name = (String)pageContext.getAttribute("userName", 2);
out.println(name);
%>
</body>
</html>

5、EL表达式

EL(Expression Language),全球叫做表达式语言,是JSP2.0推出的一种技术。主要简化了在JSP中使用Java脚本表达式。EL表达式的特点在于使用简单,支持四则运算、逻辑运算等,并且还可以对Servlet API中的对象进行数据访问。EL的语法:${expression}

运算:

示例 结果
${1+1} 2
${2*2} 4
${1==1} true
${2>5} false
${"10"==10} true
其他 ...

数据访问:

1.使用“.”来访问

示例 说明
${param.参数名} 获取请求参数的值,相当于使用request.getParameter()方法
${requestScope.xxx} 从请求作用域中访问数据
${sessionScope.xxx} 从会话作用域中访问数据
${applicationScope.xxx} 从上下文作用域中访问数据
${xxx} 不指定作用域范围时,默认按照作用域范围从小到大的顺序自动查找
其他 ...

示例代码:


姓名:${param.userName}<br/>
<%-- 访问请求作用域 --%>
请求作用域:${requestScope.userName}<br/>
会话作用域: ${sessionScope.userName}<br/>
上下文作用域: ${applicationScope.userName}<br/>
自动从作用域中取值: ${userName}<br/> <h2>访问对象中的属性</h2>
Name:${userName}<br/>
Age:${age}

2.使用"[]"来访问

示例 说明
${requestScope[“userName”]} 从请求作用域中取值
${sessionScope[“userName”]} 从会话作用域取值
其他... 同上

示例代码:

<h2>使用"[]"来访问数据</h2>
<!-- 可以获取使用带.的参数名称-->
请求作用域:${requestScope["user.userName"]}<br/>
会话作用域: ${sessionScope["user.userName"]}<br/>
上下文作用域: ${applicationScope["user.userName"]}<br/>

注意:通常使用"[]"来访问数据的时候,主要是访问一些特殊的名称,例如:request.setAttribute("user.userName")

这种方式如果使用${requestScope.user.userName}是访问不到的,应该改为

${requestScope["user.userName"]}来访问

6、JSTL核心标签库

JSTL是JSP中的标准标签库,主要用于取代JSP中大量的Java脚本代码,让页面看起来更趋向于HTML。使用也很简单,通常结合EL表达式一起使用

示例:

核心标签库(core) 说明
c:out 输出标签
c:set 声明某个变量并存入指定的作用域
c:redirect 重定向标签
c:if 条件判断
c:forEach 循环标签
其他 ...

备注:其他标签库请参阅相关官方文档

代码示例:


<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSTL</title>
</head>
<body>
<%
out.println("hello JSTL");
%>
<!-- 使用输出标签替换上面的脚本 -->
<c:out value="hello JSTL"></c:out> <%
session.setAttribute("userName", "wangl");
%>
<!-- 使用set标签替换上面的脚本,var指定变量名,value指定值,scope指定要放入的作用域 -->
<c:set var="userName" value="wangl" scope="session"></c:set>
<!-- 重定向标签 -->
<%--<c:redirect url="demo.jsp"/>--%> <!-- 使用条件判断 -->
<c:set var="age" value="70"/>
<c:if test="${age<=18}">
<h2>很年轻</h2>
</c:if>
<c:if test="${age>18 && age<50}">
<h2>还好</h2>
</c:if>
<c:if test="${age>60}">
<h2>老了</h2>
</c:if>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 声明变量i,起始为0, 到10结束 -->
<%--
<c:forEach var="i" begin="0" end="10">
${i}<br/>
</c:forEach>
--%>
<table border="1">
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>籍贯</th>
<th>班级</th>
</tr>
<!-- list中存放的map “List<Map<String,Object>>”-->
<%--
<c:forEach items="${list}" var="m">
<tr>
<td>${m.s_name}</td>
<td>${m.s_age}</td>
<td>${m.s_sex}</td>
<td>${m.origin}</td>
<td>${m.c_name}</td>
</tr>
</c:forEach>
--%> <!-- list中放的是Object[] “List<Object[]>” -->
<c:forEach items="${requestScope.list}" var="o">
<tr>
<td>${o[0]}</td>
<td>${o[1]}</td>
<td>${o[2]}</td>
<td>${o[3]}</td>
<td>${o[4]}</td>
</tr>
</c:forEach>
</table> </body>
</html>

JavaWeb入门笔记的更多相关文章

  1. JavaWeb学习笔记总结 目录篇

    JavaWeb学习笔记一: XML解析 JavaWeb学习笔记二 Http协议和Tomcat服务器 JavaWeb学习笔记三 Servlet JavaWeb学习笔记四 request&resp ...

  2. javaweb学习笔记整理补课

    javaweb学习笔记整理补课 * JavaWeb: * 使用Java语言开发基于互联网的项目 * 软件架构: 1. C/S: Client/Server 客户端/服务器端 * 在用户本地有一个客户端 ...

  3. 每天成长一点---WEB前端学习入门笔记

    WEB前端学习入门笔记 从今天开始,本人就要学习WEB前端了. 经过老师的建议,说到他每天都会记录下来新的知识点,每天都是在围绕着这些问题来度过,很有必要每天抽出半个小时来写一个知识总结,及时对一天工 ...

  4. ES6入门笔记

    ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...

  5. [Java入门笔记] 面向对象编程基础(二):方法详解

    什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...

  6. React.js入门笔记

    # React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...

  7. redis入门笔记(2)

    redis入门笔记(2) 上篇文章介绍了redis的基本情况和支持的数据类型,本篇文章将介绍redis持久化.主从复制.简单的事务支持及发布订阅功能. 持久化 •redis是一个支持持久化的内存数据库 ...

  8. redis入门笔记(1)

    redis入门笔记(1) 1. Redis 简介 •Redis是一款开源的.高性能的键-值存储(key-value store).它常被称作是一款数据结构服务器(data structure serv ...

  9. OpenGLES入门笔记四

    原文参考地址:http://www.cnblogs.com/zilongshanren/archive/2011/08/08/2131019.html 一.编译Vertex Shaders和Fragm ...

随机推荐

  1. Hadoop学习笔记(五):java开发MapReduce

    1. MapReduce的流程图(摘自马士兵老师视频),我们开发的就是其中的这两个(红框)过程.简述一下这个图,input就是我们需要处理的文件(datanode上文件的一个分块):Split就是将这 ...

  2. vue-08-axios-get-post-跨域

    1, 安装 cnpm install axios --save 2, 在main.js中引入 import Axios from 'axios' // 挂在在Vue上Vue.prototype.$ax ...

  3. Apache Flink 漫谈系列 - JOIN 算子

    聊什么 在<Apache Flink 漫谈系列 - SQL概览>中我们介绍了JOIN算子的语义和基本的使用方式,介绍过程中大家发现Apache Flink在语法语义上是遵循ANSI-SQL ...

  4. [UWP]为什么ContentControl的ControlTemplate里放两个ContentPresenter会出问题(绕口)

    1. 简单的HeaderedContentControl 上周五收到反馈,在一个ContentControl的ControlTemplate中放两个ContentPresenter会出错.出错的例子是 ...

  5. 手动生成/etc/shadow文件中的密码

    shadow文件的格式就不说了.就说说它的第二列——密码列. 通常,passwd直接为用户指定密码就ok了.但在某些情况下,要为待创建的用户事先指定密码,还要求是加密后的密码,例如kickstart文 ...

  6. MySQL中间件之ProxySQL(7):详述ProxySQL的路由规则

    返回ProxySQL系列文章:http://www.cnblogs.com/f-ck-need-u/p/7586194.html 1.关于ProxySQL路由的简述 当ProxySQL收到前端app发 ...

  7. python装饰器2:类装饰器

    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 本文是装饰器相关内容的第二篇,关于类装饰器. "类装饰器"有两种解读方式:用来装饰类的装饰器:类作为装饰器装饰其它东西.你 ...

  8. Linux文件权限与属性详解 之 su & sudo

    Linux文件权限与属性详解 之 一般权限 Linux文件权限与属性详解 之 ACL Linux文件权限与属性详解 之 SUID.SGID & SBIT Linux文件权限与属性详解 之 ch ...

  9. asp.net core webapi 生成导出excel

    /// <summary> /// 下载订单 /// </summary> /// <param name="model"></param ...

  10. Git Extensions 和 Tortoisegit 到底是什么?Git For VS(Git For Visual Studio)(Visual Studio 中使用 Git)

    前言: 我们使用 Git 作为版本控制的朋友们,一定都熟悉 Git Extensions 和 Tortoisegit 两款工具,但是对于初学者,可能就不那么了解了. 当然如果有幸,你接触过 SVN , ...