Java公司面试题集锦(三)

101、常用的Web服务器有哪些?
答:Unix和Linux平台下使用最广泛的免费HTTP服务器是Apache服务器,而Windows平台的服务器通常使用IIS作为Web服务器。选择Web服务器应考虑的因素有:性能、安全性、日志和统计、虚拟主机、代理服务器、缓冲服务和集成应用程序等。下面是对常见服务器的简介:
- IIS:Microsoft的Web服务器产品,全称是Internet Information Services。IIS是允许在公共Intranet或Internet上发布信息的Web服务器。IIS是目前最流行的Web服务器产品之一,很多著名的网站都是建立在IIS的平台上。IIS提供了一个图形界面的管理工具,称为Internet服务管理器,可用于监视配置和控制Internet服务。IIS是一种Web服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。它提供ISAPI(Intranet Server API)作为扩展Web服务器功能的编程接口;同时,它还提供一个Internet数据库连接器,可以实现对数据库的查询和更新。
- Kangle:Kangle Web服务器是一款跨平台、功能强大、安全稳定、易操作的高性能Web服务器和反向代理服务器软件。此外,Kangle也是一款专为做虚拟主机研发的Web服务器。实现虚拟主机独立进程、独立身份运行。用户之间安全隔离,一个用户出问题不影响其他用户。支持PHP、ASP、ASP.NET、Java、Ruby等多种动态开发语言。
- WebSphere:WebSphere Application Server是功能完善、开放的Web应用程序服务器,是IBM电子商务计划的核心部分,它是基于Java的应用环境,用于建立、部署和管理Internet和Intranet Web应用程序,适应各种Web应用程序服务器的需要。
- WebLogic:WebLogic Server是一款多功能、基于标准的Web应用服务器,为企业构建企业应用提供了坚实的基础。针对各种应用开发、关键性任务的部署,各种系统和数据库的集成、跨Internet协作等Weblogic都提供了相应的支持。由于它具有全面的功能、对开放标准的遵从性、多层架构、支持基于组件的开发等优势,很多公司的企业级应用都选择它来作为开发和部署的环境。WebLogic Server在使应用服务器成为企业应用架构的基础方面一直处于领先地位,为构建集成化的企业级应用提供了稳固的基础。
- Apache:目前Apache仍然是世界上用得最多的Web服务器,其市场占有率很长时间都保持在60%以上(目前的市场份额约40%左右)。世界上很多著名的网站都是Apache的产物,它的成功之处主要在于它的源代码开放、有一支强大的开发团队、支持跨平台的应用(可以运行在几乎所有的Unix、Windows、Linux系统平台上)以及它的可移植性等方面。
- Tomcat:Tomcat是一个开放源代码、运行Servlet和JSP的容器。Tomcat实现了Servlet和JSP规范。此外,Tomcat还实现了Apache-Jakarta规范而且比绝大多数商业应用软件服务器要好,因此目前也有不少的Web服务器都选择了Tomcat。
- Nginx:读作”engine x”,是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器。 Nginx是由Igor Sysoev为俄罗斯访问量第二的Rambler站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。在2014年下半年,Nginx的市场份额达到了14%。

102、JSP和Servlet是什么关系?
答:其实这个问题在上面已经阐述过了,Servlet是一个特殊的Java程序,它运行于服务器的JVM中,能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生成。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说,Servlet就是在Java中写HTML,而JSP就是在HTML中写Java代码,当然这个说法是很片面且不够准确的。JSP侧重于视图,Servlet更侧重于控制逻辑,在MVC架构模式中,JSP适合充当视图(view)而Servlet适合充当控制器(controller)。

103、讲解JSP中的四种作用域。
答:JSP中的四种作用域包括page、request、session和application,具体来说:
- page代表与一个页面相关的对象和属性。
- request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
- session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。

104、如何实现JSP或Servlet的单线程模式?
答:
对于JSP页面,可以通过page指令进行设置。

<%@page isThreadSafe=”false”%>

对于Servlet,可以让自定义的Servlet实现SingleThreadModel标识接口。

说明:如果将JSP或Servlet设置成单线程工作模式,会导致每个请求创建一个Servlet实例,这种实践将导致严重的性能问题(服务器的内存压力很大,还会导致频繁的垃圾回收),所以通常情况下并不会这么做。

105、实现会话跟踪的技术有哪些?
答:由于HTTP协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的ID,下一次用户在请求中包含此ID,服务器据此判断到底是哪一个用户。
①URL 重写:在URL中添加用户会话的信息作为请求的参数,或者将唯一的会话ID添加到URL结尾以标识一个会话。
②设置表单隐藏域:将和会话跟踪相关的字段添加到隐式表单域中,这些信息不会在浏览器中显示但是提交表单时会提交给服务器。
这两种方式很难处理跨越多个页面的信息传递,因为如果每次都要修改URL或在页面中添加隐式表单域来存储用户会话相关信息,事情将变得非常麻烦。
③cookie:cookie有两种,一种是基于窗口的,浏览器窗口关闭后,cookie就没有了;另一种是将信息存储在一个临时文件中,并设置存在的时间。当用户通过浏览器和服务器建立一次会话后,会话ID就会随响应信息返回存储在基于窗口的cookie中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话ID又会提交给服务器让服务器识别用户身份。会话中可以为用户保存信息。会话对象是在服务器内存中的,而基于窗口的cookie是在客户端内存中的。如果浏览器禁用了cookie,那么就需要通过下面两种方式进行会话跟踪。当然,在使用cookie时要注意几点:首先不要在cookie中存放敏感信息;其次cookie存储的数据量有限(4k),不能将过多的内容存储cookie中;再者浏览器通常只允许一个站点最多存放20个cookie。当然,和用户会话相关的其他信息(除了会话ID)也可以存在cookie方便进行会话跟踪。
④HttpSession:在所有会话跟踪技术中,HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建HttpSession,每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方法获得HttpSession,通过HttpSession的setAttribute方法可以将一个值放在HttpSession中,通过调用HttpSession对象的getAttribute方法,同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的是,HttpSession放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的Servlet容器可以在内存将满时将HttpSession中的对象移到其他存储设备中,但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象,这个对象最好实现了Serializable接口,这样Servlet容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。

**补充:**HTML5中可以使用Web Storage技术通过JavaScript来保存数据,例如可以使用localStorage和sessionStorage来保存用户会话的信息,也能够实现会话跟踪。

106、过滤器有哪些作用和用法?
答: Java Web开发中的过滤器(filter)是从Servlet 2.3规范开始增加的功能,并在Servlet 2.4规范中得到增强。对Web应用来说,过滤器是一个驻留在服务器端的Web组件,它可以截取客户端和服务器之间的请求与响应信息,并对这些信息进行过滤。当Web容器接受到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么容器将把请求交给过滤器进行处理。在过滤器中,你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源。当目标资源对请求作出响应时候,容器同样会将响应先转发给过滤器,在过滤器中你可以对响应的内容进行转换,然后再将响应发送到客户端。

常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件、对XML的输出应用XSLT等。

和过滤器相关的接口主要有:Filter、FilterConfig和FilterChain。

编码过滤器的例子:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

@WebFilter(urlPatterns = { "*" }, 
        initParams = {@WebInitParam(name="encoding", value="utf-8")})
public class CodingFilter implements Filter {
    private String defaultEncoding = "utf-8";

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        req.setCharacterEncoding(defaultEncoding);
        resp.setCharacterEncoding(defaultEncoding);
        chain.doFilter(req, resp);
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        String encoding = config.getInitParameter("encoding");
        if (encoding != null) {
            defaultEncoding = encoding;
        }
    }
}

下载计数过滤器的例子:

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = {"/*"})
public class DownloadCounterFilter implements Filter {

    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private Properties downloadLog;
    private File logFile;

    @Override
    public void destroy() {
        executorService.shutdown();
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        final String uri = request.getRequestURI();
        executorService.execute(new Runnable() {

            @Override
            public void run() {
                String value = downloadLog.getProperty(uri);
                if(value == null) {
                    downloadLog.setProperty(uri, "1");
                }
                else {
                    int count = Integer.parseInt(value);
                    downloadLog.setProperty(uri, String.valueOf(++count));
                }
                try {
                    downloadLog.store(new FileWriter(logFile), "");
                } 
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        chain.doFilter(req, resp);
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        String appPath = config.getServletContext().getRealPath("/");
        logFile = new File(appPath, "downloadLog.txt");
        if(!logFile.exists()) {
            try {
                logFile.createNewFile();
            } 
            catch(IOException e) {
                e.printStackTrace();
            }
        }
        downloadLog = new Properties();
        try {
            downloadLog.load(new FileReader(logFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
说明:这里使用了Servlet 3规范中的注解来部署过滤器,当然也可以在web.xml中使用<filter><filter-mapping>标签部署过滤器,如108题中所示。

107、监听器有哪些作用和用法?
答:Java Web开发中的监听器(listener)就是application、session、request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件,如下所示:
①ServletContextListener:对Servlet上下文的创建和销毁进行监听。
②ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换。
③HttpSessionListener:对Session的创建和销毁进行监听。

补充:session的销毁有两种情况:1). session超时(可以在web.xml中通过<session-config>/<session-timeout>标签配置超时时间);2). 通过调用session对象的invalidate()方法使session失效。

④HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听。
⑤ServletRequestListener:对请求对象的初始化和销毁进行监听。
⑥ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

下面是一个统计网站最多在线人数监听器的例子:

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 上下文监听器,在服务器启动时初始化onLineCount和maxOnLineCount两个变量
 并将其置于服务器上下文(ServletContext)中,其初始值都是0
*/
@WebListener
public class InitListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent evt) {
    }

    @Override
    public void contextInitialized(ServletContextEvent evt) {
        evt.getServletContext().setAttribute("onLineCount", 0);
        evt.getServletContext().setAttribute("maxOnLineCount", 0);
    }

}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 会话监听器,在用户会话创建和销毁的时候根据情况
 修改onLineCount和maxOnLineCount的值
*/
@WebListener
public class MaxCountListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        ServletContext ctx = event.getSession().getServletContext();
        int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
        count++;
        ctx.setAttribute("onLineCount", count);
        int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
        if (count > maxOnLineCount) {
            ctx.setAttribute("maxOnLineCount", count);
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            ctx.setAttribute("date", df.format(new Date()));
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count--;
        app.setAttribute("onLineCount", count);
    }
}
说明:这里使用了Servlet 3规范中的@WebListener注解配置监听器,当然你可以在web.xml文件中用<listener>标签配置监听器,如108题中所示。

108、web.xml文件中可以配置哪些内容?
答:web.xml用于配置Web应用的相关信息,如:监听器(listener)、过滤器(filter)、 Servlet、相关参数、会话超时时间、安全验证方式、错误页面等,下面是一些开发中常见的配置:

①配置Spring上下文加载监听器加载Spring配置文件并创建IoC容器:

  <context-param>
     <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <listener>
     <listener-class>
       org.springframework.web.context.ContextLoaderListener
     </listener-class>
  </listener>

②配置Spring的OpenSessionInView过滤器来解决延迟加载和Hibernate会话关闭的矛盾:

  <filter>
      <filter-name>openSessionInView</filter-name>
      <filter-class>
         org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
      </filter-class>
  </filter>

  <filter-mapping>
      <filter-name>openSessionInView</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

③配置会话超时时间为10分钟:

  <session-config>
      <session-timeout>10</session-timeout>
  </session-config>

④配置404和Exception的错误页面:

  <error-page>
      <error-code>404</error-code>
      <location>/error.jsp</location>
  </error-page>

  <error-page>
      <exception-type>java.lang.Exception</exception-type>
      <location>/error.jsp</location>
  </error-page>

⑤配置安全认证方式:

  <security-constraint>
      <web-resource-collection>
          <web-resource-name>ProtectedArea</web-resource-name>
          <url-pattern>/admin/*</url-pattern>
          <http-method>GET</http-method>
          <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
          <role-name>admin</role-name>
      </auth-constraint>
  </security-constraint>

  <login-config>
      <auth-method>BASIC</auth-method>
  </login-config>

  <security-role>
      <role-name>admin</role-name>
  </security-role>
说明:对Servlet(小服务)、Listener(监听器)和Filter(过滤器)等Web组件的配置,Servlet 3规范提供了基于注解的配置方式,可以分别使用@WebServlet@WebListener@WebFilter注解进行配置。 

补充:如果Web提供了有价值的商业信息或者是敏感数据,那么站点的安全性就是必须考虑的问题。安全认证是实现安全性的重要手段,认证就是要解决“Are you who you say you are?”的问题。认证的方式非常多,简单说来可以分为三类: 
A. What you know? — 口令 
B. What you have? — 数字证书(U盾、密保卡) 
C. Who you are? — 指纹识别、虹膜识别 
在Tomcat中可以通过建立安全套接字层(Secure Socket Layer, SSL)以及通过基本验证或表单验证来实现对安全性的支持。

109、你的项目中使用过哪些JSTL标签?
答:项目中主要使用了JSTL的核心标签库,包括、、、、等,主要用于构造循环和分支结构以控制显示逻辑。

说明:虽然JSTL标签库提供了core、sql、fmt、xml等标签库,但是实际开发中建议只使用核心标签库(core),而且最好只使用分支和循环标签并辅以表达式语言(EL),这样才能真正做到数据显示和业务逻辑的分离,这才是最佳实践。

110、使用标签库有什么好处?如何自定义JSP标签?
答:使用标签库的好处包括以下几个方面:
- 分离JSP页面的内容和逻辑,简化了Web开发;
- 开发者可以创建自定义标签来封装业务逻辑和显示逻辑;
- 标签具有很好的可移植性、可维护性和可重用性;
- 避免了对Scriptlet(小脚本)的使用(很多公司的项目开发都不允许在JSP中书写小脚本)

自定义JSP标签包括以下几个步骤:
- 编写一个Java类实现实现Tag/BodyTag/IterationTag接口(开发中通常不直接实现这些接口而是继承TagSupport/BodyTagSupport/SimpleTagSupport类,这是对缺省适配模式的应用),重写doStartTag()、doEndTag()等方法,定义标签要完成的功能
- 编写扩展名为tld的标签描述文件对自定义标签进行部署,tld文件通常放在WEB-INF文件夹下或其子目录中
- 在JSP页面中使用taglib指令引用该标签库

下面是一个自定义标签库的例子。

步骤1 - 标签类源代码TimeTag.java:

package com.jackfrued.tags;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

public class TimeTag extends TagSupport {
    private static final long serialVersionUID = 1L;

    private String format = "yyyy-MM-dd hh:mm:ss";
    private String foreColor = "black";
    private String backColor = "white";

    public int doStartTag() throws JspException {
         SimpleDateFormat sdf = new SimpleDateFormat(format);
         JspWriter writer = pageContext.getOut();
         StringBuilder sb = new StringBuilder();
         sb.append(String.format("<span style='color:%s;background-color:%s'>%s</span>",
             foreColor, backColor, sdf.format(new Date())));
         try {
           writer.print(sb.toString());
         } catch(IOException e) {
           e.printStackTrace();
         }
         return SKIP_BODY;
      }

    public void setFormat(String format) {
        this.format = format;
    }

    public void setForeColor(String foreColor) {
        this.foreColor = foreColor;
    }

    public void setBackColor(String backColor) {
        this.backColor = backColor;
    }
}

步骤2 - 编写标签库描述文件my.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>MyTag</short-name>
    <tag>
        <name>time</name>
        <tag-class>com.jackfrued.tags.TimeTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>format</name>
            <required>false</required>
        </attribute>
        <attribute>
            <name>foreColor</name>
        </attribute>
        <attribute>
            <name>backColor</name>
        </attribute>
    </tag>
</taglib>

步骤3 - 在JSP页面中使用自定义标签:

<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="my" uri="/WEB-INF/tld/my.tld" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>

<!DOCTYPE html>
<html>
  <head>
    <base href="<%=basePath%>">
    <title>首页</title>
    <style type="text/css">
        * { font-family: "Arial"; font-size:72px; }
    </style>
  </head>

  <body>
    <my:time format="yyyy-MM-dd" backColor="blue" foreColor="yellow"/>
  </body>
</html>
提示:如果要将自定义的标签库发布成JAR文件,需要将标签库描述文件(tld文件)放在JAR文件的META-INF目录下,可以JDK中的jar工具完成JAR文件的生成,如果不清楚如何操作,可以请教谷老师和百老师。

111、说一下表达式语言(EL)的隐式对象及其作用。
答:EL的隐式对象包括:pageContext、initParam(访问上下文参数)、param(访问请求参数)、paramValues、header(访问请求头)、headerValues、cookie(访问cookie)、applicationScope(访问application作用域)、sessionScope(访问session作用域)、requestScope(访问request作用域)、pageScope(访问page作用域)。

用法如下所示:

${pageContext.request.method}
${pageContext["request"]["method"]}
${pageContext.request["method"]}
${pageContext["request"].method}
${initParam.defaultEncoding}
${header["accept-language"]}
${headerValues["accept-language"][0]}
${cookie.jsessionid.value}
${sessionScope.loginUser.username}
补充:表达式语言的.和[]运算作用是一致的,唯一的差别在于如果访问的属性名不符合Java标识符命名规则,例如上面的accept-language就不是一个有效的Java标识符,那么这时候就只能用[]运算符而不能使用.运算符获取它的值

112、表达式语言(EL)支持哪些运算符?
答:除了.和[]运算符,EL还提供了:
- 算术运算符:+、-、*、/或div、%或mod
- 关系运算符:==或eq、!=或ne、>或gt、>=或ge、<或lt、<=或le
- 逻辑运算符:&&或and、||或or、!或not
- 条件运算符:${statement? A : B}(跟Java的条件运算符类似)
- empty运算符:检查一个值是否为null或者空(数组长度为0或集合中没有元素也返回true)

113、Java Web开发的Model 1和Model 2分别指的是什么?
答:Model 1是以页面为中心的Java Web开发,使用JSP+JavaBean技术将页面显示逻辑和业务逻辑处理分开,JSP实现页面显示,JavaBean对象用来保存数据和实现业务逻辑。Model 2是基于MVC(模型-视图-控制器,Model-View-Controller)架构模式的开发模型,实现了模型和视图的彻底分离,利于团队开发和代码复用,如下图所示:
这里写图片描述

114、Servlet 3中的异步处理指的是什么?
答:在Servlet 3中引入了一项新的技术可以让Servlet异步处理请求。有人可能会质疑,既然都有多线程了,还需要异步处理请求吗?答案是肯定的,因为如果一个任务处理时间相当长,那么Servlet或Filter会一直占用着请求处理线程直到任务结束,随着并发用户的增加,容器将会遭遇线程超出的风险,这这种情况下很多的请求将会被堆积起来而后续的请求可能会遭遇拒绝服务,直到有资源可以处理请求为止。异步特性可以帮助应用节省容器中的线程,特别适合执行时间长而且用户需要得到结果的任务,如果用户不需要得到结果则直接将一个Runnable对象交给Executor并立即返回即可。(如果不清楚多线程和线程池的相关内容,请查看《Java面试题全集(上)》关于多线程和线程池的部分或阅读我的另一篇文章《关于Java并发编程的总结和思考》)

补充:多线程在Java诞生初期无疑是一个亮点,而Servlet单实例多线程的工作方式也曾为其赢得美名,然而技术的发展往往会颠覆我们很多的认知,就如同当年爱因斯坦的相对论颠覆了牛顿的经典力学一般。事实上,异步处理绝不是Serlvet 3首创,如果你了解Node.js的话,对Servlet 3的这个重要改进就不以为奇了。

下面是一个支持异步处理请求的Servlet的例子:

import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 开启Tomcat异步Servlet支持
        req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

        final AsyncContext ctx = req.startAsync();  // 启动异步处理的上下文
        // ctx.setTimeout(30000);
        ctx.start(new Runnable() {

            @Override
            public void run() {
                // 在此处添加异步处理的代码

                ctx.complete();
            }
        });
    }
}

115、如何在基于Java的Web项目中实现文件上传和下载?
答:在Sevlet 3 以前,Servlet API中没有支持上传功能的API,因此要实现上传功能需要引入第三方工具从POST请求中获得上传的附件或者通过自行处理输入流来获得上传的文件,我们推荐使用Apache的commons-fileupload。
从Servlet 3开始,文件上传变得无比简单,相信看看下面的例子一切都清楚了。

上传页面index.jsp:

<%@ page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Photo Upload</title>
</head>
<body>
<h1>Select your photo and upload</h1>
<hr/>
<div style="color:red;font-size:14px;">${hint}</div>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
    Photo file: <input type="file" name="photo" />
    <input type="submit" value="Upload" />
</form>
</body>
</html>

支持上传的Servlet:

package com.jackfrued.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // 可以用request.getPart()方法获得名为photo的上传附件
        // 也可以用request.getParts()获得所有上传附件(多文件上传)
        // 然后通过循环分别处理每一个上传的文件
        Part part = request.getPart("photo");
        if (part != null && part.getSubmittedFileName().length() > 0) {
            // 用ServletContext对象的getRealPath()方法获得上传文件夹的绝对路径
            String savePath = request.getServletContext().getRealPath("/upload");
            // Servlet 3.1规范中可以用Part对象的getSubmittedFileName()方法获得上传的文件名
            // 更好的做法是为上传的文件进行重命名(避免同名文件的相互覆盖)
            part.write(savePath + "/" + part.getSubmittedFileName());
            request.setAttribute("hint", "Upload Successfully!");
        } else {
            request.setAttribute("hint", "Upload failed!");
        }
        // 跳转回到上传页面
        request.getRequestDispatcher("index.jsp").forward(request, response);
    }

}

116、服务器收到用户提交的表单数据,到底是调用Servlet的doGet()还是doPost()方法?
答:HTML的元素有一个method属性,用来指定提交表单的方式,其值可以是get或post。我们自定义的Servlet一般情况下会重写doGet()或doPost()两个方法之一或全部,如果是GET请求就调用doGet()方法,如果是POST请求就调用doPost()方法,那为什么为什么这样呢?我们自定义的Servlet通常继承自HttpServlet,HttpServlet继承自GenericServlet并重写了其中的service()方法,这个方法是Servlet接口中定义的。HttpServlet重写的service()方法会先获取用户请求的方法,然后根据请求方法调用doGet()、doPost()、doPut()、doDelete()等方法,如果在自定义Servlet中重写了这些方法,那么显然会调用重写过的(自定义的)方法,这显然是对模板方法模式的应用(如果不理解,请参考阎宏博士的《Java与模式》一书的第37章)。当然,自定义Servlet中也可以直接重写service()方法,那么不管是哪种方式的请求,都可以通过自己的代码进行处理,这对于不区分请求方法的场景比较合适。

117、JSP中的静态包含和动态包含有什么区别?
答:静态包含是通过JSP的include指令包含页面,动态包含是通过JSP标准动作包含页面。静态包含是编译时包含,如果包含的页面不存在则会产生编译错误,而且两个页面的”contentType”属性应保持一致,因为两个页面会合二为一,只产生一个class文件,因此被包含页面发生的变动再包含它的页面更新前不会得到更新。动态包含是运行时包含,可以向被包含的页面传递参数,包含页面和被包含页面是独立的,会编译出两个class文件,如果被包含的页面不存在,不会产生编译错误,也不影响页面其他部分的执行。代码如下所示:

<%-- 静态包含 --%>
<%@ include file="..." %>

<%-- 动态包含 --%>
<jsp:include page="...">
    <jsp:param name="..." value="..." />
</jsp:include>

118、Servlet中如何获取用户提交的查询参数或表单数据?
答:可以通过请求对象(HttpServletRequest)的getParameter()方法通过参数名获得参数值。如果有包含多个值的参数(例如复选框),可以通过请求对象的getParameterValues()方法获得。当然也可以通过请求对象的getParameterMap()获得一个参数名和参数值的映射(Map)。

119、Servlet中如何获取用户配置的初始化参数以及服务器上下文参数?
答:可以通过重写Servlet接口的init(ServletConfig)方法并通过ServletConfig对象的getInitParameter()方法来获取Servlet的初始化参数。可以通过ServletConfig对象的getServletContext()方法获取ServletContext对象,并通过该对象的getInitParameter()方法来获取服务器上下文参数。当然,ServletContext对象也在处理用户请求的方法(如doGet()方法)中通过请求对象的getServletContext()方法来获得。

120、如何设置请求的编码以及响应内容的类型?
答:通过请求对象(ServletRequest)的setCharacterEncoding(String)方法可以设置请求的编码,其实要彻底解决乱码问题就应该让页面、服务器、请求和响应、Java程序都使用统一的编码,最好的选择当然是UTF-8;通过响应对象(ServletResponse)的setContentType(String)方法可以设置响应内容的类型,当然也可以通过HttpServletResponsed对象的setHeader(String, String)方法来设置。

说明:现在如果还有公司在面试的时候问JSP的声明标记、表达式标记、小脚本标记这些内容的话,这样的公司也不用去了,其实JSP内置对象、JSP指令这些东西基本上都可以忘却了,关于Java Web开发的相关知识,可以看一下我的《Servlet&JSP思维导图》,上面有完整的知识点的罗列。想了解如何实现自定义MVC框架的,可以看一下我的《Java Web自定义MVC框架详解》。

121、解释一下网络应用的模式及其特点。
答:典型的网络应用模式大致有三类:B/S、C/S、P2P。其中B代表浏览器(Browser)、C代表客户端(Client)、S代表服务器(Server),P2P是对等模式,不区分客户端和服务器。B/S应用模式中可以视为特殊的C/S应用模式,只是将C/S应用模式中的特殊的客户端换成了浏览器,因为几乎所有的系统上都有浏览器,那么只要打开浏览器就可以使用应用,没有安装、配置、升级客户端所带来的各种开销。P2P应用模式中,成千上万台彼此连接的计算机都处于对等的地位,整个网络一般来说不依赖专用的集中服务器。网络中的每一台计算机既能充当网络服务的请求者,又对其它计算机的请求作出响应,提供资源和服务。通常这些资源和服务包括:信息的共享和交换、计算资源(如CPU的共享)、存储共享(如缓存和磁盘空间的使用)等,这种应用模式最大的阻力安全性、版本等问题,目前有很多应用都混合使用了多种应用模型,最常见的网络视频应用,它几乎把三种模式都用上了。

补充:此题要跟"电子商务模式"区分开,因为有很多人被问到这个问题的时候马上想到的是B2B(如阿里巴巴)、B2C(如当当、亚马逊、京东)、C2C(如淘宝、拍拍)、C2B(如威客)、O2O(如美团、饿了么)。对于这类问题,可以去百度上面科普一下。

122、什么是Web Service(Web服务)?
答:从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。这就是说,你能够用编程的方法透明的调用这个应用程序,不需要了解它的任何细节,跟你使用的编程语言也没有关系。例如可以创建一个提供天气预报的Web Service,那么无论你用哪种编程语言开发的应用都可以通过调用它的API并传入城市信息来获得该城市的天气预报。之所以称之为Web Service,是因为它基于HTTP协议传输数据,这使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件,就可相互交换数据或集成。

补充:这里必须要提及的一个概念是SOA(Service-Oriented Architecture,面向服务的架构),SOA是一种思想,它将应用程序的不同功能单元通过中立的契约联系起来,独立于硬件平台、操作系统和编程语言,使得各种形式的功能单元能够更好的集成。显然,Web Service是SOA的一种较好的解决方案,它更多的是一种标准,而不是一种具体的技术。

123、概念解释:SOAP、WSDL、UDDI。
答:
- SOAP:简单对象访问协议(Simple Object Access Protocol),是Web Service中交换数据的一种协议规范。
- WSDL:Web服务描述语言(Web Service Description Language),它描述了Web服务的公共接口。这是一个基于XML的关于如何与Web服务通讯和使用的服务描述;也就是描述与目录中列出的Web服务进行交互时需要绑定的协议和信息格式。通常采用抽象语言描述该服务支持的操作和信息,使用的时候再将实际的网络协议和信息格式绑定给该服务。
- UDDI:统一描述、发现和集成(Universal Description, Discovery and Integration),它是一个基于XML的跨平台的描述规范,可以使世界范围内的企业在互联网上发布自己所提供的服务。简单的说,UDDI是访问各种WSDL的一个门面(可以参考设计模式中的门面模式)。

提示:关于Web Service的相关概念和知识可以在W3CSchool上找到相关的资料。

124、Java规范中和Web Service相关的规范有哪些?
答:Java规范中和Web Service相关的有三个:
- JAX-WS(JSR 224):这个规范是早期的基于SOAP的Web Service规范JAX-RPC的替代版本,它并不提供向下兼容性,因为RPC样式的WSDL以及相关的API已经在Java EE5中被移除了。WS-MetaData是JAX-WS的依赖规范,提供了基于注解配置Web Service和SOAP消息的相关API。
- JAXM(JSR 67):定义了发送和接收消息所需的API,相当于Web Service的服务器端。
- JAX-RS(JSR 311 & JSR 339 & JSR 370):是Java针对REST(Representation State Transfer)架构风格制定的一套Web Service规范。REST是一种软件架构模式,是一种风格,它不像SOAP那样本身承载着一种消息协议, (两种风格的Web Service均采用了HTTP做传输协议,因为HTTP协议能穿越防火墙,Java的远程方法调用(RMI)等是重量级协议,通常不能穿越防火墙),因此可以将REST视为基于HTTP协议的软件架构。REST中最重要的两个概念是资源定位和资源操作,而HTTP协议恰好完整的提供了这两个点。HTTP协议中的URI可以完成资源定位,而GET、POST、OPTION、DELETE方法可以完成资源操作。因此REST完全依赖HTTP协议就可以完成Web Service,而不像SOAP协议那样只利用了HTTP的传输特性,定位和操作都是由SOAP协议自身完成的,也正是由于SOAP消息的存在使得基于SOAP的Web Service显得笨重而逐渐被淘汰。

125、介绍一下你了解的Java领域的Web Service框架。
答:Java领域的Web Service框架很多,包括Axis2(Axis的升级版本)、Jersey(RESTful的Web Service框架)、CXF(XFire的延续版本)、Hessian、Turmeric、JBoss SOA等,其中绝大多数都是开源框架。

提示:面试被问到这类问题的时候一定选择自己用过的最熟悉的作答,如果之前没有了解过就应该在面试前花一些时间了解其中的两个,并比较其优缺点,这样才能在面试时给出一个漂亮的答案。

下面部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring MVC等,由于Struts 2已经是明日黄花,在这里就不讨论Struts 2的面试题,如果需要了解相关内容,可以参考我的另一篇文章《Java面试题集(86-115)》。此外,这篇文章还对企业应用架构、大型网站架构和应用服务器优化等内容进行了简单的探讨,这些内容相信对面试会很有帮助。

126、什么是ORM?
答:对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术;简单的说,ORM是通过使用描述对象和数据库之间映射的元数据(在Java中可以用XML或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成Java对象,其本质上就是将数据从一种形式转换到另外一种形式。

127、持久层设计要考虑的问题有哪些?你用过的持久层框架有哪些?
答:所谓”持久”就是将数据保存到可掉电式存储设备中以便今后使用,简单的说,就是将内存中的数据保存到关系型数据库、文件系统、消息队列等提供持久化支持的设备中。持久层就是系统中专注于实现数据持久化的相对独立的层面。

持久层设计的目标包括:
- 数据存储逻辑的分离,提供抽象化的数据访问接口。
- 数据访问底层实现的分离,可以在不修改代码的情况下切换底层实现。
- 资源管理和调度的分离,在数据访问层实现统一的资源调度(如缓存机制)。
- 数据抽象,提供更面向对象的数据操作。

持久层框架有:
- Hibernate
- MyBatis
- TopLink
- Guzz
- jOOQ
- Spring Data
- ActiveJDBC

128、Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)?
答:SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。SessionFactory一般只会在启动的时候构建。对于应用程序,最好将SessionFactory通过单例模式进行封装以便于访问。Session是一个轻量级非线程安全的对象(线程间不能共享session),它表示与数据库进行交互的一个工作单元。Session是由SessionFactory创建的,在任务完成之后它会被关闭。Session是持久层服务对外提供的主要接口。Session会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的session,可以使用ThreadLocal将session和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。

129、Hibernate中Session的load和get方法的区别是什么?
答:主要有以下三项区别:
① 如果没有找到符合条件的记录,get方法返回null,load方法抛出异常。
② get方法直接返回实体类对象,load方法返回实体类对象的代理。
③ 在Hibernate 3之前,get方法只在一级缓存中进行数据查找,如果没有找到对应的数据则越过二级缓存,直接发出SQL语句完成数据读取;load方法则可以从二级缓存中获取数据;从Hibernate 3开始,get方法不再是对二级缓存只写不读,它也是可以访问二级缓存的。

说明:对于load()方法Hibernate认为该数据在数据库中一定存在可以放心的使用代理来实现延迟加载,如果没有数据就抛出异常,而通过get()方法获取的数据可以不存在。

130、Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分别是做什么的?有什么区别?
答:Hibernate的对象有三种状态:瞬时态(transient)、持久态(persistent)和游离态(detached),如第135题中的图所示。瞬时态的实例可以通过调用save()、persist()或者saveOrUpdate()方法变成持久态;游离态的实例可以通过调用 update()、saveOrUpdate()、lock()或者replicate()变成持久态。save()和persist()将会引发SQL的INSERT语句,而update()或merge()会引发UPDATE语句。save()和update()的区别在于一个是将瞬时态对象变成持久态,一个是将游离态对象变为持久态。merge()方法可以完成save()和update()方法的功能,它的意图是将新的状态合并到已有的持久化对象上或创建新的持久化对象。对于persist()方法,按照官方文档的说明:① persist()方法把一个瞬时态的实例持久化,但是并不保证标识符被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时间;② persist()方法保证当它在一个事务外部被调用的时候并不触发一个INSERT语句,当需要封装一个长会话流程的时候,persist()方法是很有必要的;③ save()方法不保证第②条,它要返回标识符,所以它会立即执行INSERT语句,不管是在事务内部还是外部。至于lock()方法和update()方法的区别,update()方法是把一个已经更改过的脱管状态的对象变成持久状态;lock()方法是把一个没有更改过的脱管状态的对象变成持久状态。

131、阐述Session加载实体对象的过程。
答:Session加载实体对象的步骤是:
① Session在调用数据库查询功能之前,首先会在一级缓存中通过实体类型和主键进行查找,如果一级缓存查找命中且数据状态合法,则直接返回;
② 如果一级缓存没有命中,接下来Session会在当前NonExists记录(相当于一个查询黑名单,如果出现重复的无效查询可以迅速做出判断,从而提升性能)中进行查找,如果NonExists中存在同样的查询条件,则返回null;
③ 如果一级缓存查询失败则查询二级缓存,如果二级缓存命中则直接返回;
④ 如果之前的查询都未命中,则发出SQL语句,如果查询未发现对应记录则将此次查询添加到Session的NonExists中加以记录,并返回null;
⑤ 根据映射配置和SQL语句得到ResultSet,并创建对应的实体对象;
⑥ 将对象纳入Session(一级缓存)的管理;
⑦ 如果有对应的拦截器,则执行拦截器的onLoad方法;
⑧ 如果开启并设置了要使用二级缓存,则将数据对象纳入二级缓存;
⑨ 返回数据对象。

132、Query接口的list方法和iterate方法有什么区别?
答:
① list()方法无法利用一级缓存和二级缓存(对缓存只写不读),它只能在开启查询缓存的前提下使用查询缓存;iterate()方法可以充分利用缓存,如果目标数据只读或者读取频繁,使用iterate()方法可以减少性能开销。
② list()方法不会引起N+1查询问题,而iterate()方法可能引起N+1查询问题

说明:关于N+1查询问题,可以参考CSDN上的一篇文章《什么是N+1查询》

133、Hibernate如何实现分页查询?
答:通过Hibernate实现分页查询,开发人员只需要提供HQL语句(调用Session的createQuery()方法)或查询条件(调用Session的createCriteria()方法)、设置查询起始行数(调用Query或Criteria接口的setFirstResult()方法)和最大查询行数(调用Query或Criteria接口的setMaxResults()方法),并调用Query或Criteria接口的list()方法,Hibernate会自动生成分页查询的SQL语句。

134、锁机制有什么用?简述Hibernate的悲观锁和乐观锁机制。
答:有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。
Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性,关于数据库的锁机制和事务隔离级别在《Java面试题大全(上)》中已经讨论过了。乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。最常见的乐观锁是通过数据版本标识来实现的,读取数据时获得数据的版本号,更新数据时将此版本号加1,然后和数据库表对应记录的当前版本号进行比较,如果提交的数据版本号大于数据库中此记录的当前版本号则更新数据,否则认为是过期数据无法更新。Hibernate中通过Session的get()和load()方法从数据库中加载对象时可以通过参数指定使用悲观锁;而乐观锁可以通过给实体类加整型的版本字段再通过XML或@Version注解进行配置。

提示:使用乐观锁会增加了一个版本字段,很明显这需要额外的空间来存储这个版本字段,浪费了空间,但是乐观锁会让系统具有更好的并发性,这是对时间的节省。因此乐观锁也是典型的空间换时间的策略。

135、阐述实体对象的三种状态以及转换关系。
答:最新的Hibernate文档中为Hibernate对象定义了四种状态(原来是三种状态,面试的时候基本上问的也是三种状态),分别是:瞬时态(new, or transient)、持久态(managed, or persistent)、游状态(detached)和移除态(removed,以前Hibernate文档中定义的三种状态中没有移除态),如下图所示,就以前的Hibernate文档中移除态被视为是瞬时态。
这里写图片描述
瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。

提示:关于这个问题,在Hibernate的官方文档中有更为详细的解读。

136、如何理解Hibernate的延迟加载机制?在实际应用中,延迟加载与Session关闭的矛盾是如何处理的?
答:延迟加载就是并不是在读取的时候就把数据加载进来,而是等到使用时再加载。Hibernate使用了虚拟代理机制实现延迟加载,我们使用Session的load()方法加载数据或者一对多关联映射在使用延迟加载的情况下从一的一方加载多的一方,得到的都是虚拟代理,简单的说返回给用户的并不是实体本身,而是实体对象的代理。代理对象在用户调用getter方法时才会去数据库加载数据。但加载数据就需要数据库连接。而当我们把会话关闭时,数据库连接就同时关闭了。

延迟加载与session关闭的矛盾一般可以这样处理:
① 关闭延迟加载特性。这种方式操作起来比较简单,因为Hibernate的延迟加载特性是可以通过映射文件或者注解进行配置的,但这种解决方案存在明显的缺陷。首先,出现”no session or session was closed”通常说明系统中已经存在主外键关联,如果去掉延迟加载的话,每次查询的开销都会变得很大。
② 在session关闭之前先获取需要查询的数据,可以使用工具方法Hibernate.isInitialized()判断对象是否被加载,如果没有被加载则可以使用Hibernate.initialize()方法加载对象。
③ 使用拦截器或过滤器延长Session的生命周期直到视图获得数据。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是这种做法。

137、举一个多对多关联的例子,并说明如何实现多对多关联映射。
答:例如:商品和订单、学生和课程都是典型的多对多关系。可以在实体类上通过@ManyToMany注解配置多对多关联或者通过映射文件中的和标签配置多对多关联,但是实际项目开发中,很多时候都是将多对多关联映射转换成两个多对一关联映射来实现的。

138、谈一下你对继承映射的理解。
答:继承关系的映射策略有三种:
① 每个继承结构一张表(table per class hierarchy),不管多少个子类都用一张表。
② 每个子类一张表(table per subclass),公共信息放一张表,特有信息放单独的表。
③ 每个具体类一张表(table per concrete class),有多少个子类就有多少张表。
第一种方式属于单表策略,其优点在于查询子类对象的时候无需表连接,查询速度快,适合多态查询;缺点是可能导致表很大。后两种方式属于多表策略,其优点在于数据存储紧凑,其缺点是需要进行连接查询,不适合多态查询。

139、简述Hibernate常见优化策略。
答:这个问题应当挑自己使用过的优化策略回答,常用的有:
① 制定合理的缓存策略(二级缓存、查询缓存)。
② 采用合理的Session管理机制。
③ 尽量使用延迟加载特性。
④ 设定合理的批处理参数。
⑤ 如果可以,选用UUID作为主键生成器。
⑥ 如果可以,选用基于版本号的乐观锁替代悲观锁。
⑦ 在开发过程中, 开启hibernate.show_sql选项查看生成的SQL,从而了解底层的状况;开发完成后关闭此选项。
⑧ 考虑数据库本身的优化,合理的索引、恰当的数据分区策略等都会对持久层的性能带来可观的提升,但这些需要专业的DBA(数据库管理员)提供支持。

140、谈一谈Hibernate的一级缓存、二级缓存和查询缓存。
答:Hibernate的Session提供了一级缓存的功能,默认总是有效的,当应用程序保存持久化实体、修改持久化实体时,Session并不会立即把这种改变提交到数据库,而是缓存在当前的Session中,除非显示调用了Session的flush()方法或通过close()方法关闭Session。通过一级缓存,可以减少程序与数据库的交互,从而提高数据库访问性能。
SessionFactory级别的二级缓存是全局性的,所有的Session可以共享这个二级缓存。不过二级缓存默认是关闭的,需要显示开启并指定需要使用哪种二级缓存实现类(可以使用第三方提供的实现)。一旦开启了二级缓存并设置了需要使用二级缓存的实体类,SessionFactory就会缓存访问过的该实体类的每个对象,除非缓存的数据超出了指定的缓存空间。
一级缓存和二级缓存都是对整个实体进行缓存,不会缓存普通属性,如果希望对普通属性进行缓存,可以使用查询缓存。查询缓存是将HQL或SQL语句以及它们的查询结果作为键值对进行缓存,对于同样的查询可以直接从缓存中获取数据。查询缓存默认也是关闭的,需要显示开启。

141、Hibernate中DetachedCriteria类是做什么的?
答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法创建的,也就意味着离开创建它的Session,Criteria就无法使用了。DetachedCriteria不需要Session就可以创建(使用DetachedCriteria.forClass()方法创建),所以通常也称其为离线的Criteria,在需要进行查询操作的时候再和Session绑定(调用其getExecutableCriteria(Session)方法),这也就意味着一个DetachedCriteria可以在需要的时候和不同的Session进行绑定。

142、@OneToMany注解的mappedBy属性有什么作用?
答:@OneToMany用来配置一对多关联映射,但通常情况下,一对多关联映射都由多的一方来维护关联关系,例如学生和班级,应该在学生类中添加班级属性来维持学生和班级的关联关系(在数据库中是由学生表中的外键班级编号来维护学生表和班级表的多对一关系),如果要使用双向关联,在班级类中添加一个容器属性来存放学生,并使用@OneToMany注解进行映射,此时mappedBy属性就非常重要。如果使用XML进行配置,可以用标签的inverse=”true”设置来达到同样的效果。

143、MyBatis中使用#和书写占位符有什么区别?   
答:#将传入的数据都当成一个字符串,会对传入的数据自动加上引号;
将传入的数据直接显示生成在SQL中。注意:使用占位符可能会导致SQL注射攻击,能用#的地方就不要使用,写order by子句的时候应该用$而不是#。

144、解释一下MyBatis中命名空间(namespace)的作用。
答:在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。

145、MyBatis中的动态SQL是什么意思?
答:对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,例如在58同城上面找房子,我们可能会指定面积、楼层和所在位置来查找房源,也可能会指定面积、价格、户型和所在位置来查找房源,此时就需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach

下面是映射文件的片段:

    <select id="foo" parameterType="Blog" resultType="Blog">
        select * from t_blog where 1 = 1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="content != null">
            and content = #{content}
        </if>
        <if test="owner != null">
            and owner = #{owner}
        </if>
   </select>

当然也可以像下面这样书写:

    <select id="foo" parameterType="Blog" resultType="Blog">
        select * from t_blog where 1 = 1 
        <choose>
            <when test="title != null">
                and title = #{title}
            </when>
            <when test="content != null">
                and content = #{content}
            </when>
            <otherwise>
                and owner = "owner1"
            </otherwise>
        </choose>
    </select>

再看看下面这个例子:

    <select id="bar" resultType="Blog">
        select * from t_blog where id in
        <foreach collection="array" index="index" 
            item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>

146、什么是IoC和DI?DI是如何实现的?
答:IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的”控制反转”就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。IoC体现了好莱坞原则 - “Don’t call me, we will call you”。依赖注入的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

举个例子:一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或setter方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。

依赖注入可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。

147、Spring中Bean的作用域有哪些?
答:在Spring的早期版本中,仅有两个作用域:singleton和prototype,前者表示Bean以单例的方式存在;后者表示每次从容器中调用Bean时,都会返回一个新的实例,prototype通常翻译为原型。

补充:设计模式中的创建型模式中也有一个原型模式,原型模式也是一个常用的模式,例如做一个室内设计软件,所有的素材都在工具箱中,而每次从工具箱中取出的都是素材对象的一个原型,可以通过对象克隆来实现原型模式。

Spring 2.x中针对WebApplicationContext新增了3个作用域,分别是:request(每次HTTP请求都会创建一个新的Bean)、session(同一个HttpSession共享同一个Bean,不同的HttpSession使用不同的Bean)和globalSession(同一个全局Session共享一个Bean)。

说明:单例模式和原型模式都是重要的设计模式。一般情况下,无状态或状态不可变的类适合使用单例模式。在传统开发中,由于DAO持有Connection这个非线程安全对象因而没有使用单例模式;但在Spring环境下,所有DAO类对可以采用单例模式,因为Spring利用AOP和Java API中的ThreadLocal对非线程安全的对象进行了特殊处理。

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。ThreadLocal,顾名思义是线程的一个本地化对象,当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量。

ThreadLocal类非常简单好用,只有四个方法,能用上的也就是下面三个方法:
- void set(T value):设置当前线程的线程局部变量的值。
- T get():获得当前线程所对应的线程局部变量的值。
- void remove():删除当前线程中线程局部变量的值。

ThreadLocal是如何做到为每一个线程维护一份独立的变量副本的呢?在ThreadLocal类中有一个Map,键为线程对象,值是其线程对应的变量的副本,自己要模拟实现一个ThreadLocal类其实并不困难,代码如下所示:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal<T> {
    private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());

    public void set(T newValue) {
        map.put(Thread.currentThread(), newValue);
    }

    public T get() {
        return map.get(Thread.currentThread());
    }

    public void remove() {
        map.remove(Thread.currentThread());
    }
}

148、解释一下什么叫AOP(面向切面编程)?
答:AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern)。

149、你是如何理解”横切关注”这个概念的?
答:”横切关注”是会影响到整个应用程序的关注功能,它跟正常的业务逻辑是正交的,没有必然的联系,但是几乎所有的业务逻辑都会涉及到这些关注功能。通常,事务、日志、安全性等关注就是应用中的横切关注功能。

150、你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?
答:
a. 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。
b. 切点(Pointcut):如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。
c. 增强(Advice):增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多资料上将增强译为“通知”,这明显是个词不达意的翻译,让很多程序员困惑了许久。

说明: Advice在国内的很多书面资料中都被翻译成"通知",但是很显然这个翻译无法表达其本质,有少量的读物上将这个词翻译为"增强",这个翻译是对Advice较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有业务逻辑的一种增强,这种增强可以是前置增强、后置增强、返回后增强、抛异常时增强和包围型增强。

d. 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的未该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
e. 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:①编译期织入:需要特殊的Java编译期(例如AspectJ的ajc);②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;③运行时织入:在运行时为目标类生成代理实现增强。Spring采用了动态代理的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。
f. 切面(Aspect):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。

补充:代理模式是GoF提出的23种设计模式中最为经典的模式之一,代理模式是对象的结构模式,它给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。简单的说,代理对象可以完成比原对象更多的职责,当需要为原对象添加横切关注功能时,就可以使用原对象的代理对象。我们在打开Office系列的Word文档时,如果文档中有插图,当文档刚加载时,文档中的插图都只是一个虚框占位符,等用户真正翻到某页要查看该图片时,才会真正加载这张图,这其实就是对代理模式的使用,代替真正图片的虚框就是一个虚拟代理;Hibernate的load方法也是返回一个虚拟代理对象,等用户真正需要访问对象的属性时,才向数据库发出SQL语句获得真实对象。

下面用一个找枪手代考的例子演示代理模式的使用:

/**
 * 参考人员接口
 * @author 骆昊
 *
 */
public interface Candidate {

    /**
     * 答题
     */
    public void answerTheQuestions();
}
/**
 * 懒学生
 * @author 骆昊
 *
 */
public class LazyStudent implements Candidate {
    private String name;        // 姓名

    public LazyStudent(String name) {
        this.name = name;
    }

    @Override
    public void answerTheQuestions() {
        // 懒学生只能写出自己的名字不会答题
        System.out.println("姓名: " + name);
    }

}
/**
 * 枪手
 * @author 骆昊
 *
 */
public class Gunman implements Candidate {
    private Candidate target;   // 被代理对象

    public Gunman(Candidate target) {
        this.target = target;
    }

    @Override
    public void answerTheQuestions() {
        // 枪手要写上代考的学生的姓名
        target.answerTheQuestions();
        // 枪手要帮助懒学生答题并交卷
        System.out.println("奋笔疾书正确答案");
        System.out.println("交卷");
    }

}
public class ProxyTest1 {

    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("王小二"));
        c.answerTheQuestions();
    }
}
说明:从JDK 1.3开始,Java提供了动态代理技术,允许开发者在运行时创建接口的代理实例,主要包括Proxy类和InvocationHandler接口。下面的例子使用动态代理为ArrayList编写一个代理,在添加和删除元素时,在控制台打印添加或删除的元素以及ArrayList的大小:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

public class ListProxy<T> implements InvocationHandler {
    private List<T> target;

