首页 > 代码库 > Java面试题集(151-180)

Java面试题集(151-180)

摘要:这部分包括了Spring、Spring MVC以及Spring和其它框架整合以及測试相关的内容,除此之外还包括了大型站点技术架构相关面试内容。


151. Spring中的BeanFactory和ApplicationContext有什么联系?

答:Spring通过配置文件描写叙述Bean以及Bean之间的依赖关系。利用Java的反射机制实现Bean的实例化,并建立Bean之间的依赖关系,在此基础上,Spring的IoC容器还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件公布、资源装载等高级服务。BeanFactory是Spring框架最核心的接口。它提供了IoC容器的配置机制。ApplicationContext建立在BeanFactory之上。提供了很多其它面向应用的功能,包含对国际化和框架事件体系的支持。

通常将BeanFactory称为IoC容器。而ApplicationContext称为应用上下文。前者更倾向于Spring本身。后者更倾向于开发人员,因此被使用得很多其它。

【补充】反射(reflection)又叫自省(introspection),是获得对象或类型元数据的方法,Java反射机制能够在执行时推断对象所属的类。在执行时构造随意一个类的对象。在执行时获得一个类的属性和方法,在执行时调用对象的方法,或者生成动态代理。

在Java中,能够通过类的Class对象获得类的构造器、属性、方法等类的元数据。还能够訪问这些属性或调用这些方法,和反射相关的类还包含:

  1. Constructor:代表类的构造器的类。

    通过Class对象的getConstructors方法能够获得类的全部构造器的数组。Java 5以后的版本号还能够通过getConstrcutor(Class... parameterTypes)获得拥有特定參数的构造器对象。Constructor对象的一个主要方法是newInstance,通过该方法能够创建一个类的实例。

  2. Method:代表方法的类。

    通过Class对象的getDeclaredMethods方法能够获得全部方法的数组,Java 5以后的版本号还能够通过getDeclaredMethod(String name, Class... parameterTypes)获得特定签名的方法,当中name是方法名,可变參数代表方法的參数列表。Method对象最重要的方法是invoke(Object obj, Object[] args),当中obj是调用该方法的目标对象。args是传给方法的參数,这样就能够调用指定对象的方法。此外,Method对象还包含下面重要方法:

    • Class getReturnType():获取方法的返回类型。
    • Class[] getParameterTypes():获取方法參数类型的数组。
    • Class[] getExceptionTypes():获取方法异常类型数组。
    • Annotation[][] getParameterAnnotations():获得方法參数注解信息的数组。
  3. Field:代表属性的类。通过Class对象的getDeclaredFields()方法能够获取类的属性数组。

    通过getDeclaredField(String name)则能够获取某个特定名称的属性。Field对象最重要的方法是set(Object obj, Object value),当中obj是目标对象,而value是要赋给属性的值。

除此之外,Java还提供了Package类用于包的反射,Java 5以后的版本号还提供了AnnotationElement类用于注解的反射。总之,Java的反射机制保证了能够通过编程的方式訪问目标类或对象的全部元素,对于被private和protected訪问修饰符修饰的成员。仅仅要JVM的安全机制同意,也能够通过反射进行调用。以下是一个反射的样例:

Car.java

package com.lovo;  
  
public class Car {  
    private String brand;  
    private int currentSpeed;  
    private int maxSpeed;  
  
    public Car(String brand, int maxSpeed) {  
        this.brand = brand;  
        this.maxSpeed = maxSpeed;  
    }  
      
    public void run() {  
        currentSpeed += 10;  
        System.out.println(brand + " is running at " + currentSpeed + " km/h");  
        if(currentSpeed > maxSpeed) {  
            System.out.println("It‘s dangerous!");  
        }  
    }  
  
}  

CarTest.java

package com.lovo;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  
  
public class CarTest {  
  
    public static void main(String[] args) throws Exception {  
        Constructor<Car> con = Car.class.getConstructor(String.class, int.class);  
        Car myCar = (Car) con.newInstance("Benz", 280);  
        Method m = myCar.getClass().getDeclaredMethod("run");  
        for(int i = 0; i < 10; i++) {  
            m.invoke(myCar);  
        }  
        Field f1 = myCar.getClass().getDeclaredField("maxSpeed");  
        Field f2 = myCar.getClass().getDeclaredField("brand");  
        f1.setAccessible(true);  
        f1.set(myCar, 80);  
        f2.setAccessible(true);  
        f2.set(myCar, "QQ");  
        m.invoke(myCar);  
    }  
}  


执行结果:

技术分享

152. 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类很easy好用,仅仅有四个方法。能用上的也就是以下三个方法:

  • void set(T value):设置当前线程的线程局部变量的值。
  • T get():获得当前线程所相应的线程局部变量的值。
  • void remove():删除当前线程中线程局部变量的值。

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

package com.lovo;  
  
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());  
    }  
}  


153. 你怎样理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?

答:

  • 连接点:程序运行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点。这些代码中的特定点就是连接点。

    Spring仅支持方法的连接点。

  • 切点:假设连接点相当于数据中的记录,那么切点相当于查询条件,一个切点能够匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到相应的连接点。

  • 增强:增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。非常多资料上将增强译为“通知”,这明显是个词不达意的翻译。让非常多程序猿困惑了许久。
  • 引介:引介是一种特殊的增强,它为类加入一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,能够动态的未该业务类加入接口的实现逻辑。让业务类成为这个接口的实现类。
  • 织入:织入是将增强加入到目标类详细连接点上的过程。AOP有三种织入方式:
    1. 编译期织入:须要特殊的Java编译期(比如AspectJ的ajc)
    2. 类装载期织入:要求使用特殊的类载入器
    3. 动态代理织入:在执行时为目标类生成代理实现增强
Spring採用了动态代理织入,而AspectJ採用了编译期织入和类装载期织入的方式。
  • 切面:切面是由切点和增强(引介)组成的,它包含了对横切关注功能的定义,也包含了对连接点的定义。

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

代理模式的类图例如以下所看到的:

技术分享

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

package com.lovo;  
  
/** 
 * 參考人员接口 
 */  
public interface Candidate {  
      
    /** 
     * 答题 
     */  
    public void answerTheQuestions();  
}  

package com.lovo;  
  
/** 
 * 懒学生 
 */  
public class LazyStudent implements Candidate {  
    private String name;        // 姓名  
      
    public LazyStudent(String name) {  
        this.name = name;  
    }  
      
    @Override  
    public void answerTheQuestions() {  
        // 懒学生仅仅能写出自己的名字不会答题  
        System.out.println("姓名: " + name);  
    }  
  
}  

package com.lovo;  
  
/** 
 * 枪手 
 * 
 */  
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("交卷");  
    }  
  
}  


package com.lovo;  
  
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的大小:

package com.lovo;  
  
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;  
    }  
  
}  

package com.lovo;  
  
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通过继承的方式为其创建代理。


154. Spring中自己主动装配的方式有哪些?

答:

  • no:不进行自己主动装配,手动设置Bean的依赖关系
  • byName:依据Bean的名字进行自己主动装配
  • byType:依据Bean的类型进行自己主动装配
  • constructor:类似于byType。只是是应用于构造器的參数。假设正好有一个Bean与构造器的參数类型同样则能够自己主动装配,否则会导致错误
  • autodetect:假设有默认的构造器,则通过constructor的方式进行自己主动装配,否则使用byType的方式进行自己主动装配
【注意】自己主动装配没有自己定义装配方式那么精确。并且不能自己主动装配简单属性(基本类型、字符串等)。在使用时应注意。


155. Spring中怎样使用注解来配置Bean?有哪些相关的注解?

答:首先须要在Spring配置文件里添加例如以下配置:

<context:component-scan base-package="org.example"/>  

然后能够用@Component、@Controller、@Service、@Repository注解来标注须要由Spring IoC容器进行对象托管的类。


156. Spring支持的事务管理类型有哪些?你在项目中使用哪种方式?

答:Spring支持编程式事务管理和声明式事务管理。很多Spring框架的用户选择声明式事务管理,由于这样的方式和应用程序的关联较少。因此更加符合轻量级容器的概念。声明式事务管理要优于编程式事务管理。虽然在灵活性方面它弱于编程式事务管理(编程式事务同意你通过代码控制业务)。


157. 怎样在Web项目中配置Spring的IoC容器?

答:假设须要在Web项目中使用Spring的IoC容器。能够在Web项目配置文件web.xml中做出例如以下配置:

<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>  
</context-param>  
  
<listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>  


158. 怎样在Web项目中配置Spring MVC?

答:要使用Spring MVC须要在Web项目配置文件里配置其前端控制器DispatcherServlet,例如以下所看到的:

<web-app>  
  
    <servlet>  
        <servlet-name>example</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <load-on-startup>1</load-on-startup>  
    </servlet>  
  
    <servlet-mapping>  
        <servlet-name>example</servlet-name>  
        <url-pattern>*.html</url-pattern>  
    </servlet-mapping>  
  
</web-app>  


【注意】上面的配置中使用了*.html的后缀映射,这样做一方面不能够通过URL判断採用了何种server端的技术,还有一方面能够欺骗搜索引擎,由于搜索引擎不会搜索动态页面。这样的做法能够称为伪静态化。


159. Spring MVC的工作原理是如何的?

答:Spring MVC的工作原理例如以下图所看到的:

技术分享

  1. client的全部请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其它模块来真正处理用户的请求。
  2. DispatcherServlet收到请求后。将依据请求的信息(包括URL、HTTP协议方法、请求头、请求參数、Cookie等)以及HandlerMapping的配置找到处理该请求的Handler(不论什么一个对象都能够作为请求的Handler)。

    当然,在这个地方Spring会通过HandlerAdapter对该处理器进行封装,HandlerAdapter是一个适配器,它用统一的接口对各种Handler中的方法进行调用。

  3. Handler完毕对用户请求的处理后。会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义。包括了数据模型以及对应的视图的信息。当然,这里的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完毕从逻辑视图到真实视图对象的解析工作。
  4. 当得到真正的视图对象后。Dispatcher会利用视图对象对模型数据进行渲染。
  5. client得到响应。可能是一个普通的HTML页面,也能够是XML或JSON字符串,还能够是一张图片或者一个PDF文件。

160. 怎样在Spring IoC容器中配置数据源?

答:

  • DBCP配置:
<bean id="dataSource"  
        class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
    <property name="driverClassName" value="http://www.mamicode.com/${jdbc.driverClassName}"/>  
    <property name="url" value="http://www.mamicode.com/${jdbc.url}"/>  
    <property name="username" value="http://www.mamicode.com/${jdbc.username}"/>  
    <property name="password" value="http://www.mamicode.com/${jdbc.password}"/>  
</bean>  
  
<context:property-placeholder location="jdbc.properties"/>  


  • C3P0配置:
<bean id="dataSource"  
        class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
    <property name="driverClass" value="http://www.mamicode.com/${jdbc.driverClassName}"/>  
    <property name="jdbcUrl" value="http://www.mamicode.com/${jdbc.url}"/>  
    <property name="user" value="http://www.mamicode.com/${jdbc.username}"/>  
    <property name="password" value="http://www.mamicode.com/${jdbc.password}"/>  
</bean>  
  
<context:property-placeholder location="jdbc.properties"/>  


161. 怎样配置配置事务增强?

答:

<?

xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the transactional advice (what ‘happens‘; see the <aop:advisor/> bean below) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with ‘get‘ are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface --> <aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> </aop:config> <!-- don‘t forget the DataSource --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="http://www.mamicode.com/oracle.jdbc.driver.OracleDriver"/> <property name="url" value="http://www.mamicode.com/jdbc:oracle:thin:@rj-t42:1521:elvis"/> <property name="username" value="http://www.mamicode.com/scott"/> <property name="password" value="http://www.mamicode.com/tiger"/> </bean> <!-- similarly, don‘t forget the PlatformTransactionManager --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>


162. 选择使用Spring框架的原因(Spring框架为企业级开发带来的优点)?

答:能够从下面几个方面作答:

  1. 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类。
  2. IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系。对象之间的依赖关系假设发生了改变仅仅须要改动配置文件而不是改动代码。由于代码的改动可能意味着项目的又一次构建和完整的回归測试。有了IoC容器。程序猿再也不须要自己编写工厂、单例,这一点特别符合Spring的精神“不要反复的发明轮子”。
  3. AOP:面向切面编程。将所有的横切关注功能封装到切面(aspect)中。通过配置的方式将横切关注功能动态加入到目标代码上。进一步实现了业务逻辑和系统服务之间的分离。还有一方面。有了AOP程序猿能够省去非常多自己写代理类的工作。
  4. MVC:Spring的MVC框架是非常优秀的,从各个方面都能够甩Struts 2几条街。为Web表示层提供了更好的解决方式。

  5. 事务管理:Spring以宽广的胸怀接纳多种持久层技术(我想这一点与Rod Johnson音乐学博士的身份不无关系,这一点特别值得Gavin King学习,上天不可能赋予一个人太多的长处以至于他没有表达谦虚的余地。关于这二位的轶事能够自己百度一下),而且为其提供了声明式的事务管理,在不须要不论什么一行代码的情况下就能够完毕事务管理。

  6. 其它:选择Spring框架的原因还远不止于此。Spring为Java企业级开发提供了一站式选择,你能够在须要的时候使用它的部分和所有,更重要的是,你甚至能够在感觉不到Spring存在的情况下。在你的项目中使用Spring提供的各种优秀的功能。

163. 依赖注入的方式以及你在项目中的选择?

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


164. 提供Spring IoC容器配置元数据的方式?

答:

  1. 基于XML文件进行配置。
  2. 基于注解进行配置。

  3. 基于Java程序进行配置(Spring 3+)
package com.lovo.bean;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
  
@Component  
public class Person {  
    private String name;  
    private int age;  
    @Autowired  
    private Car car;  
      
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    public void setCar(Car car) {  
        this.car = car;  
    }  
  
    @Override  
    public String toString() {  
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";  
    }  
  
}  

package com.lovo.bean;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class Car {  
    private String brand;  
    private int maxSpeed;  
  
    public Car(String brand, int maxSpeed) {  
        this.brand = brand;  
        this.maxSpeed = maxSpeed;  
    }  
  
    @Override  
    public String toString() {  
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";  
    }  
  
}  

package com.lovo.config;  
  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
import com.lovo.bean.Car;  
import com.lovo.bean.Person;  
  
@Configuration  
public class AppConfig {  
  
    @Bean  
    public Car car() {  
        return new Car("Benz", 320);  
    }  
      
    @Bean  
    public Person person() {  
        return new Person("Somnus", 34);  
    }  
}  

package com.lovo.test;  
  
import org.springframework.context.ConfigurableApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  
import com.lovo.bean.Person;  
import com.lovo.config.AppConfig;  
  
class Test {  
  
    public static void main(String[] args) {  
        // TWR (Java 7+)  
        try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class)) {  
            Person person = factory.getBean(Person.class);  
            System.out.println(person);  
        }  
    }  
}  


165. Spring提供了哪些Bean的作用域?

答:

  • singleton:Bean以单例的方式存在(IoC容器中仅存在该Bean的唯一实例)。

  • prototype:每次从容器中获取Bean的实例时,都会返回一个新的实例(原型模式)。
  • request:每次HTTP请求都会创建新的实例,该作用域仅适用于WebApplicationContext环境。
  • session:同一个HTTP会话共享一个Bean的实例,不同的HTTP会话使用不同的Bean实例,该作用域仅适用于WebApplicationContext环境。

  • globalSession:同一个全局会话共享一个Bean的实例,一般应用于Portlet应用中。


166. 阐述Spring框架中Bean的生命周期?

答:

  1. Spring IoC容器找到关于Bean的定义并实例化该Bean。
  2. Spring IoC容器对Bean进行依赖注入。
  3. 假设Bean实现了BeanNameAware接口,则将该Bean的id传给setBeanName方法。

  4. 假设Bean实现了BeanFactoryAware接口,则将BeanFactory对象传给setBeanFactory方法。
  5. 假设Bean实现了BeanPostProcessor接口,则调用其postProcessBeforeInitialization方法。
  6. 假设Bean实现了InitializingBean接口。则调用其afterPropertySet方法。
  7. 假设有和Bean关联的BeanPostProcessors对象,则这些对象的postProcessAfterInitialization方法被调用。

  8. 当销毁Bean实例时,假设Bean实现了DisposableBean接口。则调用其destroy方法。


167. 依赖注入时怎样注入集合属性?

答:能够在定义Bean属性时。通过<list>/<set>/<map>/<props>分别为其注入列表、集合、映射和键值都是字符串的映射属性。


168. Spring中的自己主动装配有哪些限制?

答:

  1. 假设使用了构造器注入或者setter注入。那么将覆盖自己主动装配的依赖关系。
  2. 基本数据类型的值、字符串字面量、类字面量无法使用自己主动装配来注入。

  3. 有先考虑使用显式的装配来进行更精确的依赖注入而不是使用自己主动装配。


169. 和自己主动装配相关的注解有哪些?

答:

  • @Required:该依赖关系必须装配(手动或自己主动装配)。否则将抛出BeanInitializationException异常。

  • @Autowired:自己主动装配,默认按类型进行自己主动装配。
  • @Qualifier:假设按类型自己主动装配时有不止一个匹配的类型。那么能够使用该注解指定名字来消除歧义。


170. 怎样使用HibernateDaoSupport整合Spring和Hibernate?

答:

  1. 在Spring中配置Hibernate的会话工厂(LocalSessionFactoryBean或AnnotationSessionFactoryBean)。
  2. 让DAO的实现类继承HibernateDaoSupport(继承getHibernateTemplate方法来调用模板方法)。

  3. 让Spring来管理Hibernate的事务(推荐使用声明式事务)。


171. 你是怎样理解“横切关注”这个概念的?

答:“横切关注”是会影响到整个应用程序的关注功能,它跟正常的业务逻辑是正交的。没有必定的联系,可是差点儿全部的业务逻辑都会涉及到这些关注功能。通常,事务、日志、安全性等关注就是应用中的横切关注功能。


172. 怎样理解Spring AOP中Advice这个概念?

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


173. 在Web项目中怎样获得Spring的IoC容器?

答:

WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); 


174. Spring MVC怎样对RESTful风格提供支持?

答:假设不了解RESTful能够看看百度百科的解说。关于这个问题,能够看看blogjava上的还有一个帖子。


175. 大型站点在架构上应当考虑哪些问题?

答:

  • 分层:分层是处理不论什么复杂系统最常见的手段之中的一个,将系统横向切分成若干个层面。每一个层面仅仅承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形成一个完整的复杂的系统。

    计算机网络的开放系统互联參考模型(OSI/RM)和Internet的TCP/IP模型都是分层结构,大型站点的软件系统也能够使用分层的理念将其分为持久层(提供数据存储和訪问服务)、业务层(处理业务逻辑。系统中最核心的部分)和表示层(系统交互、视图展示)。

    须要指出的是:(1)分层是逻辑上的划分,在物理上能够位于同一设备上也能够在不同的设备上部署不同的功能模块,这样能够使用很多其它的计算资源来应对用户的并发訪问;(2)层与层之间应当有清晰的边界。这样分层才有意义。才更利于软件的开发和维护。

  • 切割:切割是对软件的纵向切分。我们能够将大型站点的不同功能和服务切割开,形成高内聚低耦合的功能模块(单元)。在设计初期能够做一个粗粒度的切割,将站点切割为若干个功能模块。后期还能够进一步对每一个模块进行细粒度的切割,这样一方面有助于软件的开发和维护,还有一方面有助于分布式的部署。提供站点的并发处理能力和功能的扩展。
  • 分布式:除了上面提到的内容,站点的静态资源(JavaScript、CSS、图片等)也能够採用独立分布式部署并採用独立的域名。这样能够减轻应用server的负载压力,也使得浏览器对资源的载入更快。数据的存取也应该是分布式的。传统的商业级关系型数据库产品基本上都支持分布式部署。而新生的NoSQL产品差点儿都是分布式的。当然。站点后台的业务处理也要使用分布式技术,比如查询索引的构建、数据分析等,这些业务计算规模庞大。能够使用Hadoop以及MapReduce分布式计算框架来处理。

  • 集群:集群使得有很多其它的server提供同样的服务。能够更好的提供对并发的支持。

  • 缓存:所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算近期的位置。

    使用缓存是站点优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。

  • 异步:异步是实现软件实体之间解耦合的又一重要手段。异步架构是典型的生产者消费者模式。二者之间没有直接的调用关系,仅仅要保持数据结构不变,彼此功能实现能够任意变化而不互相影响。这对站点的扩展很有利。使用异步处理还能够提高系统可用性,加快站点的响应速度(用Ajax载入数据就是一种异步技术)。同一时候还能够起到削峰作用(应对瞬时高并发)。“能推迟处理的都要推迟处理”是站点优化的第二定律,而异步是践行站点优化第二定律的重要手段。
  • 冗余:各种server都要提供对应的冗余server以便在某台或某些server宕机时还能保证站点能够正常工作,同一时候也提供了灾难恢复的可能性。冗余是站点高可用性的重要保证。


176. 你用过的站点前端优化的技术有哪些?

答:

  1. 浏览器訪问优化
    • 降低HTTP请求数量:合并CSS、合并JavaScript、合并图片(CSS Sprite)
    • 使用浏览器缓存:通过设置HTTP响应头中的Cache-Control和Expires属性。将CSS、JavaScript、图片等在浏览器中缓存,当这些静态资源须要更新时,能够更新HTML文件里的引用来让浏览器又一次请求新的资源
    • 启用压缩
    • CSS前置,JavaScript后置
    • 降低Cookie传输
  2. CDN加速:CDN(Content Distribute Network)的本质仍然是缓存,将数据缓存在离用户近期的地方,CDN通常部署在网络运营商的机房,不仅能够提升响应速度,还能够降低应用server的压力。当然,CDN缓存的通常都是静态资源。
  3. 反向代理:反向代理相当于应用server的一个门面,能够保护站点的安全性,也能够实现负载均衡的功能,当然最重要的是它缓存了用户訪问的热点资源,能够直接从反向代理将某些内容返回给用户浏览器。


177. 你使用过的应用server优化技术有哪些?

答:

  1. 分布式缓存:缓存的本质就是内存中的哈希表,假设设计一个优质的哈希函数,那么理论上哈希表读写的渐近时间复杂度为O(1)。

    缓存主要用来存放那些读写比非常高、变化非常少的数据。这样应用程序读取数据时先到缓存中读取。假设没有或者数据已经失效再去訪问数据库或文件系统。并依据拟定的规则将数据写入缓存。对站点数据的訪问也符合二八定律(Pareto分布,幂律分布)。即80%的訪问都集中在20%的数据上,假设能够将这20%的数据缓存起来,那么系统的性能将得到显著的改善。当然,使用缓存须要解决下面几个问题:(1)频繁改动的数据;(2)数据不一致与脏读。(3)缓存雪崩(能够採用分布式缓存server集群加以解决。Memcached是广泛採用的解决方式,它是一种互不通信的集中式管理的分布式缓存方案,能够从http://memcached.org/了解到关于Memcached的相关信息)。(4)缓存预热;(5)缓存穿透(恶意持续请求不存在的数据)。

  2. 异步操作:能够使用消息队列将调用异步化,通过异步处理将短时间高并发产生的事件消息存储在消息队列中,从而起到削峰作用。

    电商站点在进行促销活动时,能够将用户的订单请求存入消息队列。这样能够抵御大量的并发订单请求对系统和数据库的冲击。

    眼下。绝大多数的电商站点即便不进行促销活动,订单系统都採用了消息队列来处理。

  3. 使用集群
  4. 代码优化
    • 多线程:基于Java的Web开发基本上都通过多线程的方式响应用户的并发请求,使用多线程技术在编程上要解决线程安全问题,主要能够考虑下面几个方面:
      • 将对象设计为无状态对象(这和面向对象的编程观点是矛盾的,在面向对象的世界中被视为不良设计),这样就不会存在并发訪问时对象状态不一致的问题。
      • 在方法内部创建对象。这样对象由进入方法的线程创建。不会出现多个线程訪问同一对象的问题。

        使用ThreadLocal将对象与线程绑定也是非常好的做法,这一点在前面已经探讨过了。

      • 对资源进行并发訪问时应当使用合理的锁机制。
    • 非堵塞I/O: 使用单线程和非堵塞I/O是眼下公认的比多线程的方式更能充分发挥server性能的应用模式。基于Node.js构建的server就採用了这种方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3规范中又引入了异步Servlet的概念。这些都为在server端採用非堵塞I/O提供了必要的基础。

    • 资源复用:资源复用主要有两种方式。一是单例。而是对象池,我们使用的数据库连接池、线程池都是对象池化技术,这是典型的用空间换取时间的策略,还有一方面也实现对资源的复用,从而避免了不必要的创建和释放资源所带来的开销。


178. 什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?

答:

  • XSS(Cross Site Script,跨站脚本攻击)是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中运行恶意脚本的攻击方式。跨站脚本攻击分有两种形式:反射型攻击(诱使用户点击一个嵌入恶意脚本的链接以达到攻击的目标,眼下有非常多攻击者利用论坛、微博公布含有恶意脚本的URL就属于这样的方式)和持久型攻击(将恶意脚本提交到被攻击站点的数据库中,用户浏览网页时,恶意脚本从数据库中被载入到页面运行。QQ邮箱的早期版本号就以前被利用作为持久型跨站脚本攻击的平台)。XSS尽管不是什么新奇玩意,可是攻击的手法却不断翻新,防范XSS主要有双方面:消毒(对危急字符进行转义)和HttpOnly(防范XSS攻击者窃取Cookie数据)。
  • SQL注入攻击是注入攻击最常见的形式(此外还有OS注入攻击(Struts 2的高危漏洞就是通过OGNL实施OS注入攻击导致的)),当server使用请求參数构造SQL语句时。恶意的SQL被嵌入到SQL中交给数据库运行。SQL注入攻击需要攻击者对数据库结构有所了解才干进行,攻击者想要获得表结构有多种方式:(1)假设使用开源系统搭建站点。数据库结构也是公开的(眼下有非常多现成的系统能够直接搭建论坛,电商站点。尽管方便快捷可是风险是必需要认真评估的)。(2)错误回显(假设将server的错误信息直接显示在页面上,攻击者能够通过非法參数引发页面错误从而通过错误信息了解数据库结构,Web应用应当设置友好的错误页,一方面符合最小吃惊原则,一方面屏蔽掉可能给系统带来危急的错误回显信息);(3)盲注。防范SQL注入攻击也能够採用消毒的方式。通过正則表達式对请求參数进行验证,此外,參数绑定也是非常好的手段,这样恶意的SQL会被当做SQL的參数而不是命令被运行。JDBC中的PreparedStatement就是支持參数绑定的语句对象。从性能和安全性上都明显优于Statement。

  • CSRF攻击(Cross Site Request Forgery,跨站请求伪造)是攻击者通过跨站请求,以合法的用户身份进行非法操作(如转账或发帖等)。

    CSRF的原理是利用浏览器的Cookie或server的Session,盗取用户身份。其原理例如以下图所看到的。防范CSRF的主要手段是识别请求者的身份,主要有下面几种方式:(1)在表单中加入令牌(token);(2)验证码。(3)检查请求头中的Referer(前面提到防图片盗链接也是用的这样的方式)。令牌和验证都具有一次消费性的特征,因此在原理上一致的。可是验证码是一种糟糕的用户体验,不是必要的情况下不要轻易使用验证码,眼下非常多站点的做法是假设在短时间内多次提交一个表单未获得成功后才要求提供验证码,这样会获得较好的用户体验。

技术分享
【补充】防火墙的架设是Web安全的重要保障。ModSecurity是开源的Web防火墙中的佼佼者。有兴趣的能够在http://www.modsecurity.org/站点获得相关信息。

企业级防火墙的架设应当有两级防火墙。Webserver和部分应用server能够架设在两级防火墙之间的DMZ,而数据和资源server应当架设在第二级防火墙之后。例如以下图所看到的。

技术分享

179. 什么是领域模型(domain model)?贫血模型(anaemic domain model) 

和充血模型(rich domain model)有什么差别?

答:领域模型是领域内的概念类或现实世界中对象的可视化表示。又称为概念模型或分析对象模型。它专注于分析问题领域本身,发掘重要的业务领域概念。并建立业务领域概念之间的关系。贫血模型是指使用的领域对象中仅仅有setter和getter方法(POJO),全部的业务逻辑都不包括在领域对象中而是放在业务逻辑层。有人将我们这里说的贫血模型进一步划分成失血模型(领域对象全然没有业务逻辑)和贫血模型(领域对象有少量的业务逻辑)。我们这里就不正确此加以区分了。充血模型将大多数业务逻辑和持久化放在领域对象中。业务逻辑(业务门面)仅仅是完毕对业务逻辑的封装、事务和权限等的处理。

以下两张图分别展示了贫血模型和充血模型的分层架构。

技术分享
贫血模型

技术分享
充血模型

贫血模型下组织领域逻辑通常使用事务脚本模式。让每一个过程相应用户可能要做的一个动作。每一个动作由一个过程来驱动。

也就是说在设计业务逻辑接口的时候。每一个方法相应着用户的一个操作,这样的模式有下面几个有点:

  1. 它是一个大多数开发人员都可以理解的简单过程模型(适合国内的绝大多数开发人员)。
  2. 它可以与一个使用行数据入口或表数据入口的简单数据訪问层非常好的协作。

  3. 事务边界的显而易见。一个事务開始于脚本的開始,终止于脚本的结束,非常easy通过代理(或切面)实现声明式事务。
然后,事务脚本模式的缺点也是非常多的,随着领域逻辑复杂性的添加。系统的复杂性将迅速添加,程序结构将变得极度混乱。开源中国社区上有一篇非常好的译文《贫血领域模型是怎样导致糟糕的软件产生》对这个问题做了比較仔细的阐述。

180. 谈一谈測试驱动开发(TDD)的优点以及你的理解。
答:TDD是指在编写真正的功能实现代码之前先写測试代码,然后依据须要重构实现代码。在JUnit的作者Kent Beck的大作《測试驱动开发:实战与模式解析》(Test-Driven Development: by Example)一书中有这么一段内容:“消除恐惧和不确定性是编写測试驱动代码的重要原因”。由于编写代码时的恐惧会让你小心试探,让你回避沟通,让你羞于得到反馈,让你变得焦躁不安,而TDD是消除恐惧、让Java开发人员更加自信更加乐于沟通的重要手段。TDD会带来的优点可能不会立即呈现,可是你在某个时候一定会发现,这些优点包含:
  1. 更清晰的代码 --- 仅仅写须要的代码
  2. 更好的设计
  3. 更出色的灵活性 --- 鼓舞程序猿面向接口编程
  4. 更高速的反馈 --- 不会到系统上线时才知道bug的存在
【补充:】敏捷软件开发的概念已经有非常多年了,并且也部分的改变了软件开发这个行业。TDD也是敏捷开发所倡导的。

技术分享技术分享

TDD能够在多个层级上应用,包含单元測试(測试一个类中的代码)、集成測试(測试类之间的交互)、系统測试(測试执行的系统)和系统集成測试(測试执行的系统包含使用的第三方组件)。TDD的实施步骤是:红(失败測试) --- 绿(通过測试) --- 重构。

关于实施TDD的具体步骤请參考还有一篇文章《測试驱动开发之初窥门径》。

在使用TDD开发时。常常会遇到须要被測对象须要依赖其它子系统的情况,可是你希望将測试代码跟依赖项隔离。以保证測试代码只针对当前被測对象或方法展开。这时候你须要的是測试替身。測试替身能够分为四类:

  1. 虚设替身:仅仅传递可是不会使用到的对象,一般用于填充方法的參数列表
  2. 存根替身:总是返回同样的预设响应,当中可能包含一些虚设状态
  3. 伪装替身:能够代替真实版本号的可用版本号(比真实版本号还是会差非常多)
  4. 模拟替身:能够表示一系列期望值的对象,而且能够提供预设响应
Java世界中实现模拟替身的第三方工具许多,包含EasyMock、Mockito、jMock等。

技术分享

Java面试题集(151-180)