servlet是javaee技术当中很重要的一部分。所有web应用框架诸如springmvc,struts都建立在其上。这使得servlet成为java面试中的一个热门话题。
这里笔者罗列了一些常见的跟servlet相关的面试问题与解答,希望能够帮助大家:
1.web服务器(web server)和应用服务器(appliction server)的区别是什么?
web服务器的任务是处理客户端的http请求并响应相对应的html页面。web服务器能理解http语言并运行在http协议之上。Apache web服务器就是一种web服务器,此外能够执行servlet和jsp的容器我们称之为servlet容器,比如tomcat。
应用服务器提供一些额外的特性比如jms支持,事务管理的支持等等。所以我们说应用服务器就是一个web服务器加上一些额外的功能以帮助开发者开发一些企业级应用。
2.get和post方法的区别是什么?
(1)get方法提交的参数会通过url传递,而post提交的内容是放在请求体中的。
(2)get传送的数据量比较小,post传送的数据量比较大,通常认为不受限制。
(3)get方法不安全因为将传送数据暴露在url上,而post则不存在这个问题。
(4)超链接和表单的默认提交方式都是get。
3.MIME类型是什么?
响应头的“content-type”值就是一个典型的MIME类型。那什么是mime类型呢?服务器发送mime类型给客户端使其知道服务端返回的数据类型是什么。这帮助客户端如何显示数据。一些常用的mime类型有text/html,text/html,application/xml等。
我们可以使用ServletContext对象的getMimeType(file)方法获取一个文件正确的mime类型,然后使用这个类型去设置响应头的content type (即response.setContentType())。当需要从服务器下载文件时这种方法非常有用。
4.什么是web应用,其目录结构是怎样的?
web应用就是一个可以运行在服务器上的提供静态和动态内容给客户端浏览器的一个模型。目录结构如下:
5.servlet容器的任务有哪些?
servlet容器也叫web容器,比如tomcat。它的主要职责如下:
(1) 提供通信支持(communication support):容器为web服务器和servlet/jsp提供了便捷的通信方式。正是由于web容器,我们不再需在服务端创建一个服务端socket对象监听、解析任何请求以及生成响应。所有这些重要且复杂的工作统统由容器为我们完成,我们仅仅需要关注我们自己应用的业务逻辑而已。
(2) 生命周期和资源管理(lifecycle and resource management):容器管理所有servlet的生命周期,它还负责加载servlet到内存,初始化servler,执行servlet中的方法以及销毁servlet,容器同时还提供诸如JNDI(java命名与目录服务)的资源管理工具。
(3) 多线程支持(multitheading support):容器为每个请求创建一个新的线程并当请求结束时及时摧毁线程。servlet只有一个实例,容器并不会为每个请求生成单独的servlet,这必然节省了时间和空间。
(4) 支持jsp(jsp support):jsp跟普通java类还是有区别的,web容器对此也是提供支持的。每个jsp页面在被容器编译后会生成对应的servlet,然后容器就像管理servlet一样管理它们。
(5) 多任务(miscellaneous task):web容器管理资源池,做内存优化,运行gc,提供安全配置,提供多应用的支持,热部署,以及其他一些我们没法看到的工作。
6.ServletContext对象和ServletConfig对象的区别?
(1)每个servlet都会有自己独有的servletConfig对象而servletContext对象是整个web应用共享的。
(2)servletConfig提供servlet的初始化参数(init-param),仅该servlet可以访问。而servletContext提供的初始化参数整个web应用的所有servlet都可以访问。
(3)servletContext对象提供了setAttribute方法设置共享参数,而servletConfig并没有对应的set方法。
7.RequestDispatcher是什么?
RequestDispatcher接口用来 将请求转发给同一个应用下的另一资源比如servlet,jsp,html等(forward) 。当然我们也可以将另一资源处理的结果包含进来一起返回给客户端(include).这个接口用于同一上下文环境下的servlet间的通讯。
8.PrintWriter和ServletOutPutStream类有什么区别?
PrintWriter是字符流,ServletOutputStream是字节流。可以通过 PrintWriter向浏览器输出字符数组或者是字符串。也可以通过ServletOutPutStream向浏览器端输出字节数组。
PrintWriter对象在servlet中可以通过response.getWriter()方法获取
ServletOutputStream对象通过response.getOutputStream方法获取。
9.在一个servlet能否同时获取PrintWriter和ServletOutputStream对象。
不可以,如果同时获取,将会抛出java.lang.IllegalStateException异常。
10.在servlet中能否产生类似死锁情况?
可以的,你在doPost方法中调用doGet方法,在doGet方法中调用doPost方法,将产生死锁(最终会抛出stackoverflow异常)。
11.servlet包装类有什么用?
servletAPI提供了两个包装类:HttpServletRequestWrapper类和HttpServletResponseWrapper类,这些包装类帮助开发者给出request和response的一般实现。我们可以继承它们并选择我们需要复写的方法进行复写(包装设计模式),而不用复写所有的方法。
12.SingleThreadModel接口是什么?
这个接口提供了线程安全机制,它保证了同一时刻不可能有两个线程并发执行servlet的service方法。但是这个方法并没有解决所有的线程安全问题,比如同一时刻两个线程仍然可以访问到session和静态常量中的内容。而且这个方法也让servlet丧失多线程的优势,所以在servlet2.4中这个方法被声明为过时。
13.是否有必要重写service方法?
一般情况下是没有必要的,因为service方法会根据请求的类型(get、post等)将请求分发给doxxx方法去执行。即使我们需要在处理请求之前需要做一些额外的事,我们也可以通过过滤器或监听器完成。
14.Servlet是否线程安全?如何创建线程安全的servlet?
HttpServlet的init和destroy方法在servlet声明周期中仅 调用一次,所以不用担心它们的线程安全。但是service方法以及doget,dopost等方法是存在线程安全问题的,因为servlet容器为每一个客户端请求都创建一个线程,这些线程在同一时刻可能访问同一个servlet的service方法,所以我们在使用这些方法时务必小心。
如何创建线程安全的servlet?(SingleThreadModel方法不算)
1 .尽量使用局部变量,减少全局变量的使用。
2.对于共享变量,加上关键字synchronized。
注:servlet中常见线程安全与不安全的对象:
线程安全:ServletRequest,ServletResponse
线程不安全:ServletContext,HttpSession。
对于 ServletContext,我们应尽量减少该对象中属性的修改。
而HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。但是当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性
15.sevlet中的属性域。
servlet提供了一些域对象方便内部servlet之间的通信,我们可以通过set/get方法为web应用设置或取出属性值。servlet提供3个域(和jsp区分开来):
1.request scope
2.session scope
3.application scope
分别由ServletRequest,HttpSession,ServletContext对象提供对应的set/get/remove方法去操作者三个域。
注:这跟web.xml中为servletConfig(针对单个servlet)和servletContext(针对web应用)定义的初始化参数不一样。
16如何调用另一个web应用的servlet/jsp等资源?
不能使用RequestDispatcher类,因为这个类只能访问本应用本身的资源。可以使用Response的sendRediect(url)方法,这个访问会返回状态码302给浏览器然后请求一个新的url。如果我们需要携带一些数据,可以在response对象中加上cookie。
17.请求转发(forward)和请求重定向(sendRediect)的区别?
转发是服务器行为,重定向是客户端行为 。两个动作的工作流程如下:
转发过程:客户浏览器发送http请求----》web服务器接受此请求--》调用内部的一个方法在容器内部完成请求处理和转发动作----》将目标资源发送给客户;在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
重定向过程:客户浏览器发送http请求----》web服务器接受后发送302状态码响应及对应新的location给客户浏览器--》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址----》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。
18.HttpServlet为什么声明为抽象类?
httpServlet类虽然是抽象类但却没有抽象方法,之所以这样设计,是因为doget,dopost等方法并没有业务逻辑,开发者至少应该重写一个service中的方法,这就是我们不能实例化HttpServlet的原因。
19.servlet的生命周期?
servlet容器负责管理servlet的生命周期,servlet生命周期如下:
(1) 加载和实例化
Servlet 容器装载和实例化一个 Servlet。创建出该 Servlet 类的一个实例。
(2) 初始化
在 Servlet 实例化完成之后,容器负责调用该 Servlet 实例的 init() 方法,在处理用户请求之前,来做一些额外的初始化工作。
(3) 处理请求
当 Servlet 容器接收到一个 Servlet 请求时,便运行与之对应的 Servlet 实例的 service() 方法,service() 方法再派遣运行与请求相对应的
doXX(doGet,doPost) 方法来处理用户请求。
(4) 销毁
当 Servlet 容器决定将一个 Servlet 从服务器中移除时 ( 如 Servlet 文件被更新 ),便调用该 Servlet 实例的 destroy() 方法,在销毁该 Servlet 实例之前,
来做一些其他的工作。
其中,(1)(2)(4) 在 Servlet 的整个生命周期中只会被执行一次。
1.servlet工作过程时序图:
2.servlet生命周期流程图:
20.servlet生命周期方法有哪些?
共有3个方法:
public void init(ServletConfig config):
这个方法是由servlet容器调用用来初始化servlet的,这个方法在servlet生命周期中仅被调用一次。
public void service(ServletRequest request, ServletResponse response)
servlet容器为每个客户端创建线程,然后都会执行service方法,但执行此方法前init方法必须已经被执行了。
public void destroy()
servlet从servlet容器中被移除时会调用此方法,仅被调用一次。
21.我们为什么应该重写无参的init方法(而不是有参的)?
当我们希望servlet在处理请求之前加载一些资源时,我们应该重写init方法。但是GenericServlet类中定义了两个init方法:
- public void init(ServletConfig config)
- public void init()
官方推荐我们重写无参的init方法,因为通过查看源码我们可以知道,其实有参的init方法会调用无参的init方法,如果我们重写有参的init方法,那么必须先执行super.init(config),否则可能会产生空指针。
22.什么是URL重写?
我们知道HttpSession是基于Cookie工作的,如果浏览器禁用Cookie将导致session失效。ServletAPI提供了一种URL重写的机制用于处理这种情形。
我们只需调用HttpServletResponse对象的encodeURL方法即可,而且这种方法仅当浏览器禁用cookie才会编码,否则返回原url。当我们希望携带session信息重定向到另一资源上时,我们可以使用encodeRediectURL方法。
23.servlet API对cookie的支持?
cookie在c/s通信方面应用很广泛,并非java独有。cookie是服务器端发给浏览器并在浏览器上保存的一段文本数据。
ServletAPI中的javax.servlet.http.Cookie类为cookie提供了支持。HttpServletRequest 对象的getCookies方法从request中获取cookie数组。HttpServletResponse对象的addCookie方法将一个cookie返回给浏览器。
24.如何当session失效时唤醒一些对象?
可以让这个对象实现javax.servlet.http.HttpSessionBindingListener接口,这是一个监听器,有两个方法:valueBound和valueUnbound方法,具体使用请查看文档说明。
25.servlet过滤器的作用?
1.打印一些请求参数到日志中
2.授权请求访问资源
3.在将请求提交给servlet之前,对request请求头或请求体进行一些格式化的处理
4.将响应数据进行压缩再返回给浏览器。
5.解决乱码问题。
26.servlet监听器的作用?
监听客户端的请求,服务器端的操作等 。通过监听器,可以自动激发一些操作,比如监听在线的用户数量( 当增加一个HttpSession时,就自动触发sessionCreated(HttpSessionEvent se)方法,在这个方法中就可以统计在线人数了 ),另外还可以用来初始化一些资源,比如数据库连接池等(web.xml中配置的context-param只能是字符串不能使对象,这时就得使用ServletContextListener了,注意,有读者可能说ServletConext的setAttribute不是可以设置对象吗?但是这是在servlet创建之后才能调用的方法,如果希望web应用一启动就产生初始参数必须使用监听器)。
27.web.xml中组件的加载顺序
context-param -> listener -> filter -> servlet
而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的
28.如何处理servlet抛出的异常?
记住,不可以将异常信息日志显示给浏览器。当出现异常时,可以返回主页,或者显示出错的原因。可以在web.xml中配置错误页面:
- <error-page>
- <error-code>404error-code>
- <location>/AppExceptionHandlerlocation>
- error-page>
-
- <error-page>
- <exception-type>javax.servlet.ServletExceptionexception-type>
- <location>/AppExceptionHandlerlocation>
- error-page>
29.如何确保servlet在应用启动之后被加载到内存?
通常情况下都是客户端请求一个servlet,这个servlet才会被加载到内存,但对于某些很大加载很耗时的servlet我们希望应用启动时就加载它们,这时我们可以在web.xml文件中配置或者使用webServlet注解(servlet3.0)告诉容器系统一启动就加载:
- <servlet>
- <servlet-name>fooservlet-name>
- <servlet-class>com.foo.servlets.Fooservlet-class>
- <load-on-startup>5load-on-startup>
- servlet>
load-on-startup节点中必须配置一个整数,整数代表应用一启动就会被加载,负数表示当客户端请求之后才加载,正数的值越小,说明越先被加载。
30.如何获取资源在文件系统中的绝对路径?
getServletContext().getRealPath(path),path是以‘/’开头的相对路径。
31.如何获取服务器的信息?
getServletContext().getServerInfo()方法,一般返回服务器明,版本号,比如: Apache Tomcat/6.0.37
32.如何在servlet中使用数据库?
如果web应用使用数据库的地方很多,最好在ServletContextListener中初始化它,并设置为context 属性以便其他servlet使用。
比如:
33.如何在servlet中获取客户端的ip地址?
request.getRemoteAddr()方法
34.servlet3.0的重要特性?
异步处理支持: 有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。
可插性支持:熟悉 Struts2 的开发者一定会对其通过插件的方式与包括 Spring 在内的各种常用框架的整合特性记忆犹新。将相应的插件封装成 JAR 包并放在类路径下,Struts2 运行时便能自动加载这些插件。现在 Servlet 3.0 提供了类似的特性,开发者可以通过插件的方式很方便的扩充已有 Web 应用的功能,而不需要修改原有的应用。
HttpServletRequest 对文件上传的支持:此前,对于处理上传文件的操作一直是让开发者头疼的问题,因为 Servlet 本身没有对此提供直接的支持,需要使用第三方框架来实现(file-upload),而且使用起来也不够简单。如今这都成为了历史,Servlet 3.0 已经提供了这个功能。