140、谈一谈的一级缓存、二级缓存和查询缓存。 答的提供了一级缓存的功能,默认总是有效的,当应用程序保存持久化实体、修改持久化实体时,并不会立即把这种改变提交到数据库,而是缓存在当前的中,除非显示调用了的()方法或通过()方法关闭。通过一级缓存,可以减少程序与数据库的交互,从而提高数据库访问性能。

级别的二级缓存是全局性的,所有的可以共享这个二级缓存。不过二级缓存默认是关闭的,需要显示开启并指定需要使用哪种二级缓存实现类(可以使用第三方提供的实现)。一旦开启了二级缓存并设置了需要使用二级缓存的实体类,就会缓存访问过的该实体类的每个对象,除非缓存的数据超出了指定的缓存空间。 一级缓存和二级缓存都是对整个实体进行缓存,不会缓存普通属性,如果希望对普通属性进行缓存,可以使用查询缓存。查询缓存是将HQL或SQL语句以及它们的查询结果作为键值对进行缓存,对于同样的查询可以直接从缓存中获取数据。查询缓存默认也是关闭的,需要显示开启。141、中类是做什么的? 答和的用法基本上是一致的,但是由的()方法创建的,也就意味着离开创建它的,就无法使用了。不需要就可以创建(使用.()方法创建),所以通常也称其为离线的,在需要进行查询操作的时候再和绑定(调用其()方法),这也就意味着一个可以在需要的时候和不同的进行绑定。142、@注解的属性有什么作用? 答:@用来配置一对多关联映射,但通常情况下,一对多关联映射都由多的一方来维护关联关系,例如学生和班级,应该在学生类中添加班级属性来维持学生和班级的关联关系(在数据库中是由学生表中的外键班级编号来维护学生表和班级表的多对一关系),如果要使用双向关联,在班级类中添加一个容器属性来存放学生,并使用@注解进行映射,此时属性就非常重要。如果使用XML进行配置,可以用set标签的="true"设置来达到同样的效果。143、中使用#和$书写占位符有什么区别? 答:#将传入的数据都当成一个字符串,会对传入的数据自动加上引号;$将传入的数据直接显示生成在SQL中。注意:使用$占位符可能会导致SQL注射攻击,能用#的地方就不要使用$,写 by子句的时候应该用$而不是#。144、解释一下中命名空间()的作用。 答:在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。145、中的动态SQL是什么意思? 答:对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,例如在58同城上面找房子,我们可能会指定面积、楼层和所在位置来查找房源,也可能会指定面积、价格、户型和所在位置来查找房源,此时就需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好提供了动态SQL的功能来解决这个问题。中用于实现动态SQL的元素主要有: - if - / when / - trim - - set - 下面是映射文件的片段。
 id="foo" ="Blog" ="Blog" * from   1 = 1if test=" != null"and  = #{}/ifif test=" != null"and  = #{}/ifif test=" != null"and  = #{}/if/
当然也可以像下面这些书写。
 id="foo" ="Blog" ="Blog" * from   1 =  test=" != null"and  = #{}/ test=" != null"and  = #{}/  = ""///
再看看下面这个例子。
 id="bar" ="Blog" * from   id  ="" =""item="item" open="(" ="," =")"#{item}//
146、什么是IoC和DI?DI是如何实现的? 答:IoC叫控制反转,是 of 的缩写,DI( )叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的"控制反转"就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。IoC体现了好莱坞原则 - "Don’t call me, we will call you"。依赖注入的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。举个例子:一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。依赖注入可以通过方法注入(设值注入)、构造器注入和接口注入三种方式来实现,支持注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则注入是更好的选择,注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。147、中Bean的作用域有哪些? 答:在的早期版本中,仅有两个作用域和,前者表示Bean以单例的方式存在;后者表示每次从容器中调用Bean时,都会返回一个新的实例,通常翻译为原型。补充:设计模式中的创建型模式中也有一个原型模式,原型模式也是一个常用的模式,例如做一个室内设计软件,所有的素材都在工具箱中,而每次从工具箱中取出的都是素材对象的一个原型,可以通过对象克隆来实现原型模式。 2.x中针对新增了3个作用域,分别是(每次HTTP请求都会创建一个新的Bean)、(同一个共享同一个Bean,不同的使用不同的Bean)和(同一个全局共享一个Bean)。说明:单例模式和原型模式都是重要的设计模式。一般情况下,无状态或状态不可变的类适合使用单例模式。在传统开发中,由于DAO持有这个非线程安全对象因而没有使用单例模式;但在环境下,所有DAO类对可以采用单例模式,因为利用AOP和Java API中的对非线程安全的对象进行了特殊处理。为解决多线程程序的并发问题提供了一种新的思路。,顾名思义是线程的一个本地化对象,当工作于多线程中的对象使用维护变量时,为每个使用该变量的线程分配一个独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量。类非常简单好用,只有四个方法,能用上的也就是下面三个方法: - void set(T ):设置当前线程的线程局部变量的值。 - T get():获得当前线程所对应的线程局部变量的值。 - void ():删除当前线程中线程局部变量的值。是如何做到为每一个线程维护一份独立的变量副本的呢?在类中有一个Map,键为线程对象,值是其线程对应的变量的副本,自己要模拟实现一个类其实并不困难,代码如下所示:
 java.util.; java.util.; java.util.Map;   { , T map = .(new , T()); void set(T ) {map.put(.(), );} T get() { map.get(.());} void () {map.(.());}}
148、解释一下什么叫AOP(面向切面编程)? 答:AOP(- )指一种程序设计范型,该范型以一种称为切面()的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点( )。149、你是如何理解"横切关注"这个概念的? 答:"横切关注"是会影响到整个应用程序的关注功能,它跟正常的业务逻辑是正交的,没有必然的联系,但是几乎所有的业务逻辑都会涉及到这些关注功能。通常,事务、日志、安全性等关注就是应用中的横切关注功能。150、你如何理解AOP中的连接点()、切点()、增强()、引介()、织入()、切面()这些概念? 答: a. 连接点():程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。仅支持方法的连接点。 b. 切点():如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。 AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。 c. 增强():增强是织入到目标类连接点上的一段程序代码。提供的增强接口都是带方位名的,如等。很多资料上将增强译为“通知”,这明显是个词不达意的翻译,让很多程序员困惑了许久。说明: 在国内的很多书面资料中都被翻译成"通知",但是很显然这个翻译无法表达其本质,有少量的读物上将这个词翻译为"增强",这个翻译是对较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有业务逻辑的一种增强,这种增强可以是前置增强、后置增强、返回后增强、抛异常时增强和包围型增强。d. 引介():引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的未该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。 e. 织入():织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:①编译期织入:需要特殊的Java编译期(例如的ajc);②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;③运行时织入:在运行时为目标类生成代理实现增强。采用了动态代理的方式实现了运行时织入,而采用了编译期织入和装载期织入的方式。 f. 切面():切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。补充:代理模式是GoF提出的23种设计模式中最为经典的模式之一,代理模式是对象的结构模式,它给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。简单的说,代理对象可以完成比原对象更多的职责,当需要为原对象添加横切关注功能时,就可以使用原对象的代理对象。我们在打开系列的Word文档时,如果文档中有插图,当文档刚加载时,文档中的插图都只是一个虚框占位符,等用户真正翻到某页要查看该图片时,才会真正加载这张图,这其实就是对代理模式的使用,代替真正图片的虚框就是一个虚拟代理;的load方法也是返回一个虚拟代理对象,等用户真正需要访问对象的属性时,才向数据库发出SQL语句获得真实对象。下面用一个找枪手代考的例子演示代理模式的使用:
/** * 参考人员接口 * @ 骆昊 * */   {/*** 答题*/ void ();}

/** * 懒学生 * @ 骆昊 * */     {  name;// 姓名 ( name) {this.name = name;}@ void () {// 懒学生只能写出自己的名字不会答题.out.("姓名: " + name);}}

/** * 枪手 * @ 骆昊 * */     {  ;// 被代理对象 ( ) {this. = ;}@ void () {// 枪手要写上代考的学生的姓名.();// 枪手要帮助懒学生答题并交卷.out.("奋笔疾书正确答案");.out.("交卷");}}

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

 java.lang..; java.util.; java.util.List;   {@("")  void main([] args) { list = new ();?  = list.();  = new (list);  = ().(.(),.(), );.add("");.add("");.add("");.("");}}
说明:使用Java的动态代理有一个局限性就是代理的类必须要实现接口,虽然面向接口编程是每个优秀的Java程序都知道的规则,但现实往往不尽如人意,对于没有实现接口的类如何为其生成代理呢?继承!继承是最经典的扩展已有代码能力的手段,虽然继承常常被初学者滥用,但继承也常常被进阶的程序员忽视。采用非常底层的字节码生成技术,通过为一个类创建子类来生成代理,它弥补了Java动态代理的不足,因此中动态代理和都是创建代理的重要手段,对于实现了接口的类就用动态代理为其生成代理类,而没有实现接口的类就用通过继承的方式为其创建代理。151、中自动装配的方式有哪些? 答: - no:不进行自动装配,手动设置Bean的依赖关系。 - :根据Bean的名字进行自动装配。 - :根据Bean的类型进行自动装配。 - :类似于,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。 - :如果有默认的构造器,则通过的方式进行自动装配,否则使用的方式进行自动装配。说明:自动装配没有自定义装配方式那么精确,而且不能自动装配简单属性(基本类型、字符串等),在使用时应注意。152、中如何使用注解来配置Bean?有哪些相关的注解? 答:首先需要在配置文件中增加如下配置:
-scan base-="org."/
然后可以用@@@@注解来标注需要由 IoC容器进行对象托管的类。这几个注解没有本质区别,只不过@通常用于控制器,@通常用于业务逻辑类,@通常用于仓储类(例如我们的DAO实现类),普通的类用@来标注。153、支持的事务管理类型有哪些?你在项目中使用哪种方式? 答支持编程式事务管理和声明式事务管理。许多框架的用户选择声明式事务管理,因为这种方式和应用程序的关联较少,因此更加符合轻量级容器的概念。声明式事务管理要优于编程式事务管理,尽管在灵活性方面它弱于编程式事务管理,因为编程式事务允许你通过代码控制业务。事务分为全局事务和局部事务。全局事务由应用服务器管理,需要底层服务器JTA支持(如等)。局部事务和底层采用的持久化方案有关,例如使用JDBC进行持久化时,需要使用对象来操作事务;而采用进行持久化时,需要使用对象来操作事务。提供了如下所示的事务管理器。这些事务的父接口都是。的事务管理机制是一种典型的策略模式,代表事务管理接口,该接口定义了三个方法,该接口并不知道底层如何管理事务,但是它的实现类必须提供()方法(开启事务)、()方法(提交事务)、()方法(回滚事务)的多态实现,这样就可以用不同的实现类代表不同的事务管理策略。使用JTA全局事务策略时,需要底层应用服务器支持,而不同的应用服务器所提供的JTA全局事务可能存在细节上的差异,因此实际配置全局事务管理器是可能需要使用的子类,如(的服务器提供)、(IBM的服务器提供)等。编程式事务管理如下所示。
?xml ="1.0" ="UTF-8"?  =" of //":xsi="":p="":p=" of //"xsi=" of // of // "-scan base-="com."/bean id=""="org....." name=""./// id="" ="org...dbcp." name=""${db.}// name="url"${db.url}// name=""${db.}// name=""${db.}/// id="" ="org..jdbc.core." name=""ref bean="" ///bean!-- JDBC事务管理器 --bean id=""="org..jdbc.." ="" name=""ref bean="" ///bean!-- 声明事务模板 --bean id=""="org...." name=""ref bean="" ///bean/

 com..dao.impl; org.....; org..jdbc.core.; com..dao.; com...Emp;@     {@  ;@  save(Emp emp) { sql = " into emp  (?,?,?)"; .(sql, emp.(), emp.(), emp.()) == 1;}}

 com..biz.impl; org.....; org...; org...; org....; org....; com..biz.; com..dao.; com...Emp;@     {@  ;@  ;@ void ( Emp emp) {.(new () {@ void ( ) {.save(emp);}});}}
声明式事务如下图所示,以整合 3为例,包括完整的DAO和业务逻辑代码。
?xml ="1.0" ="UTF-8"? =" of //":xsi="":p=""=" of //":aop=" of //aop":tx=" of //tx"xsi=" of // of // of // of //tx"!-- 配置由 IoC容器托管的对象对应的被注解的类所在的包 ---scan base-="com." /!-- 配置通过自动生成代理实现AOP功能 --aop- /!-- 配置数据库连接池 (DBCP) --bean id="" ="org...dbcp."-=""!-- 配置驱动程序类 -- name="" ="com..jdbc." /!-- 配置连接数据库的URL -- name="url" ="jdbc://:3306/" /!-- 配置访问数据库的用户名 -- name="" ="root" /!-- 配置访问数据库的口令 -- name="" ="" /!-- 配置最大连接数 -- name="" ="150" /!-- 配置最小空闲连接数 -- name="" ="5" /!-- 配置最大空闲连接数 -- name="" ="20" /!-- 配置初始连接数 -- name="" ="10" /!-- 配置连接被泄露时是否生成日志 -- name="" ="true" /!-- 配置是否删除超时连接 -- name="" ="true" /!-- 配置删除超时连接的超时门限值(以秒为单位) -- name="" ="120" /!-- 配置超时等待时间(以毫秒为单位) -- name="" ="5000" /!-- 配置空闲连接回收器线程运行的时间间隔(以毫秒为单位) -- name="" ="" /!-- 配置连接空闲多长时间后(以毫秒为单位)被断开连接 -- name="" ="" //bean!-- 配置提供的支持注解ORM映射的会话工厂 --bean id=""="org..orm..."!-- 通过注入数据源属性 -- name="" ref="" /!-- 配置实体类所在的包 -- name="" ="com.." /!-- 配置的相关属性 -- name=""!-- 在项目调试完成后要删除和属性否则对性能有显著影响 --.=org...///bean!-- 配置提供的事务管理器 --bean id=""="org..orm.."!-- 通过注入会话工厂 -- name="" ref="" //bean!-- 配置基于注解配置声明式事务 --tx- //

 com..dao; java.io.; java.util.List; com..comm.; com..comm.;/** * 数据访问对象接口(以对象为单位封装CRUD操作) * @ 骆昊 * * @ E 实体类型 * @ K 实体标识字段的类型 */   E, K   {/*** 新增* @  业务实体对象* @ 增加成功返回实体对象的标识*/ K save(E );/*** 删除* @  业务实体对象*/ void (E );/*** 根据ID删除* @ id 业务实体对象的标识* @ 删除成功返回true否则返回*/  (K id);/*** 修改* @  业务实体对象* @ 修改成功返回true否则返回*/ void (E );/*** 根据ID查找业务实体对象* @ id 业务实体对象的标识* @ 业务实体对象对象或null*/ E (K id);/*** 根据ID查找业务实体对象* @ id 业务实体对象的标识* @ lazy 是否使用延迟加载* @ 业务实体对象对象*/ E (K id,  lazy);/*** 查找所有业务实体对象* @ 装所有业务实体对象的列表容器*/  ();/*** 分页查找业务实体对象* @ page 页码* @ size 页面大小* @ 查询结果对象*/  (int page, int size);/*** 分页查找业务实体对象* @  查询条件对象* @ page 页码* @ size 页面大小* @ 查询结果对象*/  ( , int page, int size);}

 com..dao; java.io.; java.util.List; com..comm.; com..comm.;/** * 的缺省适配器 * @ 骆昊 * * @ E 实体类型 * @ K 实体标识字段的类型 */   , K   , K {@ K save(E ) { null;}@ void (E ) {}@  (K id) {E  = (id);if( != null) {(); true;} ;}@ void (E ) {}@ E (K id) { null;}@ E (K id,  lazy) { null;}@  () { null;}@  (int page, int size) { null;}@  ( , int page, int size) { null;}}

 com..dao; java.io.; java.lang..; java.util.; java.util.; java.util.List; org..; org..; org..; org.....; com..comm.; com..comm.; com..comm.;/** * 基于的实现类 * @ 骆昊 * * @ E 实体类型 * @ K 主键类型 */@( = {""})   , K    , K {@  ; ? ;// 业务实体的类对象  ;// 业务实体的名字 () { pt = () this.().(); = (?) pt.()[0]; = .();}@ K save(E ) { (K) .().save();}@ void (E ) {.().();}@ void (E ) {.().();}@ E (K id) { (id, );}@ E (K id,  lazy) {  = .(); (E) (lazy? .load(, id) : .get(, id));}@  () { .().().list();}@  (int page, int size) { new (("from " +  , page, size),(" (*) from " + ));}@  ( , int page, int size) {if(  ) {  = () ; new ((.(), page, size, .()),(.(), .()));} null;}/*** 根据HQL和可变参数列表进行查询* @ hql 基于HQL的查询语句* @  可变参数列表* @ 持有查询结果的列表容器或空列表容器*/  ( hql, ... ) { this.(hql, ());}/*** 根据HQL和参数列表进行查询* @ hql 基于HQL的查询语句* @  查询参数列表* @ 持有查询结果的列表容器或空列表容器*/  ( hql,  ) { list = (hql, ).list(); list != .size()0 ? list : .;}/*** 根据HQL和参数列表进行分页查询* @ hql 基于HQL的查询语句* @page 页码* @size 页面大小* @  可变参数列表* @ 持有查询结果的列表容器或空列表容器*/  ( hql, int page, int size, ... ) { this.(hql, page, size, ());}/*** 根据HQL和参数列表进行分页查询* @ hql 基于HQL的查询语句* @page 页码* @size 页面大小* @  查询参数列表* @ 持有查询结果的列表容器或空列表容器*/  ( hql, int page, int size,  ) { list = (hql, ).((page - 1) * size).(size).list(); list != .size()0 ? list : .;}/*** 查询满足条件的记录数* @ hql 基于HQL的查询语句* @  可变参数列表* @ 满足查询条件的总记录数*/ long ( hql, ... ) { this.(hql, ());}/*** 查询满足条件的记录数* @ hql 基于HQL的查询语句* @  参数列表容器* @ 满足查询条件的总记录数*/ long ( hql,  ) { (Long) (hql, ).();}// 创建查询对象()  ( hql,  ) {  = .().(hql);for(int i = 0; .size(); i++) {.(i, .get(i));} ;}// 将可变参数列表组装成列表容器  (... ) {  = new ();if( != null) {for(int i = 0; .; i++) {.add([i]);}} .size() == 0? . : ;}}

 com..comm; java.util.List;/** * 查询条件的接口 * @ 骆昊 * */   {/*** 添加排序字段* @  用于排序的字段* @ asc 升序还是降序* @ 查询条件对象自身(方便级联编程)*/  ( ,  asc);/*** 添加排序字段* @  是否添加此排序字段* @  用于排序的字段* @ asc 升序还是降序* @ 查询条件对象自身(方便级联编程)*/  ( ,  ,  asc);/*** 添加查询条件* @  条件* @  替换掉条件中参数占位符的参数* @ 查询条件对象自身(方便级联编程)*/  ( , ... );/*** 添加查询条件* @  是否需要添加此条件* @  条件* @  替换掉条件中参数占位符的参数* @ 查询条件对象自身(方便级联编程)*/  ( ,  , ... );/*** 获得查询语句* @ 查询语句*/  ();/*** 获取查询记录数的查询语句* @ 查询记录数的查询语句*/  ();/*** 获得查询参数* @ 查询参数的列表容器*/  ();}

 com..comm; java.util.List;/** * 查询结果 * @ 骆昊 * * @ T 泛型参数 */   {  ;// 持有查询结果的列表容器 long ;// 查询到的总记录数/*** 构造器*/ () {}/*** 构造器* @  持有查询结果的列表容器* @  查询到的总记录数*/ ( , long ) {this. = ;this. = ;}  () { ;} void ( ) {this. = ;} long () { ;} void (long ) {this. = ;}}

 com..dao; com..comm.; com...Dept;/** * 部门数据访问对象接口 * @ 骆昊 * */    ,  {/*** 分页查询顶级部门* @ page 页码* @ size 页码大小* @ 查询结果对象*/  (int page, int size);}

 com..dao.impl; java.util.List; org...; com..comm.; com..dao.; com..dao.; com...Dept;@    ,    {     = " from Dept as d  d. is null ";@  (int page, int size) { list = (, page, size);long  = ("  (*) " + ); new (list, );}}

 com..comm; java.util.List;/** * 分页器 * @ 骆昊 * * @ T 分页数据对象的类型 */   {   int  = 1;   int  = 10;   int  = 5;  data;// 分页数据  ;// 页码范围 int ;// 总页数 int size;// 页面大小 int ;// 当前页码 int ;// 页码数量/*** 构造器* @  当前页码* @ size 页码大小* @  页码数量*/ (int , int size, int ) {this. =  ?  : 1;this.size =  ? size : ;this. =  ? size : ;}/*** 构造器* @  当前页码* @ size 页码大小*/ (int , int size) {this(, size, );}/*** 构造器* @  当前页码*/ (int ) {this(, , );}/*** 构造器*/ () {this(, , );}  () { data;} int () {  != null ? .() : 1;} int () {  != null ? .() : 1;} long () { ;} int () { size;} int () { ;}/*** 将查询结果转换为分页数据* @  查询结果对象*/ void ( ) {long  = .();data = .(); = (int) (( + size - 1) / size); =  = 0 ?  : .;this. = new (, , );}}

 com..comm;/** * 页码范围 * @ 骆昊 * */   { int ;// 起始页码 int ;// 终止页码/*** 构造器* @  总共显示几个页码* @  当前页码* @  总页数*/ (int , int , int ) { =  - ( - 1) / 2; =  +  / 2;if() { = 1; =  ?  : ;}if () { = ; = ( - ) ?  -  + 1 : 1;}}/*** 获得起始页页码* @ 起始页页码*/ int () { ;}/*** 获得终止页页码* @ 终止页页码*/ int () { ;}}

 com..biz; com..comm.; com...Dept;/** * 部门业务逻辑接口 * @ 骆昊 * */   {/*** 创建新的部门* @  部门对象* @ 创建成功返回true否则返回*/  (Dept );/*** 删除指定部门* @ id 要删除的部门的编号* @ 删除成功返回true否则返回*/  ( id);/*** 分页获取顶级部门* @ page 页码* @ size 页码大小* @ 部门对象的分页器对象*/  (int page, int size);}

 com..biz.impl; org.....; org...; org....; com..biz.; com..comm.; com..comm.; com..dao.; com...Dept;@@// 声明式事务的注解     {@  ;@  (Dept ) { .save() != null;}@  ( id) { .(id);}@  (int page, int size) {  = .(page, size);  = new (page, size);.(); ;}}
154、如何在Web项目中配置的IoC容器? 答:如果需要在Web项目中使用的IoC容器,可以在Web项目配置文件web.xml中做出如下配置:
--/--.xml/-/--..web../-/
155、如何在Web项目中配置 MVC? 答:要使用 MVC需要在Web项目配置文件中配置其前端控制器,如下所示:
web--/--..web../--on-/load-on-/--/--*.html/url-/-/web-app
说明:上面的配置中使用了*.html的后缀映射,这样做一方面不能够通过URL推断采用了何种服务器端的技术,另一方面可以欺骗搜索引擎,因为搜索引擎不会搜索动态页面,这种做法称为伪静态化。156、 MVC的工作原理是怎样的?答 MVC的工作原理如下图所示:① 客户端的所有请求都交给前端控制器来处理,它会负责调用系统的其他模块来真正处理用户的请求。 ② 收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、等)以及的配置找到处理该请求的(任何一个对象都可以作为请求的)。 ③在这个地方会通过对该处理器进行封装。 ④ 是一个适配器,它用统一的接口对各种中的方法进行调用。 ⑤ 完成对用户请求的处理后,会返回一个对象给,顾名思义,包含了数据模型以及相应的视图的信息。 ⑥ 的视图是逻辑视图,还要借助完成从逻辑视图到真实视图对象的解析工作。 ⑦ 当得到真正的视图对象后,会利用视图对象对模型数据进行渲染。 ⑧ 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。157、如何在 IoC容器中配置数据源? 答: DBCP配置:
bean id=""="org...dbcp." -="" name="" ="${jdbc.}"/ name="url" ="${jdbc.url}"/ name="" ="${jdbc.}"/ name="" ="${jdbc.}"//- ="jdbc."/
C3P0配置:
bean id=""="com..v2.c3p0." -="" name="" ="${jdbc.}"/ name="" ="${jdbc.url}"/ name="user" ="${jdbc.}"/ name="" ="${jdbc.}"//- ="jdbc."/
提示: DBCP的详细配置在第153题中已经完整的展示过了。158、如何配置配置事务增强? 答:
?xml ="1.0" ="UTF-8"? =" of //":xsi="":aop=" of //aop":tx=" of //tx"xsi=" of // of // of //aop"!-- this is the   that we want to make  --bean id="" ="x.y.."/!-- the   --tx id="" -=""!-- the  ... --tx!-- all   with 'get' are read-only --tx name="get*" read-only="true"/!--   use the    (see ) --tx name="*"//tx/tx!--  that the    runs for any  an   by the   --aop id=""="(* x.y...*(..))"/aop -ref="" -ref=""//aop!-- don't  the  --bean id="" ="org...dbcp."-="" name="" =".jdbc.."/ name="url" ="jdbc:thin:@:1521:orcl"/ name="" =""/ name="" =""//bean!-- , don't  the  --bean id="" ="org..jdbc.." name="" ref=""//bean!--  bean/  here --/
159、选择使用框架的原因(框架为企业级开发带来的好处有哪些)? 答:可以从以下几个方面作答: - 非侵入式:支持基于POJO的编程模式,不强制性的要求实现框架中的接口或继承框架中的类。 - IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码,因为代码的修改可能意味着项目的重新构建和完整的回归测试。有了IoC容器,程序员再也不需要自己编写工厂、单例,这一点特别符合的精神"不要重复的发明轮子"。 - AOP(面向切面编程):将所有的横切关注功能封装到切面()中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。另一方面,有了AOP程序员可以省去很多自己写代理类的工作。 - MVC的MVC框架是非常优秀的,从各个方面都可以甩 2几条街,为Web表示层提供了更好的解决方案。 - 事务管理以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。 - 其他:选择框架的原因还远不止于此,为Java企业级开发提供了一站式选择,你可以在需要的时候使用它的部分和全部,更重要的是,你甚至可以在感觉不到存在的情况下,在你的项目中使用提供的各种优秀的功能。160、 IoC容器配置Bean的方式? 答: - 基于XML文件进行配置。 - 基于注解进行配置。 - 基于Java程序进行配置( 3+)
 com..bean; org.....; org...;@   {  name; int age;@ Car car; ( name, int age) {this.name = name;this.age = age;} void (Car car) {this.car = car;}@  () { " [name=" + name + ", age=" + age + ", car=" + car + "]";}}

 com..bean; org...;@  Car {  ; int ; Car( , int ) {this. = ;this. = ;}@  () { "Car [=" +  + ", =" +  + "]";}}

 com..; org....Bean; org....; com..bean.Car; com..bean.;@   {@ Car car() { new Car("Benz", 320);}@  () { new ("骆昊", 34);}}

 com..test; org...; org....; com..bean.; com...; Test {  void main([] args) {// TWR (Java 7+)try(  = new (.)) {  = .(.);.out.();}}}
161、阐述框架中Bean的生命周期? 答: ① IoC容器找到关于Bean的定义并实例化该Bean。 ② IoC容器对Bean进行依赖注入。 ③ 如果Bean实现了接口,则将该Bean的id传给方法。 ④ 如果Bean实现了接口,则将对象传给方法。 ⑤ 如果Bean实现了接口,则调用其方法。 ⑥ 如果Bean实现了接口,则调用其方法。 ⑦ 如果有和Bean关联的对象,则这些对象的方法被调用。 ⑧ 当销毁Bean实例时,如果Bean实现了接口,则调用其方法。162、依赖注入时如何注入集合属性? 答:可以在定义Bean属性时,通过list / set / map / 分别为其注入列表、集合、映射和键值都是字符串的映射属性。163、中的自动装配有哪些限制? 答:如果使用了构造器注入或者注入,那么将覆盖自动装配的依赖关系。 基本数据类型的值、字符串字面量、类字面量无法使用自动装配来注入。 优先考虑使用显式的装配来进行更精确的依赖注入而不是使用自动装配。164、在Web项目中如何获得的IoC容器? 答:
 ctx = .();
165. 大型网站在架构上应当考虑哪些问题? 答: - 分层:分层是处理任何复杂系统最常见的手段之一,将系统横向切分成若干个层面,每个层面只承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形成一个完整的复杂的系统。计算机网络的开放系统互联参考模型(OSI/RM)和的TCP/IP模型都是分层结构,大型网站的软件系统也可以使用分层的理念将其分为持久层(提供数据存储和访问服务)、业务层(处理业务逻辑,系统中最核心的部分)和表示层(系统交互、视图展示)。需要指出的是:(1)分层是逻辑上的划分,在物理上可以位于同一设备上也可以在不同的设备上部署不同的功能模块,这样可以使用更多的计算资源来应对用户的并发访问;(2)层与层之间应当有清晰的边界,这样分层才有意义,才更利于软件的开发和维护。 - 分割:分割是对软件的纵向切分。我们可以将大型网站的不同功能和服务分割开,形成高内聚低耦合的功能模块(单元)。在设计初期可以做一个粗粒度的分割,将网站分割为若干个功能模块,后期还可以进一步对每个模块进行细粒度的分割,这样一方面有助于软件的开发和维护,另一方面有助于分布式的部署,提供网站的并发处理能力和功能的扩展。 - 分布式:除了上面提到的内容,网站的静态资源(CSS、图片等)也可以采用独立分布式部署并采用独立的域名,这样可以减轻应用服务器的负载压力,也使得浏览器对资源的加载更快。数据的存取也应该是分布式的,传统的商业级关系型数据库产品基本上都支持分布式部署,而新生的产品几乎都是分布式的。当然,网站后台的业务处理也要使用分布式技术,例如查询索引的构建、数据分析等,这些业务计算规模庞大,可以使用以及分布式计算框架来处理。 - 集群:集群使得有更多的服务器提供相同的服务,可以更好的提供对并发的支持。 - 缓存:所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算最近的位置。使用缓存是网站优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。 - 异步:异步是实现软件实体之间解耦合的又一重要手段。异步架构是典型的生产者消费者模式,二者之间没有直接的调用关系,只要保持数据结构不变,彼此功能实现可以随意变化而不互相影响,这对网站的扩展非常有利。使用异步处理还可以提高系统可用性,加快网站的响应速度(用Ajax加载数据就是一种异步技术),同时还可以起到削峰作用(应对瞬时高并发)。quot;能推迟处理的都要推迟处理"是网站优化的第二定律,而异步是践行网站优化第二定律的重要手段。 - 冗余:各种服务器都要提供相应的冗余服务器以便在某台或某些服务器宕机时还能保证网站可以正常工作,同时也提供了灾难恢复的可能性。冗余是网站高可用性的重要保证。166、你用过的网站前端优化的技术有哪些? 答: ① 浏览器访问优化: - 减少HTTP请求数量:合并CSS、合并合并图片(CSS ) - 使用浏览器缓存:通过设置HTTP响应头中的-和属性,将CSS、图片等在浏览器中缓存,当这些静态资源需要更新时,可以更新HTML文件中的引用来让浏览器重新请求新的资源 - 启用压缩 - CSS前置,后置 - 减少传输 ② CDN加速:CDN( )的本质仍然是缓存,将数据缓存在离用户最近的地方,CDN通常部署在网络运营商的机房,不仅可以提升响应速度,还可以减少应用服务器的压力。当然,CDN缓存的通常都是静态资源。 ③ 反向代理:反向代理相当于应用服务器的一个门面,可以保护网站的安全性,也可以实现负载均衡的功能,当然最重要的是它缓存了用户访问的热点资源,可以直接从反向代理将某些内容返回给用户浏览器。167、你使用过的应用服务器优化技术有哪些? 答: ① 分布式缓存:缓存的本质就是内存中的哈希表,如果设计一个优质的哈希函数,那么理论上哈希表读写的渐近时间复杂度为O(1)。缓存主要用来存放那些读写比很高、变化很少的数据,这样应用程序读取数据时先到缓存中读取,如果没有或者数据已经失效再去访问数据库或文件系统,并根据拟定的规则将数据写入缓存。对网站数据的访问也符合二八定律(分布,幂律分布),即80%的访问都集中在20%的数据上,如果能够将这20%的数据缓存起来,那么系统的性能将得到显著的改善。当然,使用缓存需要解决以下几个问题: 频繁修改的数据; 数据不一致与脏读; 缓存雪崩(可以采用分布式缓存服务器集群加以解决,是广泛采用的解决方案); 缓存预热; 缓存穿透(恶意持续请求不存在的数据)。 ② 异步操作:可以使用消息队列将调用异步化,通过异步处理将短时间高并发产生的事件消息存储在消息队列中,从而起到削峰作用。电商网站在进行促销活动时,可以将用户的订单请求存入消息队列,这样可以抵御大量的并发订单请求对系统和数据库的冲击。目前,绝大多数的电商网站即便不进行促销活动,订单系统都采用了消息队列来处理。 ③ 使用集群。 ④ 代码优化: - 多线程:基于Java的Web开发基本上都通过多线程的方式响应用户的并发请求,使用多线程技术在编程上要解决线程安全问题,主要可以考虑以下几个方面:A. 将对象设计为无状态对象(这和面向对象的编程观点是矛盾的,在面向对象的世界中被视为不良设计),这样就不会存在并发访问时对象状态不一致的问题。B. 在方法内部创建对象,这样对象由进入方法的线程创建,不会出现多个线程访问同一对象的问题。使用将对象与线程绑定也是很好的做法,这一点在前面已经探讨过了。C. 对资源进行并发访问时应当使用合理的锁机制。 - 非阻塞I/O: 使用单线程和非阻塞I/O是目前公认的比多线程的方式更能充分发挥服务器性能的应用模式,基于Node.js构建的服务器就采用了这样的方式。Java在JDK 1.4中就引入了NIO(Non- I/O),在 3规范中又引入了异步的概念,这些都为在服务器端采用非阻塞I/O提供了必要的基础。 - 资源复用:资源复用主要有两种方式,一是单例,二是对象池,我们使用的数据库连接池、线程池都是对象池化技术,这是典型的用空间换取时间的策略,另一方面也实现对资源的复用,从而避免了不必要的创建和释放资源所带来的开销。168、什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击? 答: - XSS( Site ,跨站脚本攻击)是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式。跨站脚本攻击分有两种形式:反射型攻击(诱使用户点击一个嵌入恶意脚本的链接以达到攻击的目标,目前有很多攻击者利用论坛、微博发布含有恶意脚本的URL就属于这种方式)和持久型攻击(将恶意脚本提交到被攻击网站的数据库中,用户浏览网页时,恶意脚本从数据库中被加载到页面执行,QQ邮箱的早期版本就曾经被利用作为持久型跨站脚本攻击的平台)。XSS虽然不是什么新鲜玩意,但是攻击的手法却不断翻新,防范XSS主要有两方面:消毒(对危险字符进行转义)和(防范XSS攻击者窃取数据)。 - SQL注入攻击是注入攻击最常见的形式(此外还有OS注入攻击( 2的高危漏洞就是通过OGNL实施OS注入攻击导致的)),当服务器使用请求参数构造SQL语句时,恶意的SQL被嵌入到SQL中交给数据库执行。SQL注入攻击需要攻击者对数据库结构有所了解才能进行,攻击者想要获得表结构有多种方式:(1)如果使用开源系统搭建网站,数据库结构也是公开的(目前有很多现成的系统可以直接搭建论坛,电商网站,虽然方便快捷但是风险是必须要认真评估的);(2)错误回显(如果将服务器的错误信息直接显示在页面上,攻击者可以通过非法参数引发页面错误从而通过错误信息了解数据库结构,Web应用应当设置友好的错误页,一方面符合最小惊讶原则,一方面屏蔽掉可能给系统带来危险的错误回显信息);(3)盲注。防范SQL注入攻击也可以采用消毒的方式,通过正则表达式对请求参数进行验证,此外,参数绑定也是很好的手段,这样恶意的SQL会被当做SQL的参数而不是命令被执行,JDBC中的就是支持参数绑定的语句对象,从性能和安全性上都明显优于。 - CSRF攻击( Site ,跨站请求伪造)是攻击者通过跨站请求,以合法的用户身份进行非法操作(如转账或发帖等)。CSRF的原理是利用浏览器的或服务器的,盗取用户身份,其原理如下图所示。防范CSRF的主要手段是识别请求者的身份,主要有以下几种方式:(1)在表单中添加令牌();(2)验证码;(3)检查请求头中的(前面提到防图片盗链接也是用的这种方式)。令牌和验证都具有一次消费性的特征,因此在原理上一致的,但是验证码是一种糟糕的用户体验,不是必要的情况下不要轻易使用验证码,目前很多网站的做法是如果在短时间内多次提交一个表单未获得成功后才要求提供验证码,这样会获得较好的用户体验。补充:防火墙的架设是Web安全的重要保障,是开源的Web防火墙中的佼佼者。企业级防火墙的架设应当有两级防火墙,Web服务器和部分应用服务器可以架设在两级防火墙之间的DMZ,而数据和资源服务器应当架设在第二级防火墙之后。169. 什么是领域模型( )?贫血模型( )和充血模型(rich )有什么区别? 答:领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。贫血模型是指使用的领域对象中只有和方法(POJO),所有的业务逻辑都不包含在领域对象中而是放在业务逻辑层。有人将我们这里说的贫血模型进一步划分成失血模型(领域对象完全没有业务逻辑)和贫血模型(领域对象有少量的业务逻辑),我们这里就不对此加以区分了。充血模型将大多数业务逻辑和持久化放在领域对象中,业务逻辑(业务门面)只是完成对业务逻辑的封装、事务和权限等的处理。下面两张图分别展示了贫血模型和充血模型的分层架构。贫血模型 充血模型贫血模型下组织领域逻辑通常使用事务脚本模式,让每个过程对应用户可能要做的一个动作,每个动作由一个过程来驱动。也就是说在设计业务逻辑接口的时候,每个方法对应着用户的一个操作,这种模式有以下几个有点: - 它是一个大多数开发者都能够理解的简单过程模型(适合国内的绝大多数开发者)。 - 它能够与一个使用行数据入口或表数据入口的简单数据访问层很好的协作。 - 事务边界的显而易见,一个事务开始于脚本的开始,终止于脚本的结束,很容易通过代理(或切面)实现声明式事务。 然而,事务脚本模式的缺点也是很多的,随着领域逻辑复杂性的增加,系统的复杂性将迅速增加,程序结构将变得极度混乱。开源中国社区上有一篇很好的译文《贫血领域模型是如何导致糟糕的软件产生》对这个问题做了比较细致的阐述。170. 谈一谈测试驱动开发(TDD)的好处以及你的理解。 答:TDD是指在编写真正的功能实现代码之前先写测试代码,然后根据需要重构实现代码。在的作者Kent Beck的大作《测试驱动开发:实战与模式解析》(Test- : by )一书中有这么一段内容:“消除恐惧和不确定性是编写测试驱动代码的重要原因”。因为编写代码时的恐惧会让你小心试探,让你回避沟通,让你羞于得到反馈,让你变得焦躁不安,而TDD是消除恐惧、让Java开发者更加自信更加乐于沟通的重要手段。TDD会带来的好处可能不会马上呈现,但是你在某个时候一定会发现,这些好处包括: - 更清晰的代码 — 只写需要的代码 - 更好的设计 - 更出色的灵活性 — 鼓励程序员面向接口编程 - 更快速的反馈 — 不会到系统上线时才知道bug的存在补充:敏捷软件开发的概念已经有很多年了,而且也部分的改变了软件开发这个行业,TDD也是敏捷开发所倡导的。TDD可以在多个层级上应用,包括单元测试(测试一个类中的代码)、集成测试(测试类之间的交互)、系统测试(测试运行的系统)和系统集成测试(测试运行的系统包括使用的第三方组件)。TDD的实施步骤是:红(失败测试)- 绿(通过测试) - 重构。关于实施TDD的详细步骤请参考另一篇文章《测试驱动开发之初窥门径》。 在使用TDD开发时,经常会遇到需要被测对象需要依赖其他子系统的情况,但是你希望将测试代码跟依赖项隔离,以保证测试代码仅仅针对当前被测对象或方法展开,这时候你需要的是测试替身。测试替身可以分为四类: - 虚设替身:只传递但是不会使用到的对象,一般用于填充方法的参数列表 - 存根替身:总是返回相同的预设响应,其中可能包括一些虚设状态 - 伪装替身:可以取代真实版本的可用版本(比真实版本还是会差很多) - 模拟替身:可以表示一系列期望值的对象,并且可以提供预设响应 Java世界中实现模拟替身的第三方工具非常多,包括等。版权声明:本文为博主骆昊原创文章,遵循版权协议,转载请联系作者并附上原文出处链接。