目录
- Spring Framework 中有多少个模块,它们分别是什么?
- Spring框架的设计目标、设计理念?核心是什么?
- Spring框架中都用到了哪些设计模式?
- Spring的核心机制是什么?
- 什么是Spring IOC容器?
- 什么是依赖注入?
- 可以通过多少种方式完成依赖注入?
- 列举IoC的一些好处?
- BeanFactory和ApplicationContext有什么区别?
- Spring中常见的增强方式/Spring通知有哪些类型?
- 简述Spring中Bean的作用域?
- spring bean容器的生命周期是什么样的?
- @Autowired和@Resource之间的异同?
- Spring框架中的单例Bean是线程安全的吗?
- Spring如何处理线程的并发问题?
- 什么是Spring的内部Bean
- 什么是bean的自动装配?
- spring 自动装配 bean 有哪些方式?
- @Component, @Controller, @Repository, @Service 有何区别?
- Spring支持的事务管理类型, spring 事务实现方式有哪些?
- 说一下Spring的事务传播行为?
- 说一下 spring 的事务隔离?
- 事务并发问题
- 简述SpringMVC工作流程?
- 组件说明
- 154.SpringMVC常用的注解有哪些?
- Spring 是怎么解决循环依赖的?
- SpringMVC怎么样设定重定向和转发的?
- SpringMVC里面拦截器是怎么写的?
- 158.简述RequestMapping注解有六个属性?
- 简述注解原理?
- 如何自定义注解?
Spring Framework 中有多少个模块,它们分别是什么?
- Spring 核心容器 – 该层基本上是 Spring Framework 的核心。它包含以下模块:Spring Core、Spring Bean、SpEL (Spring Expression Language)、Spring Context
- 数据访问/集成 – 该层提供与数据库交互的支持。它包含以下模块:JDBC、ORM、OXMJMS、Transaction
- Web。该层提供了创建 Web 应用程序的支持。它包含以下模块:Web、Web – Servlet、Web – Socket、Web – Portlet
- AOP。该层支持面向切面编程
- Instrumentation。该层为类检测和类加载器实现提供支持。
- Test。该层为使用 JUnit 和 TestNG 进行测试提供支持。
- Messaging。该模块为 STOMP 提供支持。它还支持注解编程模型,该模型用于从WebSocket 客户端路由和处理 STOMP 消息。
- Aspects。该模块为与 AspectJ 的集成提供支持
Spring框架的设计目标、设计理念?核心是什么?
-
设计目标:Spring为开发者提供一个一站式轻量级应用开发平台
-
设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;
-
核心:IoC容器和AOP模块。通过IoC容器管理实例对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。
Spring框架中都用到了哪些设计模式?
-
工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
-
代理设计模式:Spring AOP功能的实现。
-
单例设计模式:Spring中的bean默认都是单例的。
-
模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
-
包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
-
观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
Spring的核心机制是什么?
- 控制反转IOC/依赖注入DI:将对象创建的控制权由代码本身转移到外部容器,通过依赖注入的方式进行对象设值
- 面向切面编程:将程序中公共部分的代码统一处理,在不改变原有业务逻辑的前提下对程序做增强处理。
什么是Spring IOC容器?
- Spring框架的核心是Spring容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。
- Spring容器使用依赖注入来管理组成应用程序的组件。
- 容器通过读取提供的配置元数据来接收对象进行实例化,配置和组装的指令。该元数据可以通过XML,Java注解或Java代码提供。
最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的
什么是依赖注入?
- 在依赖注入中,您不必创建对象,但必须描述如何创建它们(配置或者注解)。
- 不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。
- 由IoC容器将它们装配在一起。
可以通过多少种方式完成依赖注入?
设值注入(setter)、构造注入、P命名空间注入、接口注入
列举IoC的一些好处?
- 最小化应用程序中的代码
- 使应用程序易于测试,因为不需要单元测试用例中的任何单例或JNDI查找机制
- 以最小的影响和最少的侵入机制促进松耦合
- 支持即时的实例化和延迟加载服务
BeanFactory和ApplicationContext有什么区别?
- BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
- ApplicationContext 面向使用Spring 框架的开发者, ApplicationContext 使用的“场合”较多
-
功能上的区别
- BeanFactory是Spring中最底层的接口,是IOC的核心,其功能包含了各种Bean的定义、加载、实例化、依赖注入和生命周期的管理。是IOC最基本的功能。
- 而ApplicationContext接口是BeanFactory的子类,具有BeanFactory所有的功能,同时继承了MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件,使得每一个上下文都专注于一个特定层次,提供在监听器中注册bean事件。
-
加载方式的区别
- BeanFactory是延时加载,也就是说在容器启动时不会注入bean,而是在需要使用bean的时候,才会对该bean进行加载实例化。
- ApplicationContext 是在容器启动的时候,一次性创建所有的bean,所以运行的时候速度相对BeanFactory比较快。
- 因为加载方式的不同,导致BeanFactory无法提前发现spring存在的配置问题。(如果bean的某个属性没有注入,BeanFactory加载不会抛出异常,直至第一次调用getBean()方法时才会抛出异常。)但是ApplicationContext 在容器启动时就可以发现spring存在的配置问题,因为他是一次性加载的,有利于检测依赖属性是否注入(也因为其一次性加载的原因,导致占用内存空间,当Bean较多时,影响程序启动的速度)。
-
创建方式的区别
- BeanFactory是以编程的方式创建。ApplicationContext 是以声明的方式创建。
-
注册方式的区别(Processor:处理器)
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用。
- BeanFactory是需要手动注册的。ApplicationContext 是自动注册的。
Spring中常见的增强方式/Spring通知有哪些类型?
在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 最终通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 后置通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
简述Spring中Bean的作用域?
- singleton单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。
- prototype原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
- request在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
- session在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
- global Session在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。
spring bean容器的生命周期是什么样的?
对象创建
- 从xml配置的Bean,@Bean注解,或者Java代码BeanDefinitionBuilder中读取Bean的定义,实例化Bean对象;
- 设置Bean的属性;
- (BeanNameAware,BeanFactoryAware,ApplicationContextAware);注入Aware的依赖
- BeanPostProcessor.postProcessorBeforeInitialization();执行通用的方法前置处理,方法:
- 执行 InitalizingBean.afterPropertiesSet() 方法
- 执行Bean自定义的初始化方法init,或者 @PostConstruct 标注的方法;
- 执行方法BeanPostProcessor.postProcessorAfterInitialization()
- 创建对象完毕;
对象销毁 - 执行 DisposableBean.destory() 方法;
- 执行自定义的destory方法或者 @PreDestory 标注的方法;
- 销毁对象完毕
总结:
- 首先是实例化、属性赋值、初始化、销毁这 4 个大阶段;
- 再是初始化的具体操作,有 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBean 和 init-method 的初始化操作;
- 销毁的具体操作,有注册相关销毁回调接口,最后通过DisposableBean 和 destory-method 进行销毁。
@Autowired和@Resource之间的异同?
相同点:
- @Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。
不同点:
- 提供方:@Autowired是由Spring提供;@Resource是由javax.annotation.Resource提供,即J2EE提供,需要JDK1.6及以上。
- 注入方式:@Autowired只按照byType 注入;@Resource默认按byName自动注入,也提供按照byType 注入;
- 属性:@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
推荐使用@Resource注解在字段上,这样就不用写setter方法了.并且这个注解是属于J2EE的,减少了与Spring的耦合,这样代码看起就比较优雅 。
Spring框架中的单例Bean是线程安全的吗?
- 不是.Spring默认的bean是单例的,也没有进行封装处理,所以不是线程安全的。
- 但是,共享不一定会有线程安全问题。
- 如果某个bean我们定义了共享数据,且可以对共享数据进行修改,这样才会造成线程安全问题。比如我们在线程中定义一个count++,那么这个数就是不安全的,每次线程到来都会被执行一次,count值加一。
- controller、service、dao本身不是安全的,但是,我们只在这些方法中相互调用,那么不会产生线程安全问题。
- Dao层会操作数据库,但是每一个和数据库连接的connection,都会被数据库的事务机制管理,如果开启了Spring事务机制,那么也会被其管理。这都不会造成线程安全问题。
Spring如何处理线程的并发问题?
- 可以将成员变量声明在方法内。
- 将成员变量放在ThreadLocal之中。(ThreadLocal userName = new ThreadLocal<>();)
成员变量放在ThreadLocal之中,传进来的参数是跟随线程的,所以也是线程安全的。- ThreadLocal 则从另一个角度来解决多线程的并发访问。
- ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
- 因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
- ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
- 由于 ThreadLocal中可以持有任何类型的对象,低版本 JDK 所提供的 get()返回的是 Object 对象,需要强制类型转换。
- 但 JDK5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal 的使用。
- 将bean设置为原型模式。(@Scope(“prototype”)),原型模式bean线程之间不共享就不会发生线程安全问题。
- 使用同步锁(会影响系统的吞吐量):synchronized 修饰方法。
什么是Spring的内部Bean
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>
或 <constructor-arg/>
元素内使用 元素,内部bean通常是匿名的,它们的Scope一般是prototype。
什么是bean的自动装配?
在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要任何配置,能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。
spring 自动装配 bean 有哪些方式?
- 在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
@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。
Spring支持的事务管理类型, spring 事务实现方式有哪些?
- 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
- 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
说一下Spring的事务传播行为?
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。。
说一下 spring 的事务隔离?
spring 有五大隔离级别,
- ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
- ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
- ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
- ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
- ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
事务并发问题
- 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
- 不可重复读 :是指在一个事务内,多次读同一数据。
- 幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。。
简述SpringMVC工作流程?
- 浏览器发送请求送至前端控制器DispatcherServlet。
- DispatcherServlet收到请求后调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的Handler处理器返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdaptor处理器适配器。
- HandlerAdaptor去调用具体的处理器(Controller)。
- Controller返回一个ModelAndView对象给HandlerAdaptor。
- HandlerAdaptor将接收到的ModelAndView对象返回给DispatcherServlet。
- DispatcherServlet将ModelAndView对象传给ViewResolver视图解析器进行解析。
- ViewResolver视图解析器将解析的结果View返回给DispatcherServlet。
- DispatcherServlet根据View进行渲染视图。
- DispatcherServlet响应浏览器的请求。
组件说明
- DispatcherServlet:前端控制器(由框架提供),作为流程控制的中心,控制其他组件执行,统一调度,能够接受请求、响应结果。
- HandlerMapping:处理器映射器(由框架提供),根据用户请求的url路径找到负责处理的Handler处理器。
- HandlerAdaptor:处理器适配器(由框架提供),根据特定规则去执行Handler。
- Handler:处理器(需要自己开发),作为后端控制器,对具体用户的业务请求进行请求,并将处理的结果封装在ModelAndView对象中,并返回给调用者。
- ViewResolver:视图解析器(由框架提供),主要进行视图解析,根据逻辑视图名解析出真正的视图。
154.SpringMVC常用的注解有哪些?
- @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中 的所有响应请求的方法都是以该地址作为父路径。
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
- @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
- @RequestParam 参数绑定。如果需要在业务方法中获取 URL 的参数值,可以使用@RequestParam 注解
- @PathVariable。Spring MVC 支持 RESTful 风格的 URL 参数获取,通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中。
Spring 是怎么解决循环依赖的?
- 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
- 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
- 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A)。这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories),通过ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象。C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中;
- 回到 B,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。
关键字:三级缓存,提前曝光。
SpringMVC怎么样设定重定向和转发的?
- 转发(默认为转发):若需要手动设置转发,则需要在返回值前面加"forward:“,譬如"forward:user.do?name=method4”
- 重定向:在返回值前面加"redirect:“,譬如"redirect:http://www.baidu.com”
SpringMVC里面拦截器是怎么写的?
- 有两种写法,一种是实现HandlerInterceptor接口,
- 另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:
158.简述RequestMapping注解有六个属性?
- value:指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
- method:指定请求的method类型, GET、POST、PUT、DELETE等;
- consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
- produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
- params:指定request中必须包含某些参数值是,才让该方法处理。
- headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
简述注解原理?
- Java 注解就是代码中的一些特殊标记(元信息),用于在编译、类加载、运行时进行解析和使用,并执行相应的处理。
- 它本质是继承了 Annotation 的特殊接口,其具体实现类是 JDK 动态代理生成的代理类,通过反射获取注解时,返回的也是 Java 运行时生成的动态代理对象 $Proxy1。
- 通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个Map中查询出对应的值,而 memberValues 的来源是Java常量池。
如何自定义注解?
- 创建一个自定义注解:与创建接口类似,但自定义注解需要使用 @interface
- 添加元注解信息,比如 @Target、@Retention、@Document、@Inherited 等
- 创建注解方法,但注解方法不能带有参数
- 注解方法返回值为基本类型、String、Enums、Annotation 或其数组
- 注解可以有默认值;