Spring概述
什么是spring?
spring是一个轻量级的java开发框架,它的出现简化了企业级应用开发的难度,是Java开发人员更专注于业务代码逻辑的编写。它有两个特点:IOC控制翻转和AOP面向切面编程。
谈谈IOC和AOP
IOC(控制翻转)
平时我们需要使用对象,都是自己new一个出来。这样在我们项目中,就会出现大量的冗余对象。而IOC控制翻转所在的事情,就是我们之前自己new对象的操作取代了,对象由spring的IOC容器创建并管理。我们需要用到一个对象的时候,只需要向IOC容器的beanFactory索取即可。总体来说,就是将对象创建的权利从程序员手上,交到了IOC容器手中。
AOP(面向切面编程)
AOP是对OOP(面向对象编程)的一种补充,面向对象编程是将事务的特征和行为抽象成一个个对象,而面向切面编程则是在一个个对象的基础上的进一步抽象。找寻这些对象都需要做的事情,然后就成一把刀一样在这些对象上切出一个面,来加入对象都需要做的事情。AOP是通过静态代理或者动态代理来完成的,其他动态代理更为灵活,分为Java自带的代理接口和Cglib动态代理。我们通常使用AOP来完成日志、事务、安全检查相关的操作。
Spring由哪些模块组成?
- spring core:提供了框架的基本组成部分,如IOC、DI等功能。
- 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组件进行单元测试和集成测试。
Spring框架中都用到了哪些设计模式?
- 工厂模式:BeanFactory就是简单工厂模式的体现的,用来创建对象的实例。
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP面向切面编程就用来到了JDK的动态代理和CGLIB动态代理。
- 模板方法:比如,RestTemplate、JpaTemplate都用了模板方法,用来解决代码重复的问题。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现
ApplicationListener
。
Spring如何管理事务的?
Spring的事务声明有两种方式:编程式和声明式。
spring主要是通过“声明式事务”的方式对事务进行管理,即在配置文件、注解进行事务声明,通过AOP将事务切面切入程序,最大的好处是大大减少了代码量。
Spring IOC容器配置Bean的方式?
- XML文件
bean
标签的方式 - 注解的方式
- java Config代码类的方式
Bean是如何被管理的?【重点】
在spring框架中,一旦把一个bean纳入到Spring IOC容器之中,这个bean的生命周期就会交由容器进行管理,一般担当管理者角色的是BeanFactory或ApplicationContext。认识一下Bean的生命周期活动,对更好的利用它有很大的帮助。
概况来说主要有四个阶段:实例化、初始化、使用、销毁。
- 首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化。
- 按照bean定义信息配置信息,注入所有的属性。
- 如果bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,此时该bean就获得了自己在配置文件中的id。
- 如果Bean实现BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该bean的BeanFactory,这样该bean就获得了自己所在的BeanFactory。
- 如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext,这样该bean就获得了自己所在的ApplicationContext。
- 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方法。
- 如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法。
- 如果Bean配置了init-method方法,则会执行init-method配置的方法。
- 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方法。
- 经过流程9之后,就可以正式使用该bean了,对于scope为singleton的bean,spring的ioc容器中会缓存一份该bean的实例,而对于scope为prototype的bean,每次被调用都会new一个新的对象,声明周期就交给调用方管理了,不再是Spring容器进行管理了。
- 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法。
- 如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的生命周期结束了。
Spring中的自动装配有哪些限制?
- 如果使用了构造器注入或者settter注入,那么将覆盖自动装配的依赖关系。
- 基本数据类型的值、字符串字面量、类字面量无法使用自动装配来注入。
- 优先使用显式的装配来进行更精确的依赖注入而不是使用自动装配。
Spring中Bean的作用域有哪些?
- singleton作用域: 在spring IOC容器中仅仅存在一个Bean实例,Bean是以单例方式存在的,为默认值。
- prototype作用域:每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXXXBean().
- request作用域:每一个http请求中的bean是单例的,该作用域仅适用于WebApplicationContext环境
- session作用域:每一个session中的bean是单例的,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
- globalSession作用域:一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境。
什么是IOC和DI?DI是如何实现的?
IOC是控制反转的意思,之前没有引入IOC容器时,加入对象A依赖于对象B,那么对象A在创建初始化或者运行到某一点的时候,需要用到B对象时,就需要我们手动自己去创建一个B对象。这个时候无论是创建B对象,还是使用B对象的控制权都在我们自己的手上。但是在引入IOC容器之后,情形就完成变化了,当对象A运行过程中需要用到对象B的时候,不是需要我们自己再手动创建一个B对象了,而是IOC容器会自动创建一个B对象,帮助我们注入到A对象中。通过前后的对比,我们不难发现。对象A获取依赖对象B的过程,由主动行为编程了被动行为。对象的创建、管理权,从我们手上转移到了IOC容器中了。这就是IOC控制翻转。
关于DI依赖注入:其实DI与IOC控制反转是从不同方面描述的同一件事情,都是指将对象的创建、管理权从使用者的手上,通过依赖注入到IOC容器中。
IOC的优缺点
优点
降低了类之间的耦合,可维护性得到了提高。
缺点
- 因为IOC容器中对象的生成是通过反射形成的,所以在运行效率上会有一些损失。
- 要做一些额外的配置工作
你如何理解AOP中得到连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?
- 连接点:程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。
- 切点(Pointcut):如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。
- 增强(Advice):增强是植入到目标类连接点上的一段程序代码。Spring提供的增强连接口都是带方位名的。如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。
- 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能可以动态的为该业务类添加接口的实现逻辑。让业务类成为这个接口的实现类。
织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:
- 编译期织入:需要特殊的Java编译期(例如AspectJ的ajc)
- 装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强
- 运行时织入:在允许时为目标生成代理实现增强。spring采用动态代词的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。
- 切面(Aspet):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。
Spring自动装配的方式有哪些?
- no:不进行自动装配,而是手动设置Bean的依赖关系
- byName:根据Bean的名字进行自动装配
- byType:根据Bean的类型进行自动装配
- constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
- autodetect:如果有默认的构造器,这通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。
Spring中如何使用注解来配置Bean?有哪些相关的注解?
在启动类上添加@EnableAutoConfiguration或者@SpringBootApplication来开启自动配置,然后使用以下的注解来标注需要由Spring IOC容器进行对象托管的类。
- @Component:其他所有类
- @Controller:controller控制器
- @Service:service业务逻辑层
- @Repository:dao数据交互层
这几个注解的作用其实都是相同的,都是将类注入到ioc容器中,只是为了方便区别字面上有所不同罢了。
Spring MVC 的工作流程
- 客户端的所有请求都叫给前端控制dispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
- 前端控制器DispatcherServlet收到请求后,将根据请求的信息以及HandlerMapping的配置找到处理该请求的对应Handler处理器。
- spring会通过HandlerAdapter对该处理器进行封装,然后用统一的接口对各种Handler中的方法进行调用。
- Handler完成对用户请求的处理后,会返回一个ModelAndView对象给前端控制器DispatcherServlet。
- DispatcherServlet借助逻辑视图解析器ViewResolver来解析刚返回得到的ModelAndView对象
- 最后拿到真正的视图对象,浏览器对模型数据进行渲染
- 客户端得到响应,可能是一个普通的html页面,也可能是xml或者是JSON字符串,还可以是一张图片或者PDF文件。
ApplicationContext通常的实现是什么?
- FlieSystemXmlApplciationContext: 此容器从一个XML文件中加载bean是的定义,XML bean配置文件的全路径名必须提供给他的构造函数。
- ClassPathXmlApplicationContext: 此容器也从一个XML文件中加载Beans的定义,这里,你需要正确设置classpath,因为这个容器将在classpath里面找到bean配置。
- WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个web应用的所有bean。
Bean工厂和APlication contexts有什么区别?
- 相同点:两者都是通过xml配置文件加载bean
- 不同点:applicationContext相比于BeanFactory,提供了更多的扩展功能。BeanFactory是延迟加载,如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常。而ApplicationContext则在初始化自身就进行了效验,这样有利于检查说依赖属性是否注入。所以通常情况下我们选择使用ApplicationContext。
spring有几种依赖注入方式
变量注入
- 直接在变量上面使用@Autowired的既是变量注入
- 优点就是注入方式简单,非常简洁。
- 缺点:注入的对象不能用final修饰;可能会导致循环依赖的出现;对于IOC容器以外的环境,除了使用反射来提供它需要的依赖,无法复用该实现类;
构造器注入
- 显式注明必须强制注入,通过强制指明依赖注入来保证这个类的运行,防止出现空指针异常。
- 注入对象可以使用final修饰。
- 非IOC容器环境也可以使用new实例化该类的对象。
- 避免了循环依赖,如果真的存在循环依赖,spring项目启动的时候就会报错。
- 缺点就是当我们需要注入的对象过多的时候,构造函数的代码会显得非常的臃肿。
setter方法注入
- 依赖注入中使用的依赖是可选的,选择依赖的意思是注入的依赖是可以为NULL。
- 允许在类构造完成后重新注入
- 缺点就是注入对象不能使用final修饰
总结:如果注入的属性是必选的属性,则通过构造器注入的方式;如果注入的属性是可选的,则通过setter注入的方式;至于变量注入,不建议使用,虽然很省劲。
什么是spring beans?
我们的spring应用程序就是由一个个spring beans组成的,它们通过注解或者xml配置文件的方式定义,然后注入到的IOC容器中,由IOC容器管理整个生命周期。
spring中的bean是线程安全的吗?
首先spring IOC容器本身并没有提供线程安全策略,因此我们对于bean的线程安全问题,我们需要结合具体的bean作用域来说:
- singleton:单例bean,不会创建新的bean,所有的线程都公用一个bean对象,因此可能会存在线程安全问题。
- prototype:原型bean,因为每次都会创建一个bean对象,所以不会出现线程安全问题。
实际上大部分实际spring bean都是无状态的,即无需进行数据保存操作,只是进行数据的查询。这个时候即使是单例bean也是线程安全的,但是如果要进行数据保存的一些操作,这个时候就需要我们开发者自己对线程安全进行一些保证操作了。例如将singleton
变为prototype
。
Spring如何处理线程并发问题?
- 一般情况下,只有无状态的bean才可以再多线程环境下共享,在Spring中,绝大部分bean都是以singleton声明的作用域,所以可能存在线程安全问题。所以我们可以采用ThreadLocal和线程同步机制来解决线程安全问题。
- ThreadLocal采用了空间换时间的做法,为每一个线程提供一个独立的变量副本,从而隔离了多个线程对于对一个变量资源的访问。
- 而线程同步机制则是对于可能存在线程安全问题的资源进行加锁,不同的线程在访问加锁资源之前都需要获取锁资源,没有获取到的需要进行等待。这样就保证了同时只有一个线程对资源进行操作,也就解决了多线程资源并发的问题。但是因为要等待锁资源,所以是一种“时间换空间”的做法。
自动装配有哪些局限性?
- 重写:你仍需要和配置来定义依赖,意味着重要重写自动装配。
- 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串和类。
- 模糊特性:自动状态不如显示装配精确,如果有可能还是建议使用显示装配。
你可一直了spring中注入一个null和一个空字符串嘛?
可以
@Autowired和@Resource之间有什么区别?
- @Autowired默认是按照类型进行装配注入的,、
- 而@Resource默认是按照名称来装配注入的,只有当找不到名称匹配的bean才会按照类型来装配。
@Qualifier注解有什么用?
用来配合@Autowired注解更加精准的指定装配哪个bean,因为@AutorWried是按照类型进行装配的,如果有多个同类型的bean需要被装配,单单使用@AutoWried注解是不行的,需要通过添加@Qualifier注解来指定name装配。
@RequestMapping注解有什么用?
@RequestMapping注解用于将特定HTTP请求方法映射到将处理响应请求的控制器重的特定类和特定方法。
Spring 事务的本质
spring 事务的本质其实就是对数据库事务的支持,如果没有数据库的事务支持,spring是无法提供事务功能的,真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
说一下Spring的事务传播行为
spring事务的传播行为就是说,当多个事务同时存在的时候,spring如何处理这些事务的行为:
propagation_required
:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。propagation_supports
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务运行。propagation_mandatory
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。propagatin_requires_new
:创建新的事务,无论当前是否已经存在事务,都会创建新的事务。propagation_not_supported
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。propagation_never
:以非事务方式执行,如果当前存在事务,则抛出异常。propagation_nested
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,这创建一个新的事务。
说一下 spring 的事务隔离?
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
- ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
- SOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
- ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
- ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
- ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
不可重复读 :是指在一个事务内,多次读同一数据结果不一致。
幻读 :是指在一个事务内,多次读取的数据条数不一致。
Spring AOP and AspectJ AOP 有什么区别?AOP都有哪些实现方式?
AOP实现的关键在于代理模式,AOP代理模式主要分为静态代理和动态代理。静态代理代表就为AspectJ,而动态代理则以Spring AOP为代表。
- AspectJ 是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AscpectJ(切面)织入到Java字节码中,运行的时候就是增强之后的aop对象。
- spring aop使用的是动态代词,所谓的动态代词就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个aop对象,这个aop对象包含了对象的全部方法,并且在特定的切点还做了增强处理,并回调原对象的方法。
JDK动态代理和CGLIB动态代理的区别?
- JDK代理只能对实现接口的类生成代理;CGLib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
- JDK代理使用的是反射机制实现aop的动态代理,CGLib代理使用字节码处理框架ASM,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,CGLib创建效率较低,执行效率高。
- JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLib则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
动态代理和动态代理的区别?
静态代理与动态代理区别在于生成AOP代理对象的时机不同,静态代理在字节码生成的时候就已经存在代理类了,而动态代理实在字节码文件生成后,内存中动态添加代理对象的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。