    public ListProxy(List<T> target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object retVal = null;
        System.out.println("[" + method.getName() + ": " + args[0] + "]");
        retVal = method.invoke(target, args);
        System.out.println("[size=" + target.size() + "]");
        return retVal;
    }

}
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class ProxyTest2 {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Class<?> clazz = list.getClass();
        ListProxy<String> myProxy = new ListProxy<String>(list);
        List<String> newList = (List<String>) 
                Proxy.newProxyInstance(clazz.getClassLoader(), 
                clazz.getInterfaces(), myProxy);
        newList.add("apple");
        newList.add("banana");
        newList.add("orange");
        newList.remove("banana");
    }
}
说明:使用Java的动态代理有一个局限性就是代理的类必须要实现接口,虽然面向接口编程是每个优秀的Java程序都知道的规则,但现实往往不尽如人意,对于没有实现接口的类如何为其生成代理呢?继承!继承是最经典的扩展已有代码能力的手段,虽然继承常常被初学者滥用,但继承也常常被进阶的程序员忽视。CGLib采用非常底层的字节码生成技术,通过为一个类创建子类来生成代理,它弥补了Java动态代理的不足,因此Spring中动态代理和CGLib都是创建代理的重要手段,对于实现了接口的类就用动态代理为其生成代理类,而没有实现接口的类就用CGLib通过继承的方式为其创建代理。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

play_chess_ITmanito

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值