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对象获得类的构造器、属性、方法等类的元数据,还可以访问这些属性或调用这些方法,和反射相关的类还包括:
除此之外,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类非常简单好用,只有四个方法,能用上的也就是下面三个方法:
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()); } }
答:
Spring采用了动态代理织入,而AspectJ采用了编译期织入和类装载期织入的方式。
【补充】代理模式是GoF提出的23种设计模式中最为经典的模式之一,代理模式是对象的结构模式,它给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。简单的说,代理对象可以完成比原对象更多的职责,当需要为原对象添加横切关注功能时,就可以使用原对象的代理对象。我们在打开Office系列的Word文档时,如果文档中有插图,当文档刚加载时,文档中的插图都只是一个虚框占位符,等用户真正翻到某页要查看该图片时,才会真正加载这张图,这其实就是对代理模式的使用,代替真正图片的虚框就是一个虚拟代理;Hibernate的load方法也是返回一个虚拟代理对象,等用户真正需要访问对象的属性时,才向数据库发出SQL语句获得真实对象。代理模式的类图如下所示:
下面用一个找枪手代考的例子演示代理模式的使用:
package com.lovo; /** * 参考人员接口 * @author 骆昊 * */ public interface Candidate { /** * 答题 */ public void answerTheQuestions(); }
package com.lovo; /** * 懒学生 * @author 骆昊 * */ 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; /** * 枪手 * @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("交卷"); } }
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中自动装配的方式有哪些?
答:
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推断采用了何种服务器端的技术,另一方面可以欺骗搜索引擎,因为搜索引擎不会搜索动态页面,这种做法可以称为伪静态化。
159. Spring MVC的工作原理是怎样的?
答:Spring MVC的工作原理如下图所示:
160. 如何在Spring IoC容器中配置数据源?
答:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${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="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> <property name="username" value="scott"/> <property name="password" value="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框架为企业级开发带来的好处)?
答:可以从以下几个方面作答:
163. 依赖注入的方式以及你在项目中的选择?
答:依赖注入可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。
164. 提供Spring IoC容器配置元数据的方式?
答:
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("骆昊", 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); } } }
答:
166. 阐述Spring框架中Bean的生命周期?
答:
167. 依赖注入时如何注入集合属性?
答:可以在定义Bean属性时,通过<list>/<set>/<map>/<props>分别为其注入列表、集合、映射和键值都是字符串的映射属性。
168. Spring中的自动装配有哪些限制?
答:
169. 和自动装配相关的注解有哪些?
答:
170. 如何使用HibernateDaoSupport整合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. 大型网站在架构上应当考虑哪些问题?
答:
176. 你用过的网站前端优化的技术有哪些?
答:
177. 你使用过的应用服务器优化技术有哪些?
答:
178. 什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?
答:
TDD可以在多个层级上应用,包括单元测试(测试一个类中的代码)、集成测试(测试类之间的交互)、系统测试(测试运行的系统)和系统集成测试(测试运行的系统包括使用的第三方组件)。TDD的实施步骤是:红(失败测试) --- 绿(通过测试) --- 重构。关于实施TDD的详细步骤请参考另一篇文章《测试驱动开发之初窥门径》。
在使用TDD开发时,经常会遇到需要被测对象需要依赖其他子系统的情况,但是你希望将测试代码跟依赖项隔离,以保证测试代码仅仅针对当前被测对象或方法展开,这时候你需要的是测试替身。测试替身可以分为四类: