当前位置: 首页 > news >正文

做写手一般上什么网站好网站显示速度的代码是什么情况

做写手一般上什么网站好,网站显示速度的代码是什么情况,网站建设平台,华为手机应用引擎一、Spring面试题 专题部分 1.1、什么是spring? Spring是一个轻量级Java开发框架#xff0c;最早有Rod Johnson创建#xff0c;目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack#xff08;一站式#xff09;轻量…一、Spring面试题 专题部分 1.1、什么是spring? Spring是一个轻量级Java开发框架最早有Rod Johnson创建目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack一站式轻量级开源框架为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构因此Java开发者可以专注于应用程序的开发。 Spring最根本的使命是解决企业级应用开发的复杂性即简化Java开发。 Spring可以做很多事情它为企业级开发提供给了丰富的功能但是这些功能的底层都依赖于它的两个核心特性也就是依赖注入dependency injectionDI和面向切面编程aspect-oriented programmingAOP。 为了降低Java开发的复杂性Spring采取了以下4种关键策略 基于POJO的轻量级和最小侵入性编程通过依赖注入和面向接口实现松耦合基于切面和惯例进行声明式编程通过切面和模板减少样板式代码。 1.2、Spring框架的设计目标设计理念和核心是什么 Spring设计目标Spring为开发者提供一个一站式轻量级应用开发平台 Spring设计理念在JavaEE开发中支持POJO和JavaBean开发方式使应用面向接口开发充分支持OO面向对象设计方法Spring通过IoC容器实现对象耦合关系的管理并实现依赖反转将对象之间的依赖关系交给IoC容器实现解耦 Spring框架的核心IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系通过AOP以动态非侵入的方式增强服务。 IoC让相互协作的组件保持松散的耦合而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。 1.3、Spring的优缺点是什么 优点 方便解耦简化开发 Spring就是一个大工厂可以将所有对象的创建和依赖关系的维护交给Spring管理。 AOP编程的支持 Spring提供面向切面编程可以方便的实现对程序进行权限拦截、运行监控等功能。 声明式事务的支持 只需要通过配置就可以完成对事务的管理而无需手动编程。 方便程序的测试 Spring对Junit4支持可以通过注解方便的测试Spring程序。 方便集成各种优秀框架 Spring不排斥各种优秀的开源框架其内部提供了对各种优秀框架的直接支持如Struts、Hibernate、MyBatis等。 降低JavaEE API的使用难度 Spring对JavaEE开发中非常难用的一些APIJDBC、JavaMail、远程调用等都提供了封装使这些API应用难度大大降低。 缺点 Spring明明一个很轻量级的框架却给人感觉大而全 Spring依赖反射反射影响性能 使用门槛升高入门Spring需要较长时间 1.4、Spring有哪些应用场景 应用场景JavaEE企业应用开发包括SSH、SSM等 Spring价值 Spring是非侵入式的框架目标是使应用程序代码对框架依赖最小化 Spring提供一个一致的编程模型使应用直接使用POJO开发与运行环境隔离开来 Spring推动应用设计风格向面向对象和面向接口开发转变提高了代码的重用性和可测试性 1.5、Spring由哪些模块组成 Spring 总共大约有 20 个模块 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器Core Container 、 AOPAspect Oriented Programming和设备支持Instrmentation 、数据访问与集成Data Access/Integeration 、 Web、 消息Messaging 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图 spring core提供了框架的基本组成部分包括控制反转Inversion of ControlIOC和依赖注入Dependency InjectionDI功能。spring beans提供了BeanFactory是工厂模式的一个经典实现Spring将管理对象称为Bean。spring context构建于 core 封装包基础上的 context 封装包提供了一种框架式的对象访问方法。spring jdbc提供了一个JDBC的抽象层消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析 用于简化JDBC。spring aop提供了面向切面的编程实现让你可以自定义拦截器、切点等。spring Web提供了针对 Web 开发的集成特性例如文件上传利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。spring test主要为测试提供支持的支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。 1.6、Spring 框架中都用到了哪些设计模式 工厂模式BeanFactory就是简单工厂模式的体现用来创建对象的实例单例模式Bean默认为单例模式。代理模式Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术模板方法用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。观察者模式定义对象键一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都会得到通知被制动更新如Spring中listener的实现–ApplicationListener。 1.7、详细讲解一下核心容器spring context应用上下文) 模块 这是基本的Spring模块提供spring 框架的基础功能BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上它使Spring成为一个容器。 Bean 工厂是工厂模式的一个实现提供了控制反转功能用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory 它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。 1.8、Spring框架中有哪些不同类型的事件 Spring 提供了以下5种标准的事件 上下文更新事件ContextRefreshedEvent在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。 上下文开始事件ContextStartedEvent当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。 上下文停止事件ContextStoppedEvent当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。 上下文关闭事件ContextClosedEvent当ApplicationContext被关闭时触发该事件。容器被关闭时其管理的所有单例Bean都被销毁。 请求处理事件RequestHandledEvent在Web应用中当一个http请求request结束触发该事件。如果一个bean实现了ApplicationListener接口当一个ApplicationEvent 被发布以后bean会自动被通知。 1.9、Spring 应用程序有哪些不同组件 Spring 应用一般有以下组件 接口 - 定义功能。 Bean 类 - 它包含属性setter 和 getter 方法函数等。 Bean 配置文件 - 包含类的信息以及如何配置它们。 Spring 面向切面编程AOP - 提供面向切面编程的功能。 用户程序 - 它使用接口。 1.10、使用 Spring 有哪些方式 使用 Spring 有以下方式 作为一个成熟的 Spring Web 应用程序。 作为第三方 Web 框架使用 Spring Frameworks 中间层。 作为企业级 Java Bean它可以包装现有的 POJOPlain Old Java Objects。 用于远程使用。 二 、Spring控制反转(IOC) 2.1、什么是Spring IOC 容器 控制反转即IoC (Inversion of Control)它把传统上由程序代码直接操控的对象的调用权交给容器通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移从程序代码本身转移到了外部容器。 Spring IOC 负责创建对象管理对象通过依赖注入DI装配对象配置对象并且管理这些对象的整个生命周期。 2.2、控制反转(IoC)有什么作用 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事在对象关系比较复杂时如果依赖关系需要程序猿来维护的话那是相当头疼的 解耦由容器去维护具体的对象 托管了类的产生过程比如我们需要在类的产生过程中做一些处理最直接的例子就是代理如果有容器程序可以把这部分处理交给容器应用程序则无需去关心类是如何完成代理的 2.3、IOC的优点是什么 IOC 或 依赖注入把应用的代码量降到最低。 它使应用容易测试单元测试不再需要单例和JNDI查找机制。 最小的代价和最小的侵入性使松散耦合得以实现。 IOC容器支持加载服务时的饿汉式初始化和懒加载。 2.4、Spring IoC 的实现机制 Spring 中的 IoC 的实现原理就是工厂模式加反射机制。 示例 interface Fruit {public abstract void eat();}class Apple implements Fruit {public void eat(){System.out.println(Apple);} }class Orange implements Fruit {public void eat(){System.out.println(Orange);} }class Factory {public static Fruit getInstance(String ClassName) {Fruit fnull;try {f(Fruit)Class.forName(ClassName).newInstance();} catch (Exception e) {e.printStackTrace();}return f;} }class Client {public static void main(String[] a) {Fruit fFactory.getInstance(io.github.dunwu.spring.Apple);if(f!null){f.eat();}} } 2.5、Spring 的 IoC支持哪些功能 Spring 的 IoC 设计支持以下功能 依赖注入依赖检查自动装配支持集合指定初始化方法和销毁方法支持回调某些方法但是需要实现 Spring 接口略有侵入 其中最重要的就是依赖注入从 XML 的配置上说即 ref 标签。对应 Spring RuntimeBeanReference 对象。 对于 IoC 来说最重要的就是容器。容器管理着 Bean 的生命周期控制着 Bean 的依赖注入。 2.6、BeanFactory 和 ApplicationContext有什么区别 BeanFactory和ApplicationContext是Spring的两大核心接口都可以当做Spring的容器。 其中ApplicationContext是BeanFactory的子接口。 依赖关系 BeanFactory是Spring里面最底层的接口包含了各种Bean的定义读取bean配置文档管理bean的加载、实例化控制bean的生命周期维护bean之间的依赖关系。 ApplicationContext接口作为BeanFactory的派生除了提供BeanFactory所具有的功能外还提供了更完整的框架功能 继承MessageSource因此支持国际化。统一的资源文件访问方式。提供在监听器中注册bean的事件。同时加载多个配置文件。载入多个有继承关系上下文 使得每一个上下文都专注于一个特定的层次比如应用的web层。 加载方式 BeanFactroy采用的是延迟加载形式来注入Bean的即只有在使用到某个Bean时(调用getBean())才对该Bean进行加载实例化。这样我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入BeanFacotry加载后直至第一次使用调用getBean方法才会抛出异常。 ApplicationContext它是在容器启动时一次性创建了所有的Bean。这样在容器启动时我们就可以发现Spring中存在的配置错误这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean通过预载入单实例bean ,确保当你需要的时候你就不用等待因为它们已经创建好了。 相对于基本的BeanFactoryApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时程序启动较慢。 创建方式 BeanFactory通常以编程的方式被创建ApplicationContext还能以声明的方式创建如使用ContextLoader。 注册方式 BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用但两者之间的区别是BeanFactory需要手动注册而ApplicationContext则是自动注册。 2.7、Spring 如何设计容器的BeanFactory和ApplicationContext的关系详解 Spring 作者 Rod Johnson 设计了两个接口用以表示容器。 BeanFactoryApplicationContext BeanFactory 简单粗暴可以理解为就是个 HashMapKey 是 BeanNameValue 是 Bean 实例。通常只提供注册put获取get这两个功能。我们可以称之为 “低级容器”。 ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取支持多种消息例如 JSP tag 的支持对 BeanFactory 多了工具级别的支持等待。所以你看他的名字已经不是 BeanFactory 之类的工厂了而是 “应用上下文” 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法此方法是所有阅读 Spring 源码的人的最熟悉的方法用于刷新整个容器即重新加载/刷新所有的 bean。 当然除了这两个大接口还有其他的辅助接口这里就不介绍他们了。 BeanFactory和ApplicationContext的关系 为了更直观的展示 “低级容器” 和 “高级容器” 的关系这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系。 最上面的是 BeanFactory下面的 3 个绿色的都是功能扩展接口这里就不展开讲。 看下面的隶属 ApplicationContext 粉红色的 “高级容器”依赖着 “低级容器”这里说的是依赖不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能支持不同的信息源头可以访问文件资源支持应用事件Observer 模式。 通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦 左边灰色区域的是 “低级容器” 只负载加载 Bean获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置生命周期事件回调等。 小结 IoC 在 Spring 里只需要低级容器就可以实现2 个步骤 加载配置文件解析成 BeanDefinition 放在 Map 里。 调用 getBean 的时候从 BeanDefinition 所属的 Map 里拿出 Class 对象进行实例化同时如果有依赖关系将递归调用 getBean 方法 —— 完成依赖注入。 上面就是 Spring 低级容器BeanFactory的 IoC。 至于高级容器 ApplicationContext他包含了低级容器的功能当他执行 refresh 模板方法的时候将刷新整个容器的 Bean。同时其作为高级容器包含了太多的功能。一句话他不仅仅是 IoC。他支持不同信息源头支持 BeanFactory 工具类支持层级容器支持访问文件资源支持事件发布通知支持接口回调等等。 2.8、ApplicationContext通常的实现是什么 FileSystemXmlApplicationContext 此容器从一个XML文件中加载beans的定义XML Bean 配置文件的全路径名必须提供给它的构造函数。 ClassPathXmlApplicationContext此容器也从一个XML文件中加载beans的定义这里你需要正确设置classpath因为这个容器将在classpath里找bean配置。 WebXmlApplicationContext此容器加载一个XML文件此文件定义了一个WEB应用的所有bean。 2.9、什么是Spring的依赖注入 控制反转IoC是一个很大的概念可以用不同的方式来实现。其主要实现方式有两种依赖注入和依赖查找 依赖注入相对于IoC而言依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入Dependency Injection即组件之间的依赖关系由容器在应用系统运行期来决定也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询只提供普通的Java方法让容器去决定依赖关系。 2.10、依赖注入的基本原则 依赖注入的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责“查找资源”的逻辑应该从应用组件的代码中抽取出来交给IoC容器负责。容器全权负责组件的装配它会把符合依赖关系的对象通过属性JavaBean中的setter或者是构造器传递给需要的对象。 2.11、依赖注入有什么优势 依赖注入之所以更流行是因为它是一种更可取的方式让容器全权负责依赖查询受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比主要优势为 查找定位操作与应用代码完全无关。不依赖于容器的API可以很容易地在任何容器以外使用应用对象。不需要特殊的接口绝大多数对象可以做到完全不必依赖容器。 2.12、有哪些不同类型的依赖注入实现方式 依赖注入是时下最流行的IoC实现方式依赖注入分为接口注入Interface InjectionSetter方法注入Setter Injection和构造器注入Constructor Injection三种方式。其中接口注入由于在灵活性和易用性比较差现在从Spring4开始已被废弃。 构造器依赖注入构造器依赖注入通过容器触发一个类的构造器来实现的该类有一系列参数每个参数代表一个对其他类的依赖。 Setter方法注入Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后调用该bean的setter方法即实现了基于setter的依赖注入。 2.13、构造器依赖注入和 Setter方法注入的区别 构造函数注入setter 注入没有部分注入有部分注入不会覆盖 setter 属性会覆盖 setter 属性任意修改都会创建一个新实例任意修改不会创建一个新实例适用于设置很多属性适用于设置少量属性 两种依赖方式都可以使用构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖setter方法实现可选依赖。 三、Spring Beans 3.1、什么是Spring beans Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化装配和管理。这些beans通过容器中配置的元数据创建。比如以XML文件中 的形式定义。 3.2、一个 Spring Bean 定义 包含什么 一个Spring Bean 的定义包含容器必知的所有配置元数据包括如何创建一个bean它的生命周期详情及它的依赖。 3.3、如何给Spring 容器提供配置元数据Spring有几种配置方式 这里有三种重要的方法给Spring 容器提供配置元数据。 XML配置文件。基于注解的配置。基于java的配置。 3.4、Spring配置文件包含了哪些信息 Spring配置文件是个XML 文件这个文件包含了类信息描述了如何配置它们以及如何相互调用。 3.5、Spring基于xml注入bean的几种方式 Set方法注入构造器注入①通过index设置参数的位置②通过type设置参数类型静态工厂注入实例工厂 3.6、你怎样定义类的作用域 当定义一个 在Spring里我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如当Spring要在需要的时候每次生产一个新的bean实例bean的scope属性被指定为prototype。另一方面一个bean每次使用的时候必须返回同一个实例这个bean的scope 属性 必须设为 singleton。 3.7、解释Spring支持的几种bean的作用域 Spring框架支持以下五种bean的作用域 singleton : bean在每个Spring ioc 容器中只有一个实例。 prototype一个bean的定义可以有多个实例。 request每次http请求都会创建一个bean该作用域仅在基于web的Spring ApplicationContext情形下有效。 session在一个HTTP Session中一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 global-session在一个全局的HTTP Session中一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 注意 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考因为频繁创建和销毁 bean 会带来很大的性能开销。 3.8、Spring框架中的单例bean是线程安全的吗 不是Spring框架中的单例bean不是线程安全的。 spring 中的 bean 默认是单例模式spring 框架并没有对单例 bean 进行多线程的封装处理。 实际上大部分时候 spring bean 无状态的比如 dao 类所以某种程度上来说 bean 也是安全的但如果 bean 有状态的话比如 view model 对象那就要开发者自己去保证线程安全了最简单的就是改变 bean 的作用域把“singleton”变更为“prototype”这样请求 bean 相当于 new Bean()了所以就可以保证线程安全了。 有状态就是有数据存储功能。无状态就是不会保存数据。 3.9、Spring如何处理线程并发问题 在一般情况下只有无状态的Bean才可以在多线程环境下共享在Spring中绝大部分Bean都可以声明为singleton作用域因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理解决线程安全问题。 ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式仅提供一份变量不同的线程在访问前需要获取锁没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。 ThreadLocal会为每一个线程提供一个独立的变量副本从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象在编写多线程代码时可以把不安全的变量封装进ThreadLocal。 3.10、解释Spring框架中bean的生命周期 Spring Bean的生命周期只有四个阶段。 实例化和属性赋值对应构造方法和setter方法的注入初始化和销毁是用户能自定义扩展的两个阶段 实例化 Instantiation 属性赋值 Populate 初始化 Initialization 销毁 Destruction 实例化 - 属性赋值 - 初始化 - 销毁 各个阶段的工作: 实例化创建一个Bean对象 填充属性为属性赋值 初始化 如果实现了xxxAware接口通过不同类型的Aware接口拿到Spring容器的资源 如果实现了BeanPostProcessor接口则会回调该接口的postProcessBeforeInitialzation和postProcessAfterInitialization方法 如果配置了init-method方法则会执行init-method配置的方法 销毁 容器关闭后如果Bean实现了DisposableBean接口则会回调该接口的destroy方法如果配置了destroy-method方法则会执行destroy-method配置的方法 源码学习 前三个阶段主要逻辑都在doCreate()方法中逻辑很清晰就是顺序调用以下三个方法这三个方法与三个生命周期阶段一一对应非常重要在后续扩展接口分析中也会涉及。 createBeanInstance() - 实例化 populateBean() - 属性赋值 initializeBean() - 初始化 注bean的生命周期是从将bean定义全部注册到BeanFacotry中以后开始的。 源码如下能证明实例化属性赋值和初始化这三个生命周期的存在。关于本文的Spring源码都将忽略无关部分便于理解 前三个阶段的源码 // 忽略了无关代码 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper null;if (instanceWrapper null) {// 实例化阶段instanceWrapper createBeanInstance(beanName, mbd, args);}// Initialize the bean instance.Object exposedObject bean;try {// 属性赋值阶段populateBean(beanName, mbd, instanceWrapper);// 初始化阶段exposedObject initializeBean(beanName, exposedObject, mbd);} } 上面这些这个实例化Bean的方法是在getBean()方法中调用的而getBean是在finishBeanFactoryInitialization方法中调用的用来实例化单例非懒加载Bean源码如下 public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1. 刷新前的预处理prepareRefresh();// 2. 获取 beanFactory即前面创建的【DefaultListableBeanFactory】ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// 3. 预处理 beanFactory向容器中添加一些组件prepareBeanFactory(beanFactory);try {// 4. 子类通过重写这个方法可以在 BeanFactory 创建并与准备完成以后做进一步的设置postProcessBeanFactory(beanFactory);// 5. 执行 BeanFactoryPostProcessor 方法beanFactory 后置处理器invokeBeanFactoryPostProcessors(beanFactory);// 6. 注册 BeanPostProcessorsbean 后置处理器registerBeanPostProcessors(beanFactory);// 7. 初始化 MessageSource 组件做国际化功能消息绑定消息解析initMessageSource();// 8. 初始化事件派发器在注册监听器时会用到initApplicationEventMulticaster();// 9. 留给子容器子类子类重写这个方法在容器刷新的时候可以自定义逻辑web 场景下会使用onRefresh();// 10. 注册监听器派发之前步骤产生的一些事件可能没有registerListeners();// 11. 初始化所有的非单实例 beanfinishBeanFactoryInitialization(beanFactory);// 12. 发布容器刷新完成事件finishRefresh();}...} }销毁Bean阶段: 至于销毁是在容器关闭时调用的详见 ConfigurableApplicationContext#close() 生命周期常用扩展点 Spring生命周期相关的常用扩展点非常多所以问题不是不知道而是记不住或者记不牢。其实记不住的根本原因还是不够了解这里通过源码分类的方式帮大家记忆。 区分影响一个bean或者多个bean是从源码分析得出的. 以BeanPostProcessor为例 从refresh方法来看,BeanPostProcessor 实例化比正常的bean早. 从initializeBean方法看,每个bean初始化前后都调用所有BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法. 第一大类影响多个Bean的接口 实现了这些接口的Bean会切入到多个Bean的生命周期中。正因为如此这些接口的功能非常强大Spring内部扩展也经常使用这些接口例如自动注入以及AOP的实现都和他们有关。 InstantiationAwareBeanPostProcessorBeanPostProcessor 这两兄弟可能是Spring扩展中最重要的两个接口InstantiationAwareBeanPostProcessor作用于实例化阶段的前后BeanPostProcessor作用于初始化阶段的前后。正好和第一、第三个生命周期阶段对应。通过图能更好理解 InstantiationAwareBeanPostProcessor InstantiationAwareBeanPostProcessor实际上继承了BeanPostProcessor接口严格意义上来看他们不是两兄弟而是两父子。但是从生命周期角度我们重点关注其特有的对实例化阶段的影响图中省略了从BeanPostProcessor继承的方法。 InstantiationAwareBeanPostProcessor extends BeanPostProcessor InstantiationAwareBeanPostProcessor源码分析 postProcessBeforeInstantiation调用点忽略无关代码 Override protected Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)throws BeanCreationException {try {// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.// postProcessBeforeInstantiation方法调用点这里就不跟进了// 有兴趣的同学可以自己看下就是for循环调用所有的InstantiationAwareBeanPostProcessorObject bean resolveBeforeInstantiation(beanName, mbdToUse);if (bean ! null) {return bean;}}try { // 上文提到的doCreateBean方法可以看到// postProcessBeforeInstantiation方法在创建Bean之前调用Object beanInstance doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace(Finished creating instance of bean beanName );}return beanInstance;}}可以看到postProcessBeforeInstantiation在doCreateBean之前调用也就是在bean实例化之前调用的英文源码注释解释道该方法的返回值会替换原本的Bean作为代理这也是Aop等功能实现的关键点。 postProcessAfterInstantiation调用点忽略无关代码 protected void populateBean(String beanName, RootBeanDefinition mbd, Nullable BeanWrapper bw) {// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.boolean continueWithPropertyPopulation true;// InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()// 方法作为属性赋值的前置检查条件在属性赋值之前执行能够影响是否进行属性赋值if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {continueWithPropertyPopulation false;break;}}}}// 忽略后续的属性赋值操作代码 }可以看到该方法在属性赋值方法内但是在真正执行赋值操作之前。其返回值为boolean返回false时可以阻断属性赋值阶段continueWithPropertyPopulation false;。 BeanPostProcessor 关于BeanPostProcessor执行阶段的源码穿插在下文Aware接口的调用时机分析中因为部分Aware功能的就是通过他实现的!只需要先记住BeanPostProcessor在初始化前后调用就可以了。 接口源码 public interface BeanPostProcessor {//bean初始化之前调用Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}//bean初始化之后调用Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;} }第二大类只调用一次的接口 这一大类接口的特点是功能丰富常用于用户自定义扩展。 第二大类中又可以分为两类 Aware类型的接口 生命周期接口 无所不知的Aware Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。基本都能够见名知意Aware之前的名字就是可以拿到什么资源例如BeanNameAware可以拿到BeanName以此类推。调用时机需要注意所有的Aware方法都是在初始化阶段之前调用的 Aware接口众多这里同样通过分类的方式帮助大家记忆。 Aware接口具体可以分为两组至于为什么这么分详见下面的源码分析。如下排列顺序同样也是Aware接口的执行顺序能够见名知意的接口不再解释。 Aware Group1 BeanNameAwareBeanClassLoaderAwareBeanFactoryAware Aware Group2 EnvironmentAwareEmbeddedValueResolverAware 这个知道的人可能不多实现该接口能够获取Spring EL解析器用户的自定义注解需要支持spel表达式的时候可以使用非常方便。ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware) 这几个接口可能让人有点懵实际上这几个接口可以一起记其返回值实质上都是当前的ApplicationContext对象因为ApplicationContext是一个复合接口如下 public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver {} Aware调用时机源码分析 详情如下忽略了部分无关代码。代码位置就是我们上文提到的initializeBean方法详情这也说明了Aware都是在初始化阶段之前调用的 // 见名知意初始化阶段调用的方法 protected Object initializeBean(final String beanName, final Object bean, Nullable RootBeanDefinition mbd) {// 这里调用的是Group1中的三个Bean开头的AwareinvokeAwareMethods(beanName, bean);Object wrappedBean bean;// 这里调用的是Group2中的几个Aware// 而实质上这里就是前面所说的BeanPostProcessor的调用点// 也就是说与Group1中的Aware不同这里是通过BeanPostProcessorApplicationContextAwareProcessor实现的。wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);// 这个是初始化方法下文要介绍的InitializingBean调用点就是在这个方法里面invokeInitMethods(beanName, wrappedBean, mbd);// BeanPostProcessor的另一个调用点wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);return wrappedBean; } 可以看到并不是所有的Aware接口都使用同样的方式调用。Bean××Aware都是在代码中直接调用的而ApplicationContext相关的Aware都是通过BeanPostProcessor#postProcessBeforeInitialization()实现的。感兴趣的可以自己看一下ApplicationContextAwareProcessor这个类的源码就是判断当前创建的Bean是否实现了相关的Aware方法如果实现了会调用回调方法将资源传递给Bean。 至于Spring为什么这么实现应该没什么特殊的考量。也许和Spring的版本升级有关。基于对修改关闭对扩展开放的原则Spring对一些新的Aware采用了扩展的方式添加。 BeanPostProcessor的调用时机也能在这里体现包围住invokeInitMethods方法也就说明了在初始化阶段的前后执行。 关于Aware接口的执行顺序其实只需要记住第一组在第二组执行之前就行了。每组中各个Aware方法的调用顺序其实没有必要记有需要的时候点进源码一看便知。 简单的两个生命周期接口 至于剩下的两个生命周期接口就很简单了实例化和属性赋值都是Spring帮助我们做的能够自己实现的有初始化和销毁两个生命周期阶段。 InitializingBean接口 InitializingBean顾名思义是初始化Bean相关的接口。 接口定义 public interface InitializingBean {void afterPropertiesSet() throws Exception;}看方法名是在读完Properties文件之后执行的方法。afterPropertiesSet()方法是在初始化过程中被调用的。 InitializingBean 对应生命周期的初始化阶段在上面源码的invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。 有一点需要注意因为Aware方法都是执行在初始化方法之前所以可以在初始化方法中放心大胆的使用Aware接口获取的资源这也是我们自定义扩展Spring的常用方式。 除了实现InitializingBean接口之外还能通过注解PostConstruct或者xml配置的方式指定初始化方法init-method至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期只是实现方式不同我们一般只采用其中一种方式。 三种实现指定初始化方法的方法 使用PostConstruct注解该注解作用于void方法上 在配置文件中配置init-method方法 bean idstudent classcom.demo.Student init-methodinit2property namename value小明/propertyproperty nameage value20/propertyproperty nameschool refschool/property /bean将类实现InitializingBean接口 Component(student) public class Student implements InitializingBean{private String name;private int age;… }执行 Component(student) public class Student implements InitializingBean{private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}//1.使用postconstrtct注解PostConstructpublic void init(){System.out.println(执行 init方法);}//2.在xml配置文件中配置init-method方法public void init2(){System.out.println(执行init2方法 );}//3.实现InitializingBean接口public void afterPropertiesSet() throws Exception {System.out.println(执行init3方法);}} 通过测试我们可以得出结论三种实现方式的执行顺序是 Constructor PostConstruct InitializingBean init-method DisposableBean接口 DisposableBean 类似于InitializingBean对应生命周期的销毁阶段以ConfigurableApplicationContext#close()方法作为入口实现是通过循环获取所有实现了DisposableBean接口的Bean然后调用其destroy()方法 。 接口定义 public interface DisposableBean {void destroy() throws Exception; } 定义一个实现了DisposableBean接口的Bean public class IndexBean implements InitializingBean,DisposableBean {public void destroy() throws Exception {System.out.println(destroy);}public void afterPropertiesSet() throws Exception {System.out.println(init-afterPropertiesSet());}public void test(){System.out.println(init-test());} } 执行 public class Main {public static void main(String[] args) {AbstractApplicationContext applicationContextnew ClassPathXmlApplicationContext(classpath:application-usertag.xml);System.out.println(init-success);applicationContext.registerShutdownHook();} } 执行结果 init-afterPropertiesSet() init-test() init-success destroy也就是说在对象销毁的时候会去调用DisposableBean的destroy方法。在进入到销毁过程时先去调用一下DisposableBean的destroy方法然后后执行 destroy-method声明的方法用来销毁Bean中的各项数据。 扩展阅读: BeanPostProcessor注册时机与执行顺序 首先要明确一个概念在spring中一切皆bean 所有的组件都会被作为一个bean装配到spring容器中过程如下图 所以我们前面所讲的那些拓展点也都会被作为一个个bean装配到spring容器中 注册时机 我们知道BeanPostProcessor也会注册为Bean那么Spring是如何保证BeanPostProcessor在我们的业务Bean之前初始化完成呢 请看我们熟悉的refresh()方法的源码省略部分无关代码refresh的详细注解见refresh() public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1. 刷新前的预处理prepareRefresh();// 2. 获取 beanFactory即前面创建的【DefaultListableBeanFactory】ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// 3. 预处理 beanFactory向容器中添加一些组件prepareBeanFactory(beanFactory);try {// 4. 子类通过重写这个方法可以在 BeanFactory 创建并与准备完成以后做进一步的设置postProcessBeanFactory(beanFactory);// 5. 执行 BeanFactoryPostProcessor 方法beanFactory 后置处理器invokeBeanFactoryPostProcessors(beanFactory);// 6. 注册 BeanPostProcessorsbean 后置处理器registerBeanPostProcessors(beanFactory);// 7. 初始化 MessageSource 组件做国际化功能消息绑定消息解析initMessageSource();// 8. 初始化事件派发器在注册监听器时会用到initApplicationEventMulticaster();// 9. 留给子容器子类子类重写这个方法在容器刷新的时候可以自定义逻辑web 场景下会使用onRefresh();// 10. 注册监听器派发之前步骤产生的一些事件可能没有registerListeners();// 11. 初始化所有的非单实例 beanfinishBeanFactoryInitialization(beanFactory);// 12. 发布容器刷新完成事件finishRefresh();}...} }可以看出Spring是先执行registerBeanPostProcessors()进行BeanPostProcessors的注册然后再执行finishBeanFactoryInitialization创建我们的单例非懒加载的Bean。 执行顺序 BeanPostProcessor有很多个而且每个BeanPostProcessor都影响多个Bean其执行顺序至关重要必须能够控制其执行顺序才行。关于执行顺序这里需要引入两个排序相关的接口PriorityOrdered、Ordered PriorityOrdered是一等公民首先被执行PriorityOrdered公民之间通过接口返回值排序 Ordered是二等公民然后执行Ordered公民之间通过接口返回值排序 都没有实现是三等公民最后执行 在以下源码中可以很清晰的看到Spring注册各种类型BeanPostProcessor的逻辑根据实现不同排序接口进行分组。优先级高的先加入优先级低的后加入。 // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. // 首先加入实现了PriorityOrdered接口的BeanPostProcessors顺便根据PriorityOrdered排了序 String[] postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);} }sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear();// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. // 然后加入实现了Ordered接口的BeanPostProcessors顺便根据Ordered排了序 postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);} } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.// 最后加入其他常规的BeanPostProcessors boolean reiterate true; while (reiterate) {reiterate false;postProcessorNames beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear(); } 根据排序接口返回值排序默认升序排序返回值越低优先级越高。 /*** Useful constant for the highest precedence value.* see java.lang.Integer#MIN_VALUE*/ int HIGHEST_PRECEDENCE Integer.MIN_VALUE; /*** Useful constant for the lowest precedence value.* see java.lang.Integer#MAX_VALUE*/ int LOWEST_PRECEDENCE Integer.MAX_VALUE; PriorityOrdered、Ordered接口作为Spring整个框架通用的排序接口在Spring中应用广泛也是非常重要的接口。 Bean的生命周期流程图 总结 Spring Bean的生命周期分为四个阶段和多个扩展点。扩展点又可以分为影响多个Bean和影响单个Bean。整理如下 四个阶段 实例化 Instantiation 属性赋值 Populate 初始化 Initialization 销毁 Destruction 多个扩展点 影响多个Bean BeanPostProcessorInstantiationAwareBeanPostProcessor 影响单个Bean Aware Aware Group1 BeanNameAwareBeanClassLoaderAwareBeanFactoryAware Aware Group2 EnvironmentAwareEmbeddedValueResolverAwareApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware) 生命周期 InitializingBean DisposableBean 3.11、哪些是重要的bean生命周期方法 你能重载它们吗 有两个重要的bean 生命周期方法第一个是setup 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。 bean 标签有两个重要的属性init-method和destroy-method。用它们你可以自己定制初始化和注销方法。它们也有相应的注解PostConstruct和PreDestroy。 3.12、什么是Spring的内部bean什么是Spring inner beans 在Spring框架中当一个bean仅被用作另一个bean的属性时它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现内部bean通常是匿名的它们的Scope一般是prototype。 3.13、在 Spring中如何注入一个java集合 Spring提供以下几种集合的配置元素 类型用于注入一列值允许有相同的值。 类型用于注入一组值不允许有相同的值。 类型用于注入一组键值对键和值都可以为任意类型。 类型用于注入一组键值对键和值都只能为String类型。 3.14、什么是bean装配 装配或bean 装配是指在Spring 容器中把bean组装到一起前提是容器需要知道bean的依赖关系如何通过依赖注入来把它们装配到一起。 3.15、什么是bean的自动装配 在Spring框架中在配置文件中设定bean的依赖关系是一个很好的机制Spring 容器能够自动装配相互合作的bean这意味着容器不需要和配置能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上也可以设定在特定的bean上。 3.16、解释不同方式的自动装配spring 自动装配 bean 有哪些方式 在spring中对象无需自己查找或创建与其关联的其他对象由容器负责把需要相互协作的对象引用赋予各个对象使用autowire来配置自动装载模式。 在Spring框架xml配置中共有5种自动装配 no默认的方式是不进行自动装配的通过手工设置ref属性来进行装配bean。 byName通过bean的名称进行自动装配如果一个bean的 property 与另一bean 的name 相同就进行自动装配。 byType通过参数的数据类型进行自动装配。 constructor利用构造函数进行装配并且构造函数的参数通过byType进行装配。 autodetect自动探测如果有构造方法通过 construct的方式自动装配否则使用 byType的方式自动装配。 3.17、使用Autowired注解自动装配的过程是怎样的 使用Autowired注解来自动装配指定的bean。在使用Autowired注解之前需要在Spring配置文件进行配置context:annotation-config /。 在启动spring IoC时容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器当容器扫描到Autowied、Resource或Inject时就会在IoC容器自动查找需要的bean并装配给该对象的属性。在使用Autowired时首先在容器中查询对应类型的bean 如果查询结果刚好为一个就将该bean装配给Autowired指定的数据 如果查询的结果不止一个那么Autowired会根据名称来查找 如果上述查找的结果为空那么会抛出异常。解决方法时使用requiredfalse。 3.18、自动装配有哪些局限性 重写你仍需用 和 配置来定义依赖意味着总要重写自动装配。 基本数据类型你不能自动装配简单的属性如基本数据类型String字符串和类。 模糊特性自动装配不如显式装配精确如果有可能建议使用显式装配。 3.19、你可以在Spring中注入一个null 和一个空字符串吗 可以。 3.20、FactoryBean 和 BeanFactory有什么区别 简要的答案 BeanFactory 是 Bean 的工厂 ApplicationContext 的父类IOC 容器的核心负责生产和管理 Bean 对象。 FactoryBean 是 Bean可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑通过代理一个Bean对象对方法前后做一些操作。 具体的介绍 1 BeanFactory 是ioc容器的底层实现接口是ApplicationContext 顶级接口 spring不允许我们直接操作 BeanFactory bean工厂所以为我们提供了ApplicationContext 这个接口 此接口集成BeanFactory 接口ApplicationContext包含BeanFactory的所有功能,同时还进行更多的扩展。 BeanFactory 接口又衍生出以下接口其中我们经常用到的是ApplicationContext 接口 ApplicationContext 继承图 ConfiguableApplicationContext 中添加了一些方法 ... 其他省略//刷新ioc容器上下文void refresh() throws BeansException, IllegalStateException;// 关闭此应用程序上下文释放所有资源并锁定销毁所有缓存的单例bean。Overridevoid close();//确定此应用程序上下文是否处于活动状态即是否至少刷新一次且尚未关闭。boolean isActive();... 其他省略 主要作用在ioc容器进行相应的刷新关闭等操作 FileSystemXmlApplicationContext 和ClassPathXmlApplicationContext 是用来读取xml文件创建bean对象 ClassPathXmlApplicationContext 读取类路径下xml 创建bean FileSystemXmlApplicationContext 读取文件系统下xml创建bean AnnotationConfigApplicationContext 主要是注解开发获取ioc中的bean实例 2 FactoryBean 是spirng提供的工厂bean的一个接口 FactoryBean 接口提供三个方法用来创建对象 FactoryBean 具体返回的对象是由getObject 方法决定的。 */ public interface FactoryBeanT {//创建的具体bean对象的类型NullableT getObject() throws Exception;//工厂bean 具体创建具体对象是由此getObject()方法来返回的NullableClass? getObjectType();//是否单例default boolean isSingleton() {return true;}} 创建一个FactoryBean 用来生产User对象 Component public class FactoryBeanTest implements FactoryBeanUser {//创建的具体bean对象的类型Overridepublic Class? getObjectType() {return User.class;}//是否单例Overridepublic boolean isSingleton() {return true;}//工厂bean 具体创建具体对象是由此getObject()方法来返回的Overridepublic User getObject() throws Exception {return new User();} } Junit测试 RunWith(SpringRunner.class) SpringBootTest(classes {FactoryBeanTest.class}) WebAppConfiguration public class SpringBootDemoApplicationTests {Autowiredprivate ApplicationContext applicationContext;Testpublic void tesst() {FactoryBeanTest bean1 applicationContext.getBean(FactoryBeanTest.class);try {User object bean1.getObject();System.out.println(objectobject);System.out.println(object);} catch (Exception e) {e.printStackTrace();}} } 结果 true User [idnull, namenull, age0]简单的总结 BeanFactory是个bean 工厂是一个工厂类(接口) 它负责生产和管理bean的一个工厂 是ioc 容器最底层的接口是个ioc容器是spring用来管理和装配普通bean的ioc容器这些bean成为普通bean。FactoryBean是个bean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式是一个可以生产对象和装饰对象的工厂bean由spring管理后生产的对象是由getObject()方法决定的从容器中获取到的对象不是 “ FactoryBeanTest ” 对象。 3.21、Spring 如何解决循环依赖 循环依赖的简单例子 比如几个Bean之间的互相引用 甚至自己“循环”依赖自己 原型(Prototype)的场景是不支持循环依赖的 先说明前提原型(Prototype)的场景是不支持循环依赖的. 单例的场景才能存在循环依赖 原型(Prototype)的场景通常会走到AbstractBeanFactory类中下面的判断抛出异常。 if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName); } 原因很好理解创建新的A时发现要注入原型字段B又创建新的B发现要注入原型字段A… 这就套娃了, Spring就先抛出了BeanCurrentlyInCreationException 什么是原型(Prototype)的场景 通过如下方式可以将该类的bean设置为原型模式 Service Scope(prototype) public class MyReportExporter extends AbstractReportExporter{... } 在Spring中Service默认都是单例的。用了私有全局变量若不想影响下次请求就需要用到原型模式即Scope(“prototype”) 所谓单例就是Spring的IOC机制只创建该类的一个实例每次请求都会用这同一个实例进行处理因此若存在全局变量本次请求的值肯定会影响下一次请求时该变量的值。 原型模式指的是每次调用时会重新创建该类的一个实例比较类似于我们自己自己new的对象实例。 具体例子循环依赖的代码片段 我们先看看当时出问题的代码片段 Service publicclass TestService1 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {} } Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } 这两段代码中定义了两个Service类TestService1和TestService2在TestService1中注入了TestService2的实例同时在TestService2中注入了TestService1的实例这里构成了循环依赖。 只不过这不是普通的循环依赖因为TestService1的test1方法上加了一个Async注解。 大家猜猜程序启动后运行结果会怎样 org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name testService1: Bean with name testService1 has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using getBeanNamesOfType with the allowEagerInit flag turned off, for example. 报错了。。。原因是出现了循环依赖。 「不科学呀spring不是号称能解决循环依赖问题吗怎么还会出现」 如果把上面的代码稍微调整一下 Service publicclass TestService1 {Autowiredprivate TestService2 testService2;public void test1() {} } 把TestService1的test1方法上的Async注解去掉TestService1和TestService2都需要注入对方的实例同样构成了循环依赖。 但是重新启动项目发现它能够正常运行。这又是为什么 带着这两个问题让我们一起开始spring循环依赖的探秘之旅。 什么是循环依赖 循环依赖说白是一个或多个对象实例之间存在直接或间接的依赖关系这种依赖关系构成了一个环形调用。 第一种情况自己依赖自己的直接依赖 第二种情况两个对象之间的直接依赖 第三种情况多个对象之间的间接依赖 前面两种情况的直接循环依赖比较直观非常好识别但是第三种间接循环依赖的情况有时候因为业务代码调用层级很深不容易识别出来。 循环依赖的N种场景 spring中出现循环依赖主要有以下场景 场景1单例的setter注入 这种注入方式应该是spring用的最多的代码如下 Service publicclass TestService1 {Autowiredprivate TestService2 testService2;public void test1() {} } Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } 这是一个经典的循环依赖但是它能正常运行得益于spring的内部机制让我们根本无法感知它有问题因为spring默默帮我们解决了。 spring内部有三级缓存 singletonObjects 一级缓存用于保存实例化、注入、初始化完成的bean实例 earlySingletonObjects 二级缓存用于保存实例化完成的bean实例 singletonFactories 三级缓存用于保存bean创建工厂以便于后面扩展有机会创建代理对象。 下面用一张图告诉你spring是如何解决循环依赖的 细心的朋友可能会发现在这种场景中第二级缓存作用不大。 那么问题来了为什么要用第二级缓存呢 试想一下如果出现以下这种情况我们要如何处理 Service publicclass TestService1 {Autowiredprivate TestService2 testService2;Autowiredprivate TestService3 testService3;public void test1() {} } Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } Service publicclass TestService3 {Autowiredprivate TestService1 testService1;public void test3() {} } TestService1依赖于TestService2和TestService3而TestService2依赖于TestService1同时TestService3也依赖于TestService1。 按照上图的流程可以把TestService1注入到TestService2并且TestService1的实例是从第三级缓存中获取的。 假设不用第二级缓存TestService1注入到TestService3的流程如图 TestService1注入到TestService3又需要从第三级缓存中获取实例而第三级缓存里保存的并非真正的实例对象而是ObjectFactory对象。 说白了两次从三级缓存中获取都是ObjectFactory对象而通过它创建的实例对象每次可能都不一样的。 这样不是有问题 为了解决这个问题spring引入的第二级缓存。前一个图其实TestService1对象的实例已经被添加到第二级缓存中了而在TestService1注入到TestService3时只用从第二级缓存中获取该对象即可。 还有个问题第三级缓存中为什么要添加ObjectFactory对象直接保存实例对象不行吗 答不行因为假如你想对添加到三级缓存中的实例对象进行增强直接用实例对象是行不通的。 针对这种场景spring是怎么做的呢 答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中 它定义了一个匿名内部类通过getEarlyBeanReference方法获取代理对象其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。 场景二多例的setter注入 这种注入方法偶然会有特别是在多线程的场景下具体代码如下 Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) Service publicclass TestService1 {Autowiredprivate TestService2 testService2;public void test1() {} } Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } 很多人说这种情况spring容器启动会报错其实是不对的我非常负责任的告诉你程序能够正常启动。 为什么呢 其实在AbstractApplicationContext类的refresh方法中告诉了我们答案它会调用finishBeanFactoryInitialization方法该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法 标红的地方明显能够看出非抽象、单例 并且非懒加载的类才能被提前初始bean。 而多例即SCOPE_PROTOTYPE类型的类非单例不会被提前初始化bean所以程序能够正常启动。 如何让他提前初始化bean呢 只需要再定义一个单例的类在它里面注入TestService1 Service publicclass TestService3 {Autowiredprivate TestService1 testService1; } 重新启动程序执行结果 Requested bean is currently in creation: Is there an unresolvable circular reference?果然出现了循环依赖。 注意这种循环依赖问题是无法解决的因为它没有用缓存每次都会生成一个新对象。 场景三构造器注入 这种注入方式现在其实用的已经非常少了但是我们还是有必要了解一下看看如下代码 Service publicclass TestService1 {public TestService1(TestService2 testService2) {} } Service publicclass TestService2 {public TestService2(TestService1 testService1) {} } 运行结果 Requested bean is currently in creation: Is there an unresolvable circular reference?出现了循环依赖为什么呢 从图中的流程看出构造器注入没能添加到三级缓存也没有使用缓存所以也无法解决循环依赖问题。 场景四单例的代理对象setter注入 这种注入方式其实也比较常用比如平时使用Async注解的场景会通过AOP自动生成代理对象。 我那位同事的问题也是这种情况。 Service publicclass TestService1 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {} } Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } 从前面得知程序启动会报错出现了循环依赖 org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name testService1: Bean with name testService1 has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using getBeanNamesOfType with the allowEagerInit flag turned off, for example. 为什么会循环依赖呢 答案就在下面这张图中 说白了bean初始化完成之后后面还有一步去检查第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要所以前面的流程图中省略了但是在这里是关键点我们重点说说 那位同事的问题正好是走到这段代码发现第二级缓存 和 原始对象不相等所以抛出了循环依赖的异常。 如果这时候把TestService1改个名字改成TestService6其他的都不变。 Service publicclass TestService6 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {} } 再重新启动一下程序神奇般的好了。 what 这又是为什么 这就要从spring的bean加载顺序说起了默认情况下spring是按照文件完整路径递归查找的按路径文件名排序排在前面的先加载。所以TestService1比TestService2先加载而改了文件名称之后TestService2比TestService6先加载。 为什么TestService2比TestService6先加载就没问题呢 答案在下面这张图中 这种情况testService6中其实第二级缓存是空的不需要跟原始对象判断所以不会抛出循环依赖。 场景5DependsOn循环依赖 还有一种有些特殊的场景比如我们需要在实例化Bean A之前先实例化Bean B这个时候就可以使用DependsOn注解。 DependsOn(value testService2) Service publicclass TestService1 {Autowiredprivate TestService2 testService2;public void test1() {} } DependsOn(value testService1) Service publicclass TestService2 {Autowiredprivate TestService1 testService1;public void test2() {} } 程序启动之后执行结果 Circular depends-on relationship between testService2 and testService1这个例子中本来如果TestService1和TestService2都没有加DependsOn注解是没问题的反而加了这个注解会出现循环依赖问题。 这又是为什么 答案在AbstractBeanFactory类的doGetBean方法的这段代码中 它会检查dependsOn的实例有没有循环依赖如果有循环依赖则抛异常。 总体策略出现循环依赖如何解决 项目中如果出现循环依赖问题说明是spring默认无法解决的循环依赖要看项目的打印日志属于哪种循环依赖。目前包含下面几种情况 生成代理对象产生的循环依赖 的解决方案 这类循环依赖问题解决方法很多主要有 使用Lazy注解延迟加载 使用DependsOn注解指定加载先后关系 修改文件名称改变循环依赖类的加载顺序 使用DependsOn产生的循环依赖 的解决方案 这类循环依赖问题要找到DependsOn注解循环依赖的地方迫使它不循环依赖就可以解决问题。 多例循环依赖 的解决方案 这类循环依赖问题可以通过把bean改成单例的解决。 构造器循环依赖 的解决方案 这类循环依赖问题可以通过使用Lazy注解解决 3.22、Spring是怎么解决循环依赖的 首先Spring 解决循环依赖有两个前提条件 不全是构造器方式的循环依赖必须是单例 基于上面的问题我们知道Bean的生命周期本质上解决循环依赖的问题就是三级缓存通过三级缓存提前拿到未初始化的对象。 第一级缓存用来保存实例化、初始化都完成的对象 第二级缓存用来保存实例化完成但是未初始化完成的对象 第三级缓存用来保存一个对象工厂提供一个匿名内部类用于创建二级缓存中的对象 假设一个简单的循环依赖场景A、B互相依赖。 A对象的创建过程 创建对象A实例化的时候把A对象工厂放入三级缓存 A注入属性时发现依赖B转而去实例化B 同样创建对象B注入属性时发现依赖A一次从一级到三级缓存查询A从三级缓存通过对象工厂拿到A把A放入二级缓存同时删除三级缓存中的A此时B已经实例化并且初始化完成把B放入一级缓存。 接着继续创建A顺利从一级缓存拿到实例化且初始化完成的B对象A对象创建也完成删除二级缓存中的A同时把A放入一级缓存 最后一级缓存中保存着实例化、初始化都完成的A、B对象 因此由于把实例化和初始化的流程分开了所以如果都是用构造器的话就没法分离这个操作所以都是构造器的话就无法解决循环依赖的问题了。 3.23、为什么要三级缓存二级不行吗 不可以主要是为了生成代理对象。 因为三级缓存中放的是生成具体对象的匿名内部类他可以生成代理对象也可以是普通的实例对象。 使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。 假设只有二级缓存的情况往二级缓存中放的显示一个普通的Bean对象BeanPostProcessor去生成代理对象之后覆盖掉二级缓存中的普通Bean对象那么多线程环境下可能取到的对象就不一致了。 四、Spring注解 4.1、什么是基于Java的Spring注解配置? 给一些注解的例子 基于Java的配置允许你在少量的Java注解的帮助下进行你的大部分Spring配置而非通过XML文件。 以Configuration 注解为例它用来标记类可以当做一个bean的定义被Spring IOC容器使用。 另一个例子是Bean注解它表示此方法将要返回一个对象作为一个bean注册进Spring应用上下文。 Configuration public class StudentConfig {Beanpublic StudentBean myStudent() {return new StudentBean();} } 4.2、怎样开启注解装配 注解装配在默认情况下是不开启的为了使用注解装配我们必须在Spring配置文件中配置 context:annotation-config/元素。 4.3、Component, Controller, Repository, Service 有何区别 Component这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。 Controller这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。 Service此注解是组件注解的特化。它不会对 Component 注解提供任何其他行为。您可以在服务层类中使用 Service 而不是 Component因为它以更好的方式指定了意图。 Repository这个注解是具有类似用途和功能的 Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器并使未经检查的异常有资格转换为 Spring DataAccessException。 4.4、Required 注解有什么作用 这个注解表明bean的属性必须在配置的时候设置通过一个bean定义的显式的属性值或通过自动装配若Required注解的bean属性未被设置容器将抛出BeanInitializationException。示例 public class Employee {private String name;Requiredpublic void setName(String name){this.namename;}public string getName(){return name;} } 4.5、Autowired 注解有什么作用 Autowired默认是按照类型装配注入的默认情况下它要求依赖对象必须存在可以设置它required属性为false。Autowired 注解提供了更细粒度的控制包括在何处以及如何完成自动装配。它的用法和Required一样修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。 public class Employee {private String name;Autowiredpublic void setName(String name) {this.namename;}public string getName(){return name;} } 4.6、Autowired和Resource之间的区别 Autowired可用于构造函数、成员变量、Setter方法 Autowired和Resource之间的区别 Autowired默认是按照类型装配注入的默认情况下它要求依赖对象必须存在可以设置它required属性为false。 Resource默认是按照名称来装配注入的只有当找不到与名称匹配的bean才会按照类型来装配注入。 4.7、Qualifier 注解有什么作用 当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时您可以使用Qualifier 注解和 Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。 4.8、RequestMapping 注解有什么用 RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。此注释可应用于两个级别 类级别映射请求的 URL 方法级别映射 URL 以及 HTTP 请求方法 五、Spring数据访问 5.1、解释对象/关系映射集成模块 Spring 通过提供ORM模块支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具Spring 支持集成主流的ORM框架如HiberateJDO和 iBATISJPATopLinkJDOOJB 。Spring的事务管理同样支持以上所有ORM框架及JDBC。 5.2、在Spring框架中如何更有效地使用JDBC 使用Spring JDBC 框架资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用这个模板叫JdbcTemplate 5.3、解释JDBC抽象和DAO模块 通过使用JDBC抽象和DAO模块保证数据库代码的简洁并能避免数据库资源错误关闭导致的问题它在各种不同的数据库的错误信息之上提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。 5.4、spring DAO 有什么用 Spring DAO数据访问对象 使得 JDBCHibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时无需考虑捕获每种技术不同的异常。 5.5、spring JDBC API 中存在哪些类 JdbcTemplate SimpleJdbcTemplate NamedParameterJdbcTemplate SimpleJdbcInsert SimpleJdbcCall 5.6、JdbcTemplate是什么 JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象执行写好的或可调用的数据库操作语句提供自定义的数据错误处理。 5.7、使用Spring通过什么方式访问Hibernate使用 Spring 访问 Hibernate 的方法有哪些 在Spring中有两种方式访问Hibernate 使用 Hibernate 模板和回调进行控制反转 扩展 HibernateDAOSupport 并应用 AOP 拦截器节点 5.8、如何通过HibernateDaoSupport将Spring和Hibernate结合起来 用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步 配置the Hibernate SessionFactory 继承HibernateDaoSupport实现一个DAO 在AOP支持的事务中装配 5.9、Spring支持的事务管理类型 spring 事务实现方式有哪些 Spring支持两种类型的事务管理 编程式事务管理这意味你通过编程的方式管理事务给你带来极大的灵活性但是难维护。 声明式事务管理这意味着你可以将业务代码和事务管理分离你只需用注解和XML配置来管理事务。 5.10、Spring事务的实现方式和实现原理 Spring事务的本质其实就是数据库对事务的支持没有数据库的事务支持spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。 5.11、说一下Spring的事务传播行为 spring事务的传播行为说的是当多个事务同时存在的时候spring如何处理这些事务的行为。 ① PROPAGATION_REQUIRED如果当前没有事务就创建一个新事务如果当前存在事务就加入该事务该设置是最常用的设置。 ② PROPAGATION_SUPPORTS支持当前事务如果当前存在事务就加入该事务如果当前不存在事务就以非事务执行。 ③ PROPAGATION_MANDATORY支持当前事务如果当前存在事务就加入该事务如果当前不存在事务就抛出异常。 ④ PROPAGATION_REQUIRES_NEW创建新事务无论当前存不存在事务都创建新事务。 ⑤ PROPAGATION_NOT_SUPPORTED以非事务方式执行操作如果当前存在事务就把当前事务挂起。 ⑥ PROPAGATION_NEVER以非事务方式执行如果当前存在事务则抛出异常。 ⑦ PROPAGATION_NESTED如果当前存在事务则在嵌套事务内执行。如果当前没有事务则按REQUIRED属性执行。 5.12、说一下 spring 的事务隔离 spring 有五大隔离级别默认值为 ISOLATION_DEFAULT使用数据库的设置其他四个隔离级别和数据库的隔离级别一致 ISOLATION_DEFAULT用底层数据库的设置隔离级别数据库设置的是什么我就用什么 ISOLATION_READ_UNCOMMITTED未提交读最低隔离级别、事务未提交前就可被其他事务读取会出现幻读、脏读、不可重复读 ISOLATION_READ_COMMITTED提交读一个事务提交后才能被其他事务读取到会造成幻读、不可重复读SQL server 的默认级别 ISOLATION_REPEATABLE_READ可重复读保证多次读取同一个数据时其值都和事务开始时候的内容是一致禁止读取到别的事务未提交的数据会造成幻读MySQL 的默认级别 ISOLATION_SERIALIZABLE序列化代价最高最可靠的隔离级别该隔离级别能防止脏读、不可重复读、幻读。 脏读 表示一个事务能够读取另一个事务中还未提交的数据。比如某个事务尝试插入记录 A此时该事务还未提交然后另一个事务尝试读取到了记录 A。 不可重复读 是指在一个事务内多次读同一数据。 幻读 指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录但是第二次同等条件下查询却有 n1 条记录这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据同一个记录的数据内容被修改了所有数据行的记录就变多或者变少了。 5.13、Spring框架的事务管理有哪些优点 为不同的事务API 如 JTAJDBCHibernateJPA 和JDO提供一个不变的编程模式。 为编程式事务管理提供了一套简单的API而不是一些复杂的事务API 支持声明式事务管理。 和Spring各种数据访问抽象层很好得集成。 5.14、你更倾向用那种事务管理类型 大多数Spring框架的用户选择声明式事务管理因为它对应用代码的影响最小因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理虽然比编程式事务管理这种方式允许你通过代码控制事务少了一点灵活性。唯一不足地方是最细粒度只能作用到方法级别无法做到像编程式事务那样可以作用到代码块级别。 六、Spring面向切面编程(AOP) 6.1、什么是AOP OOP(Object-Oriented Programming)面向对象编程允许开发者定义纵向的关系但并适用于定义横向的关系导致了大量代码的重复而不利于各个模块的重用。 AOP(Aspect-Oriented Programming)一般称为面向切面编程作为面向对象的一种补充用于将那些与业务无关但却对多个对象产生影响的公共行为和逻辑抽取并封装为一个可重用的模块这个模块被命名为“切面”Aspect减少系统中的重复代码降低了模块间的耦合度同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。 6.2、Spring AOP and AspectJ AOP 有什么区别AOP 有哪些实现方式 AOP实现的关键在于 代理模式AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ动态代理则以Spring AOP为代表。 1AspectJ是静态代理的增强所谓静态代理就是AOP框架会在编译阶段生成AOP代理类因此也称为编译时增强他会在编译阶段将AspectJ(切面)织入到Java字节码中运行的时候就是增强之后的AOP对象。 2Spring AOP使用的动态代理所谓的动态代理就是说AOP框架不会去修改字节码而是每次运行时在内存中临时为方法生成一个AOP对象这个AOP对象包含了目标对象的全部方法并且在特定的切点做了增强处理并回调原对象的方法。 6.3、JDK动态代理和CGLIB动态代理的区别 Spring AOP中的动态代理主要有两种方式JDK动态代理和CGLIB动态代理 JDK动态代理只提供接口的代理不支持类的代理。核心InvocationHandler接口和Proxy类InvocationHandler 通过invoke()方法反射来调用目标类中的代码动态地将横切逻辑和业务编织在一起接着Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。 如果代理类没有实现 InvocationHandler 接口那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIBCode Generation Library是一个代码生成的类库可以在运行时动态的生成指定类的一个子类对象并覆盖其中特定方法并添加增强代码从而实现AOP。CGLIB是通过继承的方式做的动态代理因此如果某个类被标记为final那么它是无法使用CGLIB做动态代理的。 静态代理与动态代理区别在于生成AOP代理对象的时机不同相对来说AspectJ的静态代理方式具有更好的性能但是AspectJ需要特定的编译器进行处理而Spring AOP则无需特定的编译器处理。 InvocationHandler 的 invoke(Object proxy,Method method,Object[] args)proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。 6.4、如何理解 Spring 中的代理 将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下目标对象和代理对象是相同的。 Advice Target Object Proxy 6.5、解释一下Spring AOP里面的几个名词 1切面Aspect切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中切面可以使用通用类基于模式的风格 或者在普通类中以 AspectJ 注解来实现。 2连接点Join point指方法在Spring AOP中一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中并添加新的行为。 3通知Advice在AOP术语中切面的工作被称为通知。 4切入点Pointcut切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 5引入Introduction引入允许我们向现有类添加新方法或属性。 6目标对象Target Object 被一个或者多个切面aspect所通知advise的对象。它通常是一个代理对象。也有人把它叫做 被通知adviced 对象。 既然Spring AOP是通过运行时代理实现的这个对象永远是一个 被代理proxied 对象。 7织入Weaving织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入 编译期切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。 类加载期切面在目标类加载到JVM时被织入。需要特殊的类加载器它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。 运行期切面在应用运行的某个时刻被织入。一般情况下在织入切面时AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。 6.6、Spring在运行时通知对象 通过在代理类中包裹切面Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类并拦截被通知方法的调用再把调用转发给真正的目标bean。当代理拦截到方法调用时在调用目标bean方法之前会执行切面逻辑。 直到应用需要被代理的bean时Spring才创建代理对象。如果使用的是ApplicationContext的话在ApplicationContext从BeanFactory中加载所有bean的时候Spring才会创建被代理的对象。因为Spring运行时才创建代理对象所以我们不需要特殊的编译器来织入SpringAOP的切面。 6.7、Spring只支持方法级别的连接点 因为Spring基于动态代理所以Spring只支持方法连接点。Spring缺少对字段连接点的支持而且它不支持构造器连接点。方法之外的连接点拦截功能我们可以利用Aspect来补充。 6.8、在Spring AOP 中关注点和横切关注的区别是什么在 spring aop 中 concern 和 cross-cutting concern 的不同之处 关注点concern是应用中一个模块的行为一个关注点可能会被定义成一个我们想实现的一个功能。 横切关注点cross-cutting concern是一个关注点此关注点是整个应用都会使用的功能并影响整个应用比如日志安全和数据传输几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。 6.9、Spring通知有哪些类型 在AOP术语中切面的工作被称为通知实际上是程序执行时要通过SpringAOP框架触发的代码段。 Spring切面可以应用5种类型的通知 前置通知Before在目标方法被调用之前调用通知功能后置通知After在目标方法完成之后调用通知此时不会关心方法的输出是什么返回通知After-returning 在目标方法成功执行之后调用通知异常通知After-throwing在目标方法抛出异常后调用通知环绕通知Around通知包裹了被通知的方法在被通知的方法调用之前和调用之后执行自定义的行为。 同一个aspect不同advice的执行顺序 ①没有异常情况下的执行顺序 around before advice before advice target method 执行 around after advice after advice afterReturning ②有异常情况下的执行顺序 around before advice before advice target method 执行 around after advice after advice afterThrowing:异常发生 java.lang.RuntimeException: 异常发生 6.10、什么是切面 Aspect aspect 由 pointcount 和 advice 组成切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作: 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上 如何在 advice 中编写切面代码. 可以简单地认为, 使用 Aspect 注解的类就是切面. 6.11、解释基于XML Schema方式的切面实现 在这种情况下切面由常规类以及基于XML的配置实现。 6.12、解释基于注解的切面实现 在这种情况下(基于AspectJ的实现)涉及到的切面声明的风格与带有java5标注的普通java类一致。 6.13、有几种不同类型的自动代理 BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator Metadata autoproxying 七、Spring MVC面试题 专题部分 7.1、什么是Spring MVC简单介绍下你对Spring MVC的理解 Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架通过把模型-视图-控制器分离将web层进行职责解耦把复杂的web应用分成逻辑清晰的几部分简化开发减少出错方便组内开发人员之间的配合。 7.2、Spring MVC的优点 1可以支持各种视图技术,而不仅仅局限于JSP 2与Spring框架集成如IoC容器、AOP等 3清晰的角色分配前端控制器(dispatcherServlet) , 请求到处理器映射handlerMapping), 处理器适配器HandlerAdapter), 视图解析器ViewResolver。 4 支持各种请求资源的映射策略。 7.3、Spring MVC的主要组件 1前端控制器 DispatcherServlet不需要程序员开发 作用接收请求、响应结果相当于转发器有了DispatcherServlet 就减少了其它组件之间的耦合度。 2处理器映射器HandlerMapping不需要程序员开发 作用根据请求的URL来查找Handler 3处理器适配器HandlerAdapter 注意在编写Handler的时候要按照HandlerAdapter要求的规则去编写这样适配器HandlerAdapter才可以正确的去执行Handler。 4处理器Handler需要程序员开发 5视图解析器 ViewResolver不需要程序员开发 作用进行视图的解析根据视图逻辑名解析成真正的视图view 6视图View需要程序员开发jsp View是一个接口 它的实现类支持不同的视图类型jspfreemarkerpdf等等 7.4、什么是DispatcherServlet Spring的MVC框架是围绕DispatcherServlet来设计的它用来处理所有的HTTP请求和响应。 7.5、什么是Spring MVC框架的控制器 控制器提供一个访问应用程序的行为此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层允许用户创建多种用途的控制器。 7.6、Spring MVC的控制器是不是单例模式,如果是,有什么问题,怎么解决 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。 7.7、请描述Spring MVC的工作流程描述一下 DispatcherServlet 的工作流程 1用户发送请求至前端控制器DispatcherServlet 2 DispatcherServlet收到请求后调用HandlerMapping处理器映射器请求获取Handle 3处理器映射器根据请求url找到具体的处理器生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet 4DispatcherServlet 调用 HandlerAdapter处理器适配器 5HandlerAdapter 经过适配调用 具体处理器(Handler也叫后端控制器) 6Handler执行完成返回ModelAndView 7HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet 8DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析 9ViewResolver解析后返回具体View 10DispatcherServlet对View进行渲染视图即将模型数据填充至视图中 11DispatcherServlet响应用户。 7.8、MVC是什么MVC设计模式的好处有哪些 mvc是一种设计模式设计模式就是日常开发中编写代码的一种好的方法和经验的总结。模型model-视图view-控制器controller三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。 mvc设计模式的好处 1.分层设计实现了业务系统各个组件之间的解耦有利于业务系统的可扩展性可维护性。 2.有利于系统的并行开发提升开发效率。 7.9、注解原理是什么 注解本质是一个继承了Annotation的特殊接口其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。 7.10、Spring MVC常用的注解有哪些 RequestMapping用于处理请求 url 映射的注解可用于类或方法上。用于类上则表示类中的所有响应请求的方法都是以该地址作为父路径。 RequestBody注解实现接收http请求的json数据将json转换为java对象。 ResponseBody注解实现将conreoller方法返回对象转化为json对象响应给客户。 7.11、SpingMvc中的控制器的注解一般用哪个,有没有别的注解可以替代 一般用Controller注解,也可以使用RestController,RestController注解相当于ResponseBody Controller,表示是表现层,除此之外一般不用别的注解代替。 7.12、Controller注解的作用 在Spring MVC 中控制器Controller 负责处理由DispatcherServlet 分发的请求它把用户请求的数据经过业务处理层处理之后封装成一个Model 然后再把该Model 返回给对应的View 进行展示。在Spring MVC 中提供了一个非常简便的定义Controller 的方法你无需继承特定的类或实现特定的接口只需使用Controller 标记一个类是Controller 然后使用RequestMapping 和RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象它们可以通过Controller 的方法参数灵活的获取到。 Controller 用于标记在一个类上使用它标记的类就是一个Spring MVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法并检测该方法是否使用了RequestMapping 注解。Controller 只是定义了一个控制器类而使用RequestMapping 注解的方法才是真正处理请求的处理器。单单使用Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式 在Spring MVC 的配置文件中定义MyController 的bean 对象。 在Spring MVC 的配置文件中告诉Spring 该到哪里去找标记为Controller 的Controller 控制器。 7.13、RequestMapping注解的作用 RequestMapping是一个用来处理请求地址映射的注解可用于类或方法上。用于类上表示类中的所有响应请求的方法都是以该地址作为父路径。 RequestMapping注解有六个属性下面我们把她分成三类进行说明下面有相应示例。 value method value 指定请求的实际地址指定的地址可以是URI Template 模式后面将会说明 method 指定请求的method类型 GET、POST、PUT、DELETE等 consumesproduces consumes 指定处理请求的提交内容类型Content-Type例如application/json, text/html; produces: 指定返回的内容类型仅当request请求头中的(Accept)类型中包含该指定类型才返回 paramsheaders params 指定request中必须包含某些参数值是才让该方法处理。 headers 指定request中必须包含某些指定的header值才能让该方法处理请求。 7.14、ResponseBody注解的作用 该注解用于将Controller的方法返回的对象通过适当的HttpMessageConverter转换为指定格式后写入到Response对象的body数据区。 使用时机返回的数据不是html标签的页面而是其他某种格式的数据时如json、xml等使用 7.15、PathVariable和RequestParam的区别 请求路径上有个id的变量值可以通过PathVariable来获取 RequestMapping(value “/page/{id}”, method RequestMethod.GET) RequestParam用来获得静态的URL请求入参 spring注解时action里用到。 7.16、Spring MVC与Struts2区别 相同点 都是基于mvc的表现层框架都用于web项目的开发。 不同点 1.前端控制器不一样。Spring MVC的前端控制器是servletDispatcherServlet。struts2的前端控制器是filterStrutsPreparedAndExcutorFilter。 2.请求参数的接收方式不一样。Spring MVC是使用方法的形参接收请求的参数基于方法的开发线程安全可以设计为单例或者多例的开发推荐使用单例模式的开发执行效率更高默认就是单例开发模式。struts2是通过类的成员变量接收请求的参数是基于类的开发线程不安全只能设计为多例的开发。 3.Struts采用值栈存储请求和响应的数据通过OGNL存取数据Spring MVC通过参数解析器是将request请求内容解析并给方法形参赋值将数据和视图封装成ModelAndView对象最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 4.与spring整合不一样。Spring MVC是spring框架的一部分不需要整合。在企业项目中Spring MVC使用更多一些。 7.17、Spring MVC怎么样设定重定向和转发的 1转发在返回值前面加forward:“例如forward:user.do?namemethod4” 2重定向在返回值前面加redirect:“例如redirect:http://www.baidu.com” 7.18、Spring MVC怎么和AJAX相互调用的 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 1加入Jackson.jar 2在配置文件中配置json的映射 3在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上ResponseBody注解。 7.19、如何解决POST请求中文乱码问题GET的又如何处理呢 1解决post请求乱码问题 在web.xml中配置一个CharacterEncodingFilter过滤器设置成utf-8 filterfilter-nameCharacterEncodingFilter/filter-namefilter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-classinit-paramparam-nameencoding/param-nameparam-valueutf-8/param-value/init-param /filterfilter-mappingfilter-nameCharacterEncodingFilter/filter-nameurl-pattern/*/url-pattern /filter-mapping 2get请求中文参数出现乱码解决方法有两个 ①修改tomcat配置文件添加编码与工程编码一致如下 ConnectorURIEncodingutf-8 connectionTimeout20000 port8080 protocolHTTP/1.1 redirectPort8443/②另外一种方法对参数进行重新编码 String userName new String(request.getParamter(“userName”).getBytes(“ISO8859-1”),“utf-8”) ISO8859-1是tomcat默认编码需要将tomcat编码后的内容按utf-8编码。 7.20、Spring MVC的异常处理 可以将异常抛给Spring框架由Spring框架来处理我们只需要配置简单的异常处理器在异常处理器中添视图页面即可。 7.21、如果在拦截请求中我想拦截get方式提交的方法,怎么配置 可以在RequestMapping注解里面加上methodRequestMethod.GET。 7.22、怎样在方法里面得到Request,或者Session 直接在方法的形参中声明request,Spring MVC就自动把request对象传入。 7.23、如果想在拦截的方法里面得到从前台传入的参数,怎么得到 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 7.24、如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象 直接在方法中声明这个对象,Spring MVC就自动会把属性赋值到这个对象里面。 7.25、Spring MVC中函数的返回值是什么 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的但一般用String比较好。 7.26、Spring MVC用什么对象从后台向前台传递数据的 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。 7.27、怎么样把ModelMap里面的数据放入Session里面 可以在类上面加上SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 7.28、Spring MVC里面拦截器是怎么写的 有两种写法,一种是实现HandlerInterceptor接口另外一种是继承适配器类接着在接口方法当中实现处理逻辑然后在Spring MVC的配置文件中配置拦截器即可 !-- 配置Spring MVC的拦截器 -- mvc:interceptors!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --bean idmyInterceptor classcom.zwp.action.MyHandlerInterceptor/bean!-- 只针对部分请求拦截 --mvc:interceptormvc:mapping path/modelMap.do /bean classcom.zwp.action.MyHandlerInterceptorAdapter //mvc:interceptor /mvc:interceptors 7.29、介绍一下 WebApplicationContext WebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能它不同于一般的ApplicationContext 因为它能处理主题并找到被关联的servlet。 八、Tomcat 专题 部分 8.1、Tomcat是什么 Tomcat 服务器Apache软件基金会项目中的一个核心项目是一个免费的开放源代码的Web 应用服务器属于轻量级应用服务器在中小型系统和并发访问用户不是很多的场合下被普遍使用是开发和调试JSP 程序的首选。 8.2、Tomcat的缺省端口是多少怎么修改 找到Tomcat目录下的conf文件夹进入conf文件夹里面找到server.xml文件打开server.xml文件在server.xml文件里面找到下列信息把Connector标签的8080端口改成你想要的端口 Service nameCatalina Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 / 8.3、omcat 有哪几种Connector 运行模式(优化) 下面我们先大致了解Tomcat Connector的三种运行模式。 BIO同步并阻塞 一个线程处理一个请求。缺点并发量高时线程数较多浪费资源。Tomcat7或以下在Linux系统中默认使用这种方式。 配制项protocol”HTTP/1.1” NIO同步非阻塞IO 利用Java的异步IO处理可以通过少量的线程处理大量的请求可以复用同一个线程处理多个connection(多路复用)。 Tomcat8在Linux系统中默认使用这种方式。 Tomcat7必须修改Connector配置来启动。 配制项protocol”org.apache.coyote.http11.Http11NioProtocol” 备注我们常用的JettyMinaZooKeeper等都是基于java nio实现. APR即Apache Portable Runtime从操作系统层面解决io阻塞问题。AIO方式异步非阻塞IO(Java NIO2又叫AIO) 主要与NIO的区别主要是操作系统的底层区别.可以做个比喻:比作快递NIO就是网购后要自己到官网查下快递是否已经到了(可能是多次)然后自己去取快递AIO就是快递员送货上门了(不用关注快递进度)。 配制项protocol”org.apache.coyote.http11.Http11AprProtocol” 备注需在本地服务器安装APR库。Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。Linux如果安装了apr和nativeTomcat直接启动就支持apr。 8.4、Tomcat有几种部署方式 在Tomcat中部署Web应用的方式主要有如下几种 利用Tomcat的自动部署。 把web应用拷贝到webapps目录。Tomcat在启动时会加载目录下的应用并将编译后的结果放入work目录下。 使用Manager App控制台部署。 在tomcat主页点击“Manager App” 进入应用管理控制台可以指定一个web应用的路径或war文件。 修改conf/server.xml文件部署。 修改conf/server.xml文件增加Context节点可以部署应用。 增加自定义的Web部署文件。 在conf/Catalina/localhost/ 路径下增加 xyz.xml文件内容是Context节点可以部署应用。 8.5、tomcat容器是如何创建servlet类实例用到了什么原理 当容器启动时会读取在webapps目录下所有的web应用中的web.xml文件然后对 xml文件进行解析并读取servlet注册信息。然后将每个应用中注册的servlet类都进行加载并通过 反射的方式实例化。有时候也是在第一次请求时实例化 在servlet注册时加上1如果为正数则在一开始就实例化如果不写或为负数则第一次请求实例化。 8.6、Tomcat工作模式 Tomcat作为servlet容器有三种工作模式 独立的servlet容器servlet容器是web服务器的一部分 进程内的servlet容器servlet容器是作为web服务器的插件和java容器的实现web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足 进程外的servlet容器servlet容器运行于web服务器之外的地址空间并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优 进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类 Tomcat作为应用程序服务器请求来自于前端的web服务器这可能是Apache, IIS, Nginx等 Tomcat作为独立服务器请求来自于web浏览器 学了本章之后你应该明白的是 Server、Service、Connector、Container四大组件之间的关系和联系以及他们的主要功能点 Tomcat执行的整体架构请求是如何被一步步处理的 Engine、Host、Context、Wrapper相关的概念关系 Container是如何处理请求的 Tomcat用到的相关设计模式 8.7、Tomcat顶层架构 俗话说站在巨人的肩膀上看世界一般学习的时候也是先总览一下整体然后逐个部分个个击破最后形成思路了解具体细节Tomcat的结构很复杂但是 Tomcat 非常的模块化找到了 Tomcat 最核心的模块问题才可以游刃而解了解了 Tomcat 的整体架构对以后深入了解 Tomcat 来说至关重要 先上一张Tomcat的顶层结构图图A如下 Tomcat中最顶层的容器是Server代表着整个服务器从上图中可以看出一个Server可以包含至少一个Service即可以包含多个Service用于具体提供服务。 Service主要包含两个部分Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件他们的作用如下 Connector用于处理连接相关的事情并提供Socket与Request请求和Response响应相关的转化; Container用于封装和管理Servlet以及具体处理Request请求 一个Tomcat中只有一个Server一个Server可以包含多个Service一个Service只有一个Container但是可以有多个Connectors这是因为一个服务可以有多个连接如同时提供Http和Https链接也可以提供向相同协议不同端口的连接示意图如下Engine、Host、Context下面会说到 多个 Connector 和一个 Container 就形成了一个 Service有了 Service 就可以对外提供服务了但是 Service 还要一个生存的环境必须要有人能够给她生命、掌握其生死大权那就非 Server 莫属了所以整个 Tomcat 的生命周期由 Server 控制。 另外上述的包含关系或者说是父子关系都可以在tomcat的conf目录下的server.xml配置文件中看出下图是删除了注释内容之后的一个完整的server.xml配置文件Tomcat版本为8.0 上边的配置文件还可以通过下边的一张结构图更清楚的理解 Server标签设置的端口号为8005shutdown”SHUTDOWN” 表示在8005端口监听“SHUTDOWN”命令如果接收到了就会关闭Tomcat。一个Server有一个Service当然还可以进行配置一个Service有多个ConnectorService左边的内容都属于Container的Service下边是Connector。 Tomcat顶层架构小结 Tomcat中只有一个Server一个Server可以有多个Service一个Service可以有多个Connector和一个Container Server掌管着整个Tomcat的生死大权 Service 是对外提供服务的 Connector用于接受请求并将请求封装成Request和Response来具体处理 Container用于封装和管理Servlet以及具体处理request请求 知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用对于绝大多数的开发人员来说Server和Service对我们来说确实很远而我们开发中绝大部分进行配置的内容是属于Connector和Container的所以接下来介绍一下Connector和Container。 8.8、Connector和Container的微妙关系 由上述内容我们大致可以知道一个请求发送到Tomcat之后首先经过Service然后会交给我们的ConnectorConnector用于接收请求并将接收的请求封装为Request和Response来具体处理Request和Response封装完之后再交由Container进行处理Container处理完请求之后再返回给Connector最后在由Connector通过Socket将处理的结果返回给客户端这样整个请求的就处理完了 Connector最底层使用的是Socket来进行连接的Request和Response是按照HTTP协议来封装的所以Connector同时需要实现TCP/IP协议和HTTP协议 Tomcat既然需要处理请求那么肯定需要先接收到这个请求接收请求这个东西我们首先就需要看一下Connector Connector架构分析 Connector用于接受请求并将请求封装成Request和Response然后交给Container进行处理Container处理完之后在交给Connector返回给客户端。 因此我们可以把Connector分为四个方面进行理解 1、Connector如何接受请求的 2、如何将请求封装成Request和Response的 3、封装完之后的Request和Response如何交给Container进行处理的 4、Container处理完之后如何交给Connector并返回给客户端的 首先看一下Connector的结构图图B如下所示 Connector就是使用ProtocolHandler来处理请求的不同的ProtocolHandler代表不同的连接类型比如Http11Protocol使用的是普通Socket来连接的Http11NioProtocol使用的是NioSocket来连接的。 其中ProtocolHandler由包含了三个部件Endpoint、Processor、Adapter。 Endpoint用来处理底层Socket的网络连接Processor用于将Endpoint接收到的Socket封装成RequestAdapter用于将Request交给Container进行具体的处理。 Endpoint由于是处理底层的Socket网络连接因此Endpoint是用来实现TCP/IP协议的而Processor用来实现HTTP协议的Adapter将请求适配到Servlet容器进行具体的处理。 Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求AsyncTimeout用于检查异步Request的超时Handler用于处理接收到的Socket在内部调用Processor进行处理。 8.9、Container架构分析 Container用于封装和管理Servlet以及具体处理Request请求在Container内部包含了4个子容器结构图如下图C 4个子容器的作用分别是 Engine引擎用来管理多个站点一个Service最多只能有一个Engine Host代表一个站点也可以叫虚拟主机通过配置Host就可以添加站点 Context代表一个应用程序对应着平时开发的一套程序或者一个WEB-INF目录以及下面的web.xml文件 Wrapper每一Wrapper封装着一个Servlet 下面找一个Tomcat的文件目录对照一下如下图所示 Context和Host的区别是Context表示一个应用我们的Tomcat中默认的配置下webapps下的每一个文件夹目录都是一个Context其中ROOT目录中存放着主应用其他目录存放着子应用而整个webapps就是一个Host站点。 我们访问应用Context的时候如果是ROOT下的则直接使用域名就可以访问例如www.baidu.com如果是Hostwebapps下的其他应用则可以使用www.baidu.com/docs进行访问当然默认指定的根应用ROOT是可以进行设定的只不过Host站点下默认的主应用是ROOT目录下的。 8.10、Container如何处理请求的 Container处理请求是使用Pipeline-Valve管道来处理的Valve是阀门之意 Pipeline-Valve是责任链模式责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理每个处理者负责做自己相应的处理处理完之后将处理后的结果返回再让下一个处理者继续处理。 但是Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同区别主要有以下两点 每个Pipeline都有特定的Valve而且是在管道的最后一个执行这个Valve叫做BaseValveBaseValve是不可删除的 在上层容器的管道的BaseValve中会调用下层容器的管道。 我们知道Container包含四个子容器而这四个子容器对应的BaseValve分别在StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。 Pipeline的处理流程图如下图D Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理这里的最顶层容器的Pipeline就是EnginePipelineEngine的管道 在Engine的管道中依次会执行EngineValve1、EngineValve2等等最后会执行StandardEngineValve在StandardEngineValve中会调用Host管道然后再依次执行Host的HostValve1、HostValve2等最后在执行StandardHostValve然后再依次调用Context的管道和Wrapper的管道最后执行到StandardWrapperValve。 当执行到StandardWrapperValve的时候会在StandardWrapperValve中创建FilterChain并调用其doFilter方法来处理请求这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法这样请求就得到了处理 当所有的Pipeline-Valve都执行完之后并且处理完了具体的请求这个时候就可以将返回的结果交给Connector了Connector在通过Socket的方式将结果返回给客户端。
http://www.dnsts.com.cn/news/143786.html

相关文章:

  • 朝阳区建设工作办公室网站捕鱼游戏网站开发商
  • 别人不能注册我的wordpress站网站建设公司方唯
  • 网站维护升级页面网站建设必须买主机吗
  • 怎么建一个购物网站福州网站微信公众号
  • 做网站销售工资怎么样公司核名查询系统
  • 旅游网站的设计与实现开题报告要进一步增强门户网站建设合力
  • 陕西网站备案查询套版网站怎么做
  • 图片库网站建设wordpress 找不到网页
  • 上海橙网站设计公司聚美优品网站建设项目规划书
  • 网站开发人员薪资网站做浏览器兼容
  • 在线考试系统网站模板网站开发中 html
  • 自己做网站域名建设网站和备案
  • 微信网站是多少钱微信公众号移动网站开发
  • wordpress可以建网站吗自适应h5网站
  • 喀什网站建设企业网址怎么制作
  • 网站空间后台怎么进入注册城乡规划师含金量到底有多高
  • 西安未央区网站建设263企业邮箱入口登录方法
  • 白银网站seo永久免费域名哪里申请
  • 电子商务官方网站中国建设银行官网登录入口手机版
  • 株洲第三方网站建设哪家好wordpress 视频直播
  • 可信网站权威性怎么样高端网站制作上海站霸科技
  • 阿里云建立网站备案深圳wap网站建设
  • 如何制作网站视频教程做自己网站彩票
  • 设计网站怎么做的设计公司名称大全与寓意
  • 郑州app网站公司建立一个购物网站
  • 建立本地网站江苏城乡建设网站
  • 网站建立连接不安全怎么解决angular 做的网站
  • 王野天津卫视seo推广优化服务
  • 仁怀哪儿做网站做英文网站赚钱
  • seo快速建站网站源码交易平台代码