框架面试题


MVC 模式基础 在 MVC 模式中,一个应用被划分成了模型(Model)、视图(View)和控制器(Controller) 三个部分。 模型、视图、控制器各部分的作用  模型(Model):负责封装应用的状态,并实现应用的功能。通常分为数据模型和业务 逻辑模型,数据模型用来存放业务数据,比如订单信息、用户信息等;而业务逻辑模型 包含应用的业务操作,比如订单的添加或者修改等。  视图(View):用来将模型的内容展现给用户,用户可以通过视图来请求模型进行更新。 视图从模型获得要展示的数据,然后用自己的方式展现给用户,相当于提供界面来与用 户进行人机交互;用户在界面上操作或者填写完成后,会点击提交按钮或是以其它触发 事件的方式,来向控制器发出请求。  控制器(Controller):用来控制应用程序的流程和处理视图所发出的请求。当控制器接 收到用户的请求后,会将用户的数据和模型的更新相映射,也就是调用模型来实现用户 请求的功能;然后控制器会选择用于响应的视图,把模型更新后的数据展示给用户。 模型和视图的关系 在 MVC 中,模型和视图是分离的,通常视图里面不会有任何逻辑实现;而模型也是不 依赖于视图的,同一个模型可能会有多种不同的展示方式,即同一个模型可以对应多种不同 的视图。例如,在 windows 操作系统上浏览文件夹时,文件夹就那些,数据并没有变化,但 是展示方式就有多种,比如大图标、小图标、详细信息等等展示方式。以 tomcat7 文件夹下 的文件为例,按照不同的展示方式,模型负责输出的内容,而视图负责输出的形式,模型不 依赖于视图,模型与视图是解耦的。因此在修改视图时候,不必关心模型,而只需要直接修 改视图的展示方式即可。 MVC 的组件关系图 MVC 的组件关系图描述了模型、视图、控制器的交互关系: 1. 首先是展示视图给用户,用户在视图上进行操作,并填写一些业务数据 2. 然后用户点击提交按钮发出请求 3. 视图发出的用户请求会到达控制器,请求中包含了想要完成什么样的业务功能以及相 关的数据。 4. 控制器会处理用户请求,把请求中的数据进行封装,然后选择并调用合适的模型,请求 模型进行状态更新,然后选择接下来要展示给用户的视图。 5. 模型处理用户请求的业务功能,同时进行模型状态的维护和更新 6. 当模型状态发生改变的时候,模型会通知相应的视图,告诉视图它的状态发生了改变。 7. 视图接到模型的通知后,会向模型进行状态查询,获取需要展示的数据,然后按照视图 本身的展示方式,把这些数据展示出来。 MVC 模式作用 MVC 模式的核心手段是解耦,MVC 模式通过仔细的划分功能,把整个应用程序划分成模型、 视图、控制器三个部分,然后严密控制三个部分之间的通信,从而得到一个结构清晰、功能 分布合理、可重用、可扩展、可维护的应用程序。 因此,使用 MVC 模式,可以获得以下好处:  低耦合性:在 MVC 模式中,模型和视图是解耦的,模型不会依赖于视图,视图仅仅从 模型中获取需要展示的数据,不会与模型的逻辑处理相关联。  更低的开发成本:MVC 模式帮我们清楚的划分了各部分的职责,让程序员各司其职, Java 程序员只关心业务逻辑的实现,也就是模型部分;而界面程序员只关心页面展示, 也就是视图部分。  更好的可维护性:MVC 模式划分出明晰的模型和视图,并使其解耦,在软件需求发生 变更的时候,就可以各自独立的改变而不会相互影响,使得程序更容易维护和扩展。 Struts2 Struts2 是基于 MVC 的轻量级的 web 应用框架。  所谓框架:就是能完成一定功能的半成品软件。在没有框架的时候,所有的工作都要乖 乖的从零做起;有了框架,它为我们提供了一定的功能,就可以在框架的基础上做起, 大大提高开发的效率和质量。  web 应用框架,这说明 Struts2 的应用范围是 Web 应用而不是其它地方。Struts2 更注 重将 Web 应用领域的日常工作和常见问题抽象化,提供一个平台让我们能快速的完成 Web 应用开发。  轻量级:是相对于重量级而言,指的是 Struts2 在运行的时候,对 Web 服务器的资源消 耗较少,比如 CPU、内存等,但是运行速度相对较快。  基于 MVC,说明基于 Struts2 开发的 Web 应用自然就能实现 MVC,也说明 Struts2 着力于在 MVC 的各个部分为我们的开发提供相应帮助。 Struts2 能干什么  Struts2 通过简单、集中的配置来调度动作类,使得我们配置和修改都非常容易。  Struts2 提供简单、统一的表达式语言来访问所有可供访问的数据。  Struts2 提供内存式的数据中心,所有可供访问的数据都集中存放在内存中,在调用中 不需要将数据传来传去,都去这个内存数据中心访问即可。  Struts2 提供在动作类执行的前或后附加执行一定功能的能力,能实现 AOP。  Struts2 提供标准的、强大的验证框架和国际化框架,且与 Struts2 的其他特性紧密结 合。 Struts2 和 MVC Struts2 是一种基于 MVC 的 Web 应用框架,下面看看 Struts2 和 MVC 的关系。这里只是 先讲一下 Struts2 是如何跟 MVC 对应的,其中一些名词所代表的具体功能,比如前端控制 器(FilterDispatcher)、动作(Action)、结果(Result)等。在之后的学习中会不断深入具 体的细节。 控制器——FilterDispatcher 用户请求首先到达前端控制器 FilterDispatcher。FilterDispatcher 负责根据用户提交的 URL 和 struts.xml 中的配置,来选择合适的动作(Action),让这个 Action 来处理用户的请 求。FilterDispatcher 其实是一个过滤器(Filter,servlet 规范中的一种 web 组件),它是 Struts2 核心包里已经做好的类,不需要我们去开发,只是要在项目的 web.xml 中配置一下 即可。FilterDispatcher 体现了 J2EE 核心设计模式中的前端控制器模式。 动作——Action 在用户请求经过 FilterDispatcher 之后,被分发到了合适的动作 Action 对象。Action 负责 把用户请求中的参数组装成合适的数据模型,并调用相应的业务逻辑进行真正的功能处理, 获取下一个视图展示所需要的数据。Struts2 的 Action,相比于别的 web 框架的动作处理, 它 实 现 了 与 Servlet API 的 解 耦 , 使 得 Action 里面不需要再直接去引用和使用 HttpServletRequest 与 HttpServletResponse 等接口。 因而使得 Action 的单元测试更加 简单,而且强大的类型转换也使得我们少做了很多重复的工作。 视图——Result 视图结果用来把动作中获取到的数据展现给用户。在 Struts2 中有多种优秀的结果展示方式, 常规的 jsp,模板 freemarker、velocity,还有各种其它专业的展示方式,如图表 jfreechart、 报表 JasperReports、将 XML 转化为 HTML 的 XSLT 等等。而且各种视图结果在同一个 工程里面可以混合出现。 Spring MVC Spring MVC 概述: Spring MVC 是 Spring 提供的一个强大而灵活的 web 框架。借助于注解,Spring MVC 提 供了几乎是 POJO 的开发模式,使得控制器的开发和测试更加简单。这些控制器一般不直 接处理请求,而是将其委托给 Spring 上下文中的其他 bean,通过 Spring 的依赖注入功能, 这些 bean 被注入到控制器中。 Spring MVC 主要由 DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图 组成。他的两个核心是两个核心: 处理器映射:选择使用哪个控制器来处理请求 视图解析器:选择结果应该如何渲染 通过以上两点,Spring MVC 保证了如何选择控制处理请求和如何选择视图展现输出之间的 松耦合。 SpringMVC 运行原理 (1) Http 请求:客户端请求提交到 DispatcherServlet。 (2) 寻找处理器:由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处 理请求的 Controller。 (3) 调用处理器:DispatcherServlet 将请求提交到 Controller。 (4)(5)调用业务处理和返回结果:Controller 调用业务逻辑处理后,返回 ModelAndView。 (6)(7)处理视图映射并返回模型: DispatcherServlet 查询一个或多个 ViewResoler 视图解 析器,找到 ModelAndView 指定的视图。 (8) Http 响应:视图负责将结果显示到客户端。 SpringMVC 接口解释 (1)DispatcherServlet 接口: Spring 提供的前端控制器,所有的请求都有经过它来统一分发。在 DispatcherServlet 将请 求分发给 Spring Controller 之前,需要借助于 Spring 提供的 HandlerMapping 定位到具体 的 Controller。 (2)HandlerMapping 接口: 能够完成客户请求到 Controller 映射。 (3)Controller 接口: 需要为并发用户处理上述请求,因此实现 Controller 接口时,必须保证线程安全并且可重用。 Controller 将处理用户请求,这和 Struts Action 扮演的角色是一致的。一旦 Controller 处理 完用户请求,则返回 ModelAndView 对 象 给 DispatcherServlet 前端控制器, ModelAndView 中包含了模型(Model)和视图(View)。 从宏观角度考虑,DispatcherServlet 是整个 Web 应用的控制器;从微观考虑,Controller 是单个 Http 请求处理过程中的控制器,而 ModelAndView 是 Http 请求过程中返回的模型 (Model)和视图(View)。 (4)ViewResolver 接口: Spring 提供的视图解析器(ViewResolver)在 Web 应用中查找 View 对象,从而将相应结 果渲染给客户。 DispatcherServlet: 是整个 Spring MVC 的核心。它负责接收 HTTP 请求组织协调 Spring MVC 的各个组成部 分。其主要工作有以下三项: (1)截获符合特定格式的 URL 请求。 (2)初始化 DispatcherServlet 上下文对应 WebApplicationContext,并将其与业务层、持 久化层的 WebApplicationContext 建立关联。 (3)初始化 Spring MVC 的各个组成组件,并装配到 DispatcherServlet 中。 SpringMVC 与 Struts2 区别与比较总结 (1)Struts2 是类级别的拦截, 一个类对应一个 request 上下文,SpringMVC 是方法级别 的拦截,一个方法对应一个 request 上下文,而方法同时又跟一个 url 对应。所以说从架构 本身上 SpringMVC 就容易实现 restful url,而 struts2 的架构实现起来要费劲,因为 Struts2 中 Action 的一个方法可以对应一个 url,而其类属性却被所有方法共享,这也就无法用注解 或其他方式标识其所属方法了。 (2)由上边原因,SpringMVC 的方法之间基本上独立的,独享 request response 数据,请 求数据通过参数获取,处理结果通过 ModelMap 交回给框架,方法之间不共享变量。而 Struts2 虽然方法之间也是独立的,但其所有 Action 变量是共享的,这不会影响程序运行, 却给我们编码 读程序时带来麻烦,每次来了请求就创建一个 Action,一个 Action 对象对应 一个 request 上下文。 (3)由于 Struts2 需要针对每个 request 进行封装,把 request,session 等 servlet 生命周 期的变量封装成一个一个 Map,供给每个 Action 使用,并保证线程安全,所以在原则上,是 比较耗费内存的。 (4)拦截器实现机制上,Struts2 有以自己的 interceptor 机制,SpringMVC 用的是独立的 AOP 方式,这样导致 Struts2 的配置文件量还是比 SpringMVC 大。 (5)SpringMVC 的入口是 servlet,而 Struts2 是 filter(filter 和 servlet 是不同的,以前认 为 filter 是 servlet 的一种特殊)。 (6)SpringMVC 集成了 Ajax,使用非常方便,只需一个注解@ResponseBody 就可以实 现,然后直接返回响应文本即可,而 Struts2 拦截器集成了 Ajax,在 Action 中处理时一般必 须安装插件或者自己写代码集成进去,使用起来也相对不方便。 (7)SpringMVC 验证支持 JSR303,处理起来相对更加灵活方便,而 Struts2 验证比较繁 琐,感觉太烦乱。 (8)Spring MVC 和 Spring 是无缝的。从这个项目的管理和安全上也比 Struts2 高(当然 Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml 配置的地方不少)。 (9)设计思想上,Struts2 更加符合 OOP 的编程思想, SpringMVC 就比较谨慎,在 servlet 上扩展。 (10)SpringMVC 开发效率和性能高于 Struts2。 (11)SpringMVC 可以认为已经 100%零配置。 Spring spring 框架: spring 是 J2EE 应用程序框架,是轻量级的 IoC 和 AOP 的容器框架,主要是针对 javaBean 的生命周期进行管理的轻量级容器,可以单独使用,也可以和 Struts 框架,ibatis 框架等组 合使用。 spring 是做什么的 1. 根据 Spring 最核心的功能 IOC(或者说 DI)--依赖注入,可以看出 Spring 主要是帮助你 管理你的类实例的,也就是说 Spring 是一个容器,容器在启动的时候,会根据你在配置文 件里的配置类装配你的类,以及处理各个类实例之间的依赖关系。对象的生命周期都由 Spring 来管理,给我们最直观的感受是,你不用自己 new 一个对象了,当你需要一个对象 时,直接去 spring 容器中 get 就行了。 2. Spring 还有一个很重要的功能是 AOP--面向切面的编程,这个功能主要是给应用程序提 供特定的服务的,比如:日志服务,事务服务等。有了这个你就可以通过配置来定制服务, 而不用在一开始就在类中写上日志管理,事务处理等代码。这样提高了代码的简洁性以及组 件的可重用性。 3. Spring 为各个框架的整合提供了一个平台,这样就可以通过 Spring 把 Struts,Hibernate 或者 ibatis 等整合到一起。 Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器 之上,核心容器定义了创建、配置和管理 bean 的方式,如图 所示。 组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合 实现。每个模块的功能如下: 1. 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式 将应用程序的配置和依赖性规范与实际的应用程序代码分开。 2. Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信 息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和 调度功能。 3. Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支 持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理 服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集 成到应用程序中。 4. Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来 管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处 理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。 5. Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对 象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 6. Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集 成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 7. Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实 现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术, 其中包括 JSP、Velocity、Tiles、iText 和 POI。 Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。 Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无 疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之 间重用。 IoC 能做什么 IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设 计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖 对象,从而 导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给 了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利 于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。 其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。 应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变 成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了 IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。 IoC 和 DI DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形 象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并 非为软件系 统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。 通过依赖注入机制,我们只需要通过简单的配置,而无需任何 代码就可指定目标需要的资 源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。 理解 DI 的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分 析一下:  谁依赖于谁:当然是应用程序依赖于 IoC 容器;  为什么需要依赖:应用程序需要 IoC 容器来提供对象需要的外部资源;  谁注入谁:很明显是 IoC 容器注入应用程序某个对象,应用程序依赖的对象;  注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。 IoC 和 DI 由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较 含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象 关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IoC 而言, “依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。 AOP 的基本概念 AOP(Aspect Oriented Programming),即面向切面编程,可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。AOP 利用一种称为"横切"的技术, 剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其 命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同 调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于 未来的可操作性和可维护性。 使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主 要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们 经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用 在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。 AOP 核心概念  连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初 始化、方法执行、方法调用、字段调用或处理异常等等,Spring 只支持方法执行连接点, 在 AOP 中表示为“在哪里干”;  切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring 支持 perl5 正则表达式和 AspectJ 切入点模式,Spring 默认使用 AspectJ 语法,在 AOP 中表示为“在哪里干的集合”;  通知(Advice):在 连接点上执行的行为,通知提供了在 AOP 中需要在切入点所选择 的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知 (after advice)、环绕通知(around advice),在 Spring 中通过代理模式实现 AOP,并 通过拦截器模式以环绕连接点的拦截器链织入通知;在 AOP 中表示为“干什么”;  方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。可以认为是通 知、引入和切入点的组合;在 Spring 中可以使用 Schema 和@AspectJ 方式进行组织 实现;在 AOP 中表示为“在哪干和干什么集合”;  引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段 或方法,Spring 允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对 象), 在 AOP 中表示为“干什么(引入什么)”;  目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的 对象,需要被通知的对象,从而也可称为“被通知对象”;由于 Spring AOP 通过代理模 式实现,从而这个对象永远是被代理对象,在 AOP 中表示为“对谁干”;  AOP 代理(AOP Proxy):AOP 框架使用代理模式创建的对象,从而实现在连接点处 插入通知(即应用切面),就是通过代理来对目标对象应用切面。在 Spring 中,AOP 代 理可以用 JDK 动态代理或 CGLIB 代理实现,而通过拦截器模型应用切面。  织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出 AOP 代理 对象的过程,织入可以在编译期、类装载期、运行期进行。 在 AOP 中,通过切入点选择目标对象的连接点,然后在目标对象的相应连接点处织入通知, 而切入点和通知就是切面(横切关注点),而在目标对象连接点处应用切面的实现方式是通 过 AOP 代理对象。 Spring 对 AOP 的支持 Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成、管理,其依赖关系也由 IOC 容器负责 管理。因此,AOP 代理可以直接使用容器中的其它 bean 实例作为目标,这种关系可由 IOC 容器的依赖注入提供。Spring 创建代理的规则为: 1、 默认使用 Java 动态代理来创建 AOP 代理,这样就可以为任何接口实例创建代理了 2、 当需要代理的类不是代理接口的时候,Spring 会切换为使用 CGLIB 代理,也可强制使 用 CGLIB AOP 编程其实是很简单的事情,纵观 AOP 编程,程序员只需要参与三个部分: 1、 定义普通业务组件 2、 定义切入点,一个切入点可能横切多个业务组件 3、 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作 所以进行 AOP 编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增 强处理,AOP 框架将自动生成 AOP 代理,即:代理对象的方法=增强处理+被代理对象的方 法 Spring 的优点 1.使用 Spring 的 IOC 容器,将对象之间的依赖关系交给 Spring,降低组件之间的耦合性, 让我们更专注于应用逻辑 2.可以提供众多服务,事务管理,WS 等。 3.AOP 的很好支持,方便面向切面编程。 4.对主流的框架提供了很好的集成支持,如 Hibernate,Struts2,JPA 等 5.Spring DI 机制降低了业务对象替换的复杂性。 6.Spring 属于低侵入,代码污染极低。 7.Spring 的高度可开放性,并不强制依赖于 Spring,开发者可以自由选择 Spring 部分或全 部 MyBatis MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免 了几乎所有的 JDBC 代码和手工设置参数以及抽取结果集。MyBatis 使用简单的 XML 或注解来配置和映射基本体,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。mybatis 提供一种“半自动化”的 ORM 实现。这里的“半 自动化”,是相对 Hibernate 等提供了全面的数据库封装机制的“全自动化”ORM 实现而言, “全自动”ORM 实现了 POJO 和数据库表之间的映射,以及 SQL 的自动生成和执行。 mybatis 的重点是在于 POJO 与 SQL 之间的映射关系。 Mybatis 的执行流程。如下图所示: 原理详解: MyBatis 应用程序根据 XML 配置文件创建 SqlSessionFactory,SqlSessionFactory 在根 据配置,配置来源于两个地方,一处是配置文件,一处是 Java 代码的注解,获取一个 SqlSession。SqlSession 包含了执行 sql 所需要的所有方法,可以通过 SqlSession 实例直 接运行映射的 sql 语句,完成对数据的增删改查和事务提交等,用完之后关闭 SqlSession。 MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架,其主要就完成 2 件事情:  封装 JDBC 操作  利用反射打通 Java 类与 SQL 语句之间的相互转换 MyBatis 的主要设计目的就是让我们对执行 SQL 语句时对输入输出的数据管理更加方便, 所以方便地写出 SQL 和方便地获取 SQL 的执行结果才是 MyBatis 的核心竞争力。 MyBatis 的主要成员  Configuration:MyBatis 所有的配置信息都保存在 Configuration 对象之中,配置文件 中的大部分配置都会存储到该类中  SqlSession:作为 MyBatis 工作的主要顶层 API,表示和数据库交互时的会话,完成必 要数据库增删改查功能  Executor:MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓 存的维护  StatementHandler:封装了 JDBC Statement 操作,负责对 JDBC statement 的操作, 如设置参数等  ParameterHandler:负责对用户传递的参数转换成 JDBC Statement 所对应的数据 类型  ResultSetHandler:负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集 合  TypeHandler:负责 java 数据类型和 jdbc 数据类型(也可以说是数据表列类型)之间的 映射和转换  MappedStatement:维护一条节点的封装  SqlSource:负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封 装到 BoundSql 对象中,并返回  BoundSql:表示动态生成的 SQL 语句以及相应的参数信息 以上主要成员在一次数据库操作中基本都会涉及,在 SQL 操作中重点需要关注的是 SQL 参数什么时候被设置和结果集怎么转换为 JavaBean 对象的,这两个过程正好对应 StatementHandler 和 ResultSetHandler 类中的处理逻辑。 MyBatis 缓存 MyBatis 提供查询缓存,用于减轻数据库压力,提高性能。MyBatis 提供了一级缓存和二级 缓存。 一级缓存:是 SqlSession 级别的缓存,每个 SqlSession 对象都有一个哈希表用于缓存 数据,不同 SqlSession 对象之间缓存不共享。同一个 SqlSession 对象对象执行 2 遍相同 的 SQL 查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库 查询了,直接返回缓存结果即可。MyBatis 默认是开启一级缓存的。 二级缓存是:mapper 级别的缓存,二级缓存是跨 SqlSession 的,多个 SqlSession 对象 可以共享同一个二级缓存。不同的 SqlSession 对象执行两次相同的 SQL 语句,第一次会 将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis 默认是不开启 二级缓存的,可以在配置文件中使用如下配置来开启二级缓存: 当 SQL 语句进行更新操作(删除/添加/更新)时,会清空对应的缓存,保证缓存中存储的都是 最新的数据。MyBatis 的二级缓存对细粒度的数据级别的缓存实现不友好,比如如下需求: 对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品 信息,此时如果使用 mybatis 的二级缓存就无法实现当一个商品变化时只刷新该商品的缓 存信息而不刷新其它商品的信息,因为 mybaits 的二级缓存区域以 mapper 为单位划分,当 一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根 据需求对数据有针对性缓存,具体业务具体实现。 MyBatis 的优缺点 优点: 1. 简单易学 mybatis 本身就很小且简单。没有任何第三方依赖,最简单安装只要两个 jar 文件+配置几个 sql 映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的 掌握它的设计思路和实现。 2. 灵活 mybatis 不会对应用程序或者数据库的现有设计强加任何影响。 sql 写在 xml 里, 便于统一管理和优化。通过 sql 基本上可以实现我们不使用数据访问框架可以实现的所 有功能,或许更多。 3. 解除 sql 与程序代码的耦合通过提供 DAL 层,将业务逻辑和数据访问逻辑分离,使系统 的设计更清晰,更易维护,更易单元测试。sql 和代码的分离,提高了可维护性。 4. 提供映射标签,支持对象与数据库的 orm 字段关系映射 5. 提供对象关系映射标签,支持对象关系组建维护 6. 提供 xml 标签,支持编写动态 sql。 缺点: 1. 编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此。 2. SQL 语句依赖于数据库,导致数据库移植性差,不能更换数据库。 3. 框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查 询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。 4. 二级缓存机制不佳 5. mybatis 框架执行过程: 1、配置 mybatis 的配置文件,SqlMapConfig.xml(名称不固定) 2、通过配置文件,加载 mybatis 运行环境,创建 SqlSessionFactory 会话工厂 SqlSessionFactory 在实际使用时按单例方式。 3、通过 SqlSessionFactory 创建 SqlSession SqlSession 是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议 sqlSession 应用场合在方法体内。 4、调用 sqlSession 的方法去操作数据。 如果需要提交事务,需要执行 SqlSession 的 commit()方法。 5、释放资源,关闭 SqlSession mybatis 与 Hibernate 的不同 1.Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己 编写 Sql 语句,不过 mybatis 可以通过 XML 或注解方式灵活配置要运行的 sql 语句,并将 java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对 象。 2.Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性能, 灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软 件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql 映射文件,工作量大。 3.Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需 求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate 的学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡, 以及怎样用好 Hibernate 需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是 好架构,所以框架只有适合才是最好。 Hibernate 什么是 Hibernate? Hibernate,翻译过来是冬眠的意思,正好现在已经进入秋季,世间万物开始准备冬 眠了。其实对于对象来说就是持久化。 扫盲--------------------------------------------------------------------------  持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中 (如磁盘)。持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可 以存储在磁盘文件中、XML 数据文件中等等。  持久化是将程序数据在持久状态和瞬时状态间转换的机制。  JDBC 就是一种持久化机制。文件 IO 也是一种持久化机制。 结束-------------------------------------------------------------------------- hibernate 简介: hibernate 是一个开源框架,它是对象关联关系映射的框架,它对 JDBC 做了轻量级的封装, 而我们 java 程序员可以使用面向对象的思想来操纵数据库。 hibernate 核心接口 session:负责被持久化对象 CRUD 操作 sessionFactory:负责初始化 hibernate,创建 session 对象 configuration:负责配置并启动 hibernate,创建 SessionFactory Transaction:负责事物相关的操作 Query 和 Criteria 接口:负责执行各种数据库查询 我们从三个角度理解一下 Hibernate:  Hibernate 是对 JDBC 进一步封装 原来没有使用 Hiberante 做持久层开发时,存在很多冗余,如:各种 JDBC 语句, connection 的管理,所以出现了 Hibernate 把 JDBC 封装了一下,我们不用操作数据, 直接操作它就行了。  我们再从分层的角度来看 我们知道非常典型的三层架构:表示层,业务层,还有持久层。Hiberante 也是持久层 的框架,而且持久层的框架还有很多,比如:IBatis,Nhibernate,JDO,OJB,EJB 等 等。  Hibernate 是开源的一个 ORM(对象关系映射)框架。 ORM,即 Object-Relational Mapping,它的作用就是在关系型数据库和对象之间做了 一个映射。从对象(Object)映射到关系(Relation),再从关系映射到对象。这样,我 们在操作数据库的时候,不需要再去和复杂 SQL 打交道,只要像操作对象一样操作它 就可以了(把关系数据库的字段在内存中映射成对象的属性)。 Hibernate 的核心: 从上图中,我们可以看出 Hibernate 六大核心接口,两个主要配置文件,以及他们直接的关 系。Hibernate 的所有内容都在这了。那我们从上到下简单的认识一下,每个接口进行一句 话总结。 1、Configuration 接口:负责配置并启动 Hibernate 2、SessionFactory 接口:负责初始化 Hibernate 3、Session 接口:负责持久化对象的 CRUD 操作 4、Transaction 接口:负责事务 5、Query 接口和 Criteria 接口:负责执行各种数据库查询 注意:Configuration 实例是一个启动期间的对象,一旦 SessionFactory 创建完成它就被丢 弃了。 hibernate 工作原理: 1. 通过 Configuration config = new Configuration().configure();// 读 取 并 解 析 hibernate.cfg.xml 配置文件 2.由 hibernate.cfg.xml 中的读取并解析映 射信息 3.通过 SessionFactory sf = config.buildSessionFactory();//创建 SessionFactory 4.Session session = sf.openSession();//打开 Sesssion 5.Transaction tx = session.beginTransaction();//创建并启动事务 Transation 6.persistent operate 操作数据,持久化操作 7.tx.commit();//提交事务 8.关闭 Session 9.关闭 SesstionFactory hibernate 的工作原理 hibernate 如何连接数据库? 配置文件 Hibernate.cfg.xml 文件中定义了和数据库进行连接的信息,包括数据库方言.jdbc 驱动.用户名.密码和 URL 等。 Configuration 类借助 dom4j 的 xml 解析器进行 xml 的解析设置环境,然后使用这些环境 属性来生成 sessionfactory。这样 sessionfactory 生成的 session 就能够成功获得数据库的连接。 hibernate 如何进行数据库写操作? 当保存一个 pojo 持久化对象时,触发 Hibernate 保存事件监听器进行处理。Hibernate 通过 映射文件获得对象对应的数据库表名以及属性对应的数据库列名,然后通过反射机制获得 持久化对象的各个属性,最终组织向数据库插入新对象的 SQL 的 insert 语句。调用 session.save()保存数据后这个对象会被标识为持久化状态放在 session,当事物进行提 交时才真正执行 insert 语句。 hibernate 如何从数据中载入对象? 当需要读取读取文件时,Hibernate 先尝试从 session 缓存中读取,如果 session 缓存数据 不存在或是脏数据并且配置了二级缓存,Hibernate 尝试从二级缓存中检索数据;否则 Hibernate 会根据对象类型·主键等信息组织 select 语句到数据库中读取,再把 select 结 果组织成对象返回。 hibernate 如何进行数据库查询操作? Hibernate 提供 SQL HQL Criteria 查询方式。HQL 是其中运用最广泛的查询方式。用户 使用 session.createQuery()函数以一条 HQL 语句为参数创建 Query 查询对象后, Hibernate 会使用 Anltr 库把 HQL 语句解析成 jdbc 可以识别的 sql 语句。如果设置了查询 缓存,那么执行 Query.list()时,Hibernate 会先对查询缓存进行查询,如果查询缓存不存 在,在使用 select 语句查询数据库。 Hibernate 是如何延迟加载?get 与 load 的区别 1. 对于 Hibernate get 方法,Hibernate 会确认一下该 id 对应的数据是否存在,首先在 session 缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据 库中没有就返回 null。这个相对比较简单,也没有太大的争议。主要要说明的一点就是在这 个版 本 (bibernate3.2 以上)中 get 方法也会查找二级缓存! 2. Hibernate load 方法加载实体对象的时候,根据映射文件上类级别的 lazy 属性的配置(默 认为 true),分情况讨论:  若为 true,则首先在 Session 缓存中查找,看看该 id 对应的对象是否存在,不存在则使 用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由 CGLIB 动态生成)。 等到具体使用该对象(除获取 OID 以外)的时候,再查询二级缓存和数据库,若仍没发现 符合条件的记录,则会抛出一个 ObjectNotFoundException。  若为 false,就跟 Hibernateget 方法查找顺序一样,只是最终若没发现符合条件的记录, 则会抛出一个 ObjectNotFoundException。 这里 get 和 load 有两个重要区别:  如果未能发现符合条件的记录,Hibernate get 方法返回 null,而 load 方法会抛出一个 ObjectNotFoundException。  load 方法可返回没有加载实体数据的代 理类实例,而 get 方法永远返回有实体数据的 对象。 总之对于 get 和 load 的根本区别,一句话,hibernate 对于 load 方法认为该数据在数据库 中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常; 而对于 get 方 法,hibernate 一定要获取到真实的数据,否则返回 null。 Hibernate 中怎样实现类之间的关系?(如:一对多、多对多的关系) 类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们 程序中把所有的表与类都映射在一起,它们通过配置文件中的 many-to-one、one-to- many、many-to-many、 Hibernate 的缓存机制: Hibernate 缓存的作用: Hibernate 是一个持久层框架,经常访问物理数据库,为了降低应用程序对物理数据源 访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制, 应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据 Hibernate 缓存分类: Hibernate 缓存包括两大类:Hibernate 一级缓存和 Hibernate 二级缓存  Hibernate 一级缓存又称为“Session 的缓存”,它是内置的,意思就是说,只要你使 用 hibernate 就必须使用 session 缓存。由于 Session 对象的生命周期通常对应一个 数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。在第一级缓存中,持 久化类的每个实例都具有唯一的 OID。  Hibernate 二级缓存又称为“SessionFactory 的缓存”,由于 SessionFactory 对象 的生命周期和应用程序的整个过程对应,因此 Hibernate 二级缓存是进程范围或者集 群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被 缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,在默认 情况下,SessionFactory 不会启用这个插件。 什么样的数据适合存放到第二级缓存中?  很少被修改的数据  不是很重要的数据,允许出现偶尔并发的数据  不会被并发访问的数据  常量数据 不适合存放到第二级缓存的数据?  经常被修改的数据  绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发  与其他应用共享的数据。 Hibernate 查找对象如何应用缓存? 当 Hibernate 根据 ID 访问数据对象的时候,首先从 Session 一级缓存中查;查不到,如果 配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,把结果按照 ID 放入 到缓存 删除、更新、增加数据的时候,同时更新缓存 Hibernate 管理缓存实例 无论何时,我们在管理 Hibernate 缓存(Managing the caches)时,当你给 save()、 update()或 saveOrUpdate()方法传递一个对象时,或使用 load()、 get()、list()、iterate() 或 scroll()方法获得一个对象时, 该对象都将被加入到 Session 的内部缓存中。 当随后 flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作 发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用 evict() 方法,从一级缓 存中去掉这些对象及其集合。 优化 Hibernate  使用双向一对多关联,不使用单向一对多  灵活使用单向一对多关联  不用一对一,用多对一取代  配置对象缓存,不使用集合缓存  一对多集合使用 Bag,多对多集合使用 Set  继承类使用显式多态  表字段要少,表关联不要怕多,有二级缓存撑腰 hibernate 的开发步骤: 1 搭建好环境 引入 hibernate 最小的 jar 包 准备 Hibernate.cfg.xml 启动配置文件 2 写实体类(pojo) 3 为实体类写映射文件"User.hbm.xml" 在 hibernate.cfg.xml 添加映射的实体 4 创建库表 5 写测试类 1) 获得 Configuration 2) 创建 SessionFactory 3) 打开 Session 4) 开启事务 5) 使用 session 操作数据 6) 提交事务 7) 关闭资源 Hibernate 的优/缺点: 优点: 1、更加对象化:以对象化的思维操作数据库,我们只需要操作对象就可以了,开发更加对象 化。 2、移植性:因为 Hibernate 做了持久层的封装,你就不知道数据库,你写的所有的代码都具 有可复用性。 3、Hibernate 是一个没有侵入性的框架,没有侵入性的框架我们称为轻量级框架。对比 Struts 的 Action 和 ActionForm,都需要继承,离不开 Struts。Hibernate 不需要继承任何 类,不需要实现任何接口。这样的对象叫 POJO 对象。 4、Hibernate 代码测试方便。 5、提高效率,提高生产力。 缺点: 1、使用数据库特性的语句,将很难调优 2、对大批量数据更新存在问题 3、系统中存在大量的攻击查询功能 mybatis+spring+springMVC 整个项目流程的配置 一.用 maven 来对项目的管理,导入包依赖到 POM.xml 1. 日志依赖 如 slf4j、logback、log4j 等。 2. 数据库依赖 如 c3p0、mysql 等。 3. mybatis 依赖 如 mybatis、mybatis-spring 等。 4. Servlet web 层相关依赖 如 Servlet、jstl、tablib、jason 等。 5. spring 相 关 依 赖 如 spring 、 spring-jdbc 、 spring-bean 、 spring-context 、 spring-tx 等。 6. springMVC 相关依赖 如 spring-web、springweb-mvc 等。 二、建立相关的数据库(主键的设置为项目的关键)。 三、建立与数据库映射的 entity 层(*.java),注意一对多、多对一的映射关系。 四、dao 层(*Dao.java)的建立、根据数据库相关的业务逻辑,建立 dao 层的接口类、不需要 实现类。 五、配置 mybatis 配置文件(mybatis-config.xml)。对一些全局属性的配置 如,“使用列别 名替换列名”、“开启驼峰命名转换“等。 六、利用 mybatis 通过配置文件(*-dao.xml)建立对 dao 层(*Dao.java)的映射。如运用其 “强大的”sql 语句实现对数据库的增删改查。 七、利用 spring 配置文件(spring-dao.xml)对 mybatis 以及其他资源进行整合。 1. 扫描数据库配置文件(jdbc.properties)。 2. 数据库连接池(dataSource)的配置。 3. 建立 sqlSessionFactory 对象, 1) 注入连接池、 2) 利用 configLocation 对配置文件(mybatis-config.xml)进行注册。 3) 使用 typeAliasesPackage 对 entity 层进行扫描。 4) 使用 mapperLocations 对(*-dao.xml)等映射 dao 层的配置文件进行烧苗。 4. 配置扫描 Dao 层接口包,动态实现 Dao 接口,并注入到 spring 容器中 i. 使用 sqlSessionFactoryBeanName 对 sqlSessionFactory 进行管理,防止 jdbc.properties 等配置文件未初始化,就加载。 ii. 使用 basePackage 对 dao 层进行扫描 ------------------------------------------------------------------------------ 上述完成后、应该编写对 dao 层的单元测试类、验证以上代码部分的逻辑,保证不出错误。不然将 会影响到后面的代码的逻辑处理与维护。 八、利用 spring 配置文件(spring-service.xml)对 service 层进行全局属性的配置。 1. 扫描 service 包(包含子包)下所有使用注解的类型,如@Service、@Autowired 2. 配置事务管理器 transactionManager(与 mybatis 采用的连接方式要一致),注 入连接池(dataSource) 3. 配 置 基 于 注 解 ( 如 @Transactional) 的 声 明 式 事 务 , 注 入 事 务 管 理 器 transactionManager 九、根据核心的业务逻辑建立 service 层,包括 service 接口、service 实现类。 ------------------------------------------------------------------------------ 上述完成后、应该编写对 service 层的单元测试类、验证以上代码部分的逻辑,保证不出错误。不然将 会影响到后面的代码的逻辑处理与维护。 十、根据视图层的相关的业务逻辑建立 Controller。对 url 请求的处理,以及传递所需的对象 给视图层。 十一、利用 spring 配置文件(spring-web.xml)对 web 层进行全局属性的配置。 1)开启 springMVC 注解模式 1.自动注册 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter 2.提供一系列:数据绑定,数字和日期的 format,@NumberFormat,@DataTimeFormat,xml,json 默认读写支持 2)静态资源默认 servlet 配置、或是对需要处理的资源资源处理。 3)配置显示 JSP 的 ViewResolver。还有一些其他的 ViewResolver(如上传下载、json)。 4)扫描 web 层相关的 bean。 十二、配置 web.xml。创建 springMVC 核心的 DispatcherServlet。 1)扫描所有的 spring 相关的配置文件(spring-dao.xml、spring-service.xml、spring- web.xml) 2)至此完成了“mybatis-spring-springMVC”的整个配置过程 使用 Maven 搭建 Struts2+Spring3+Hibernate4 的整合开发环境 1. 一建立 Maven 工程 2. 二搭建 Spring3 开发环境 1. 1 下载 Spring3 需要的 jar 包 2. 2 编写 Spring 配置文件 3. 3 编写单元测试 4. 4 在 webxml 中配置 Spring 监听器 3. 三搭建 Struts2 开发环境并整合 Spring3 1. 1 下载 Struts2 需要的 jar 包 2. 2 编写 Strutsxml 配置文件 3. 3 在 webxml 中配置 Struts2 的过滤器 4. 4 编写测试 4. 四搭建 Hibernate4 开发环境并整合 Spring3 1. 1 下载 Hibernate4 需要的 jar 包 2. 2 添加数据库驱动 jar 包 3. 3 添加数据库连接池 jar 包 4. 4 添加 aspectjweaver 包 5. 5 编写连接数据库的配置信息 6. 6 编写 Hibernate 与 Spring 整合的配置文件 7. 7 编写单元测试代码 5. 五三大框架综合测试 1. 1 完善 webxml 文件中的配置 2. 2 编写测试代码 【SSH 网上商城项目实战】 整合 Struts2、Hibernate4.3 和 Spring4.2 1. 整个项目 jar 包的管理 2. 搭建 Spring 环境 1. 1 添加配置文件 beansxml 和相应的 jar 包 2. 2 测试 Spring 的 IoC 环境 3. 搭建 Hibernate 环境 1. 1 添加相应的 jar 包 2. 2 新建数据库和表 3. 3 DB 浏览器连接到 Mysql 数据库 4. 4 创建 xml 映射文件和 sessionFactory 5. 5 通过逆向工程生成 modelorm 映射文件 6. 6 测试 Hibernate 持久化数据库 4. 整合 Spring 和 Hibernate 1. 1 导入相应的 jar 包 2. 2 配置数据源 dataSource 3. 3 配置 sessionFactory 4. 4 配置事务管理器 5. 5 配置 advice 通知 6. 6 配置 AOP 切面 7. 7 测试整合结果 5. 搭建 Struts2 环境 1. 1 添加相应的配置和 jar 包 2. 2 创建 Action 并且配置到 strutsxml 文件中 3. 3 测试 Struts2 环境 6. Spring 和 Struts2 整合 1. 1 添加相应的 jar 包 2. 2 把 Action 和它的依赖交给 Spring 管理 3. 3 修改 strutsxml 中的配置 4. 4 配置监听器 5. 5 测试整合结果
还剩22页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

temadon

贡献于2018-02-05

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf