1.Servlet底层原理总结
底层通过浏览器(程序)访问服务器(程序),实际是通过(操作)系统底层TCP/IP层的(主机)地址和端口
建立计算机底层间的连接,实现程序间访问,响应。Servlet是服务器程序在收到第一次访问时(Socket级)
运行class文件(并加载配置文件数据)在内存中创建(new)和调用的,同时创建(new)request,response对象传递给它,都包含着相关
请求和响应数据,服务器调用Servlet的init方法初始化,doGet,doPost方法处理和响应请求
一个应用的Servlet实现多线程并发访问,不同的应用存储不同的目录(虚拟目录映射),运行不同的应用程序
实际最终还是像同一台计算机上最底层的java程序间的相互访问
程序示例:只开启服务器,直接通过底层程序访问主机地址和端口,获取服务端数据,URL地址为服务器下一个应用(虚拟目录映射)
public class RangeDemo {
public static void main(String[] args) throws Exception{
//中文文件名怎么传?内容是中文怎么防乱码?
//原始的读写,只开启服务器,利用IP地址,端口,虚拟目录映射,用浏览器访问此资源
//懂原理后不要每次去想底层细节!直接抽象到高层对象和程序流程调用去自然迅速地理解!!
URL url=new URL("http://localhost:8080/workday16/index.jsp");
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//设置请求头,断点续传方式
conn.setRequestProperty("Range", "bytes=5");
//这些API直接查直接用,没有技术含量!重在项目,架构,算法,新技术,经验!!!快过这些基础!!
InputStream in=conn.getInputStream();
int len=0;
byte buffer[]=new byte[1024];
FileOutputStream out=new FileOutputStream("c:\\1qa.txt",true);//append方式
while((len=in.read(buffer))>0){
out.write(buffer,0,len);
}
//注意去练finally,关资源!!
in.close();
out.close();
}
}
2.ServletConfig:
在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
为了得到ServletConfig对象,可以复写init方法,在Servlet类中增加字段保存,但实际上通过this.getServletConfig即可获取(this代表Servlet对象.看源码可知其实现原理就是前面的,不过是在父类中增加了字段,实现了init方法,如此在Servlet对象中保存了服务器创建并传递过来的ServletConfig
源码细节:
<pre name="code" class="java">public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() {
}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
...
}
例子:
web.xml中的配置:
<servlet>
<servlet-name>ServletDemo6</servlet-name>
<servlet-class>cn.itcast.ServletDemo6</servlet-class>
<init-param>
<param-name>data</param-name>
<param-value>xxxxxx</param-value>
</init-param>
<init-param>
<param-name>data1</param-name>
<param-value>yyyyyy</param-value>
</init-param>
<init-param>
<param-name>data2</param-name>
<param-value>zzzzz</param-value>
</init-param>
</servlet>
//ServletConfig对象:用于封装servlet的配置信息
//在实际开发中,有一些东西不适合在servlet中写死,可以通过配置方式配给servlet.比如
//采用哪个码表,连接哪个库,使用哪个配置文件
public class ServletDemo6 extends HttpServlet {
//private ServletConfig config;//记住服务器传递过来的ServletConfig信息
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//处理ServletConfig信息
//String value=config.getInitParameter("data");
//直接获得ServletConfig对象
String value=this.getServletConfig().getInitParameter("data");
System.out.println(value);
//得到所有初始化数据
Enumeration e=this.getServletConfig().getInitParameterNames();
while(e.hasMoreElements()){
String name=(String) e.nextElement();//名称
//名称对应的值
String value1=this.getServletConfig().getInitParameter(name);
System.out.println(name+"="+value1);
}
}
//服务器将web.xml中的初始化信息封装在ServletConfig对象中,传递给init方法
@Override
//public void init(ServletConfig config) throws ServletException {
//this.config=config;
//}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
3.ServletContext:代表web应用,管理web资源
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为 context 域 对象。
setAttribute,getAttribute共享资源,getContext获取(别的)web应用
getInitParameter得到web应用的配置信息,而上面的ServletConfig对象是得到Servlet的配置信息
getRealPath返回资源绝对路径,getResource返回资源URL,getResourceAsStream把资源作为流返回
示例:
web.xml中代表整个应用的参数配置:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>data</param-name>
<param-value>xxx</param-value>
</context-param>
...
得到应用参数的方法:
String value=this.getServletContext().getInitParameter("data");
System.out.println(value);
程序:ServletDemo8访问ServletDemo7在ServletContext中设置的参数
public class ServletDemo7 extends HttpServlet {
//ServletContext:代表Web应用对象
//同一个应用中不同的Servlet可通过它共享数据
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到ServletContext
//方法1
//ServletContext context=this.getServletConfig().getServletContext();
//方法2
//context=this.getServletContext();
String data="aaa";
this.getServletContext().setAttribute("data", data);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
public class ServletDemo8 extends HttpServlet {
//ServletContext域:1.它是一个容器2.它的作用范围是整个应用
//先访问ServletDemo7设置,再访问ServletDemo8,同一个应用,共享数据--->ServletContext域,整个应-用-程-序范围内数据共享!
//实际应用:聊天室----->都发给ServletContext,别人共享
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String value=(String)this.getServletContext().getAttribute("data");
System.out.println(value);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
转发:
public class ServletDemo10 extends HttpServlet {
//ServletContext创建时间:服务器启动时创建所有应用的ServletContext
//转发数据:不同于重定向,一次客户端请求,而重定向是两次
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//转发给jsp
String data="aaaaaaaaa";
//但通过ServletContext共享会出现多线程安全问题,又是多线程访问同一资源!
//所以实际开发中要通过request域来转发
this.getServletContext().setAttribute("data", data);
//转发对象
RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/1.jsp");
rd.forward(request, response);//转过去
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
问题:线程安全问题:如果数据通过ServletContext带,则会出现多用户间的线程安全问题,因为ServletContext域的数据被整个应用共享!!!-->而应该用request域,为每个用户创建一个request对象。
ServletContext管理web资源:
代码:注-意-看-注-释-中-的-知-识-点!!!
//读取资源文件
public class ServletDemo11 extends HttpServlet {
//ServletContext管理web资源
//应用的配置使用资源文件,两种类型:xml和properties
//xml应用于数据彼此相关的情况,比如标签间的嵌套关系,properties用于数据彼此无关的情况:比如数据库配置
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//读取资源文件
//搞清目录,包就是classes下的文件夹,WebRoot就是应用根目录/,这里是服务器调用,要写相对于此应用,该文件的路径
//在Eclipse中开发完后是发送到服务器中去,要遵循服务器中应用的组织结构,服务器中没有src
InputStream in=this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
//如果用FileInputStream--->文件是相对路径,相对于谁:这是java工程,虚拟机调用服务器程序,服务器程序调用web应用
//这个路径是相对于java虚拟机(想想玩命令行时,是java虚拟机的java命令在当前目录下,直接操作当前目录下的文件,虚拟机在哪个目录下启动就相对于哪个目录写相对路径)
//服务器程序是在bin下开启的(虚拟机在此目录下执行服务器程序),所以要相对于Tomcat的bin目录写路径
//而这里的web工程,是相对于服务器编程!是服务器调用,要写服务器中应用的路径!
//所以不能再以Java工程的传统方式读!!!---->这个可用ServletContext的getRealPath方法得到绝对路径:
//String path=this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");
//模板代码(背)
Properties props=new Properties();
props.load(in);//load进Properties对象,用Map来保存数据
String url=props.getProperty("url");
String username=props.getProperty("username");
String password=props.getProperty("password");
System.out.println(url);
System.out.println(username);
System.out.println(password);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
传统方式FileInputStream读取文件:注意读上面注-释-中-的-知-识-点!!-------->获取文件在服务器中的绝对路径!!
代码:
public class ServletDemo12 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取绝对路径,用于获取资源名称(比如下载时,或用户传的资源,客户机带来的资源文件名,下载时需要回显给用户资源文件名)--->ServletContext只能获取流
String path=this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");
String filename=path.substring(path.lastIndexOf("\\")+1);
System.out.println("当前读取的资源名称是:"+filename);
FileInputStream in=new FileInputStream(path);
Properties props=new Properties();
props.load(in);
String url=props.getProperty("url");
String username=props.getProperty("username");
String password=props.getProperty("password");
System.out.println(url);
System.out.println(username);
System.out.println(password);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
实际应用中,Dao层中读取配置文件,拿不到ServletContext的情况下(如果非要拿到ServletContext,就需要Web层以参数的方式传递其引用过来,这样就破坏了设计原则,Web层侵入了Dao层,造成耦合)读取配置文件的办法:通过类装载器读取---------->UserDao.class.getClassLoader():获取装-载-该-类-的类装载器!!
通过流读取的方式:
//创建对象原理:反射,类装载器装载类字节码,创建对象
//服务器用类装载器装载、调用类目录下的类文件,如果配置文件在类目录下,也可通过类装载器装载
//得到装载class目录下类文件的类装载器
public class UserDao {
public void update() throws IOException{
Properties dbconfig=new Properties();
InputStream in=UserDao.class.getClassLoader().getResourceAsStream("db.properties");
dbconfig.load(in);
System.out.println(dbconfig.getProperty("url"));
}
public static void main(String[] args) throws IOException{
new UserDao().update();
}
}
问题:类装载器只装载一次,就像装载类文件,此后一直驻留内存中,那么即使此后文件改了,也无法读取更新后的数据
解决办法:不通过类装载器读,但仍可通过它获得文件位置,通过传统方式读文件
代码:
public class UserDao {
public void update() throws IOException{
/*Properties dbconfig=new Properties();
InputStream in=UserDao.class.getClassLoader().getResourceAsStream("db.properties");
dbconfig.load(in);
System.out.println(dbconfig.getProperty("url"));*/
//getResource获取URL
String path=UserDao.class.getClassLoader().getResource("db.properties").getPath();
FileInputStream in=new FileInputStream(path);
Properties dbconfig=new Properties();
//从传统方式的流中获取数据,以便获取更新后的数据
dbconfig.load(in);
System.out.println(dbconfig.getProperty("password"));
}
public static void main(String[] args) throws IOException{
new UserDao().update();
}
}
实际开发中只读取一次,可以放到静态代码块中。Dao层的异常可以抛到上层去,读取文件失败应该是一个错误,可以抛出错误ExceptionInInitializerError.
注意实际开发中用装载器读取的文件不能太大,因为是一次性加载进内存,太大会造成内存溢出!!
4.Request获取数据的几种方法及需要注意的
注:要加上Values非空的严谨判断!
注:要加上Value非空及去空格后非空的判断,检查用户不填写或只输入全空格的情况!!(用于表单校验等,比如非必填项,判断是非空值才校验格式!!)
注:获取数据Map,注意键值对的值为String[]类型,因为会有同名数据多个值的情况;注意用BeanUtils用Map集合填充Bean,BeanUtils拷贝Bean(同名同类型)属性时只支持8种基本数据类型,对象类型需要注册类转换器!!
注:流方式获取,主要用于二进制数据,比如文件上传。而post提交的表单数据也会在请求体中,也可通过流获取,如图:
表单提交项为空的情况,不加判断的后果:
注:空指针异常!!
正确的做法:
另:超链接提交请求的方式,url中如果有中文数据,需要经过URL编码方式提交:表单页面,用到jsp中jstl和el表达式!!
Request乱码原理:(注:表单页面提交的码表取决于html头设置,控制浏览器以什么码表传输数据)
request.setCharacterEncoding方式对get方式提交无效!!那么我们就需要用原始的方法反向查iso8859-1,用正确的码表把乱码恢复回来:
超链接提交的中文,想不乱码也要手工处理:
测试题:下面方式不会乱码:
相当于:
以下代码会导致异常:
解决办法:记得跳转完后加上return!!
各种地址的写法:
注:一般只要写地址都先写/,不写/的是相对路径,相对于虚拟机启动目录!!
防盗链:判断是不是从本网站的网站点过来的(referer)
注:记得return!!
注:用复制过来的具体地址直接访问也属盗链,因为是直接访问过来的,没有通过任何本网站的页面!!