spring基础知识点讲解,spring面试题

1. Spring 概述

1.1 Spring简介

Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。

1.2 Spring优势

整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀天,就能体会到它的⼀些优势。

  • 方便解耦,简化开发
    通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。
  • AOP编程的⽀持
    通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过AOP轻松应付。
  • 声明式事务的⽀持
    @Transactional
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼开发效率和质量。
  • ⽅便程序的测试
    可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的事情。
  • ⽅便集成各种优秀框架
    Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接⽀持。
  • 降低JavaEE API的使⽤难度
    Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低。
  • 源码是经典的 Java 学习范例
    Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。

1.3 Spring 的核⼼结构

Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零侵⼊的轻量级框架。
在这里插入图片描述

  • Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应用中bean的创建、配置和管理,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,我们还会发现有多种Spring应用上下文的实现,所有Spring模块都构建于核心容器之上
  • 面向切面编程(AOP)/ Aspects spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础,于DI一样,AOP可以帮助应用对象解耦
  • 数据访问与集成(Data Access/Integration)
    Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
  • Web该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
  • Test为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。

2. Spring的核心思想

注意: IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了⾮常好的实现(Java)

2.1 IoC

2.1.1 什么是IoC

IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现
描述的事情: Java开发领域对象的创建,管理的问题
传统开发⽅式: ⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
IoC思想下开发⽅式: 我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可我们丧失了⼀个权利(创建、管理对象的利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列事情)
为什么叫控制反转?
控制:指的是对象创建(实例化,管理)的权利
反转:控制权交给外部环境了(Spring框架,IoC容器)
在这里插入图片描述

2.1.2 IoC解决了什么问题

IoC解决对象之间的耦合问题
在这里插入图片描述

2.1.3 IoC和DI的区别

DI:Dependancy Injection(依赖注⼊)
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
在这里插入图片描述

2.2 AOP

2.2.1 什么是AOP

AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
AOP是OOP的延续,从OOP说起
OOP三⼤特征:封装、继承和多态
OOP是⼀种垂直继承体系:
在这里插入图片描述
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了
在这里插入图片描述
横切逻辑代码:
在这里插入图片描述
横切逻辑代码存在什么问题:

  • 横切代码重复问题
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护部方便

AOP独辟蹊径剔除横向抽取机制,将横切逻辑代码和圆舞曲逻辑代码进行拆分:
在这里插入图片描述
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业务逻辑中,达到和原来⼀样的效果,这个是⽐较难的

2.2.2 AOP在解决什么问题

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

2.2.3 为什么叫做面向切面编程

「切」: 指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」: 横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯

3. Spring IOC 应⽤

3.1 Spring IoC基础

在这里插入图片描述

3.1.1 BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等
在这里插入图片描述
启动IoC容器的方式:

1.Java环境下启动IoC容器:

  • ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
  • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器

2.Web环境下启动IoC容器

  • 从xml启动容器
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
    	 <display-name>Archetype Created Web Application</display-name>
    	 
    	 <!--配置Spring ioc容器的配置⽂件-->
    	 <context-param>
    		 <param-name>contextConfigLocation</param-name>
    		 <param-value>classpath:applicationContext.xml</param-value>
    	 </context-param>
    	 
    	 <!--使⽤监听器启动Spring的IOC容器-->
    	 <listener>
    	 	<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
    	 </listener>
    </web-app>
    
  • 从配置类启动容器
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        
        <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
        <context-param>
        	<param-name>contextClass</param-name>
        	<paramvalue>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
        
        <!--配置启动类的全限定类名-->
        <context-param>
        	<param-name>contextConfigLocation</param-name>
        	<param-value>com.lagou.edu.SpringConfig</param-value>
        </context-param>
        
        <!--使⽤监听器启动Spring的IOC容器-->
        <listener>
        	<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    </web-app>
    

3.1.2 纯xml模式

  • 实例化Bean的三种方式

    • 1). 方式一:使用无参构造函数
      在默认情况下,它会通过反射调用无参构造函数来创建对象,如果类中没有无参构造函数,将创建失败
      <!--配置service对象-->
      <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
      </bean>
      
    • 2). 方式二:使用静态方法创建
      在实际开发中,我们使用的对象有时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法还是static修饰的方法,即是这种情况。
      例如:我们在做jdbc操作时,会用到java.sql.Connection接口的实现类,如果是mysql数据库,那么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection() ,因为我们要注册驱动,还要提供URL和凭证信息,⽤ DriverManager.getConnection ⽅法来获取连接。
      在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置。
      <!--使⽤静态⽅法创建对象的配置⽅式-->
      <bean id="userService" class="com.lagou.factory.BeanFactory"
       factory-method="getTransferService"></bean>
      
    • 3). 方式三:使用实例化方法创建
      此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可采⽤下⾯的配置⽅式
      <!--使⽤实例⽅法创建对象的配置⽅式-->
      <bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean>
      <bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
      
  • Bean的X及⽣命周期

    • 1). 作⽤范围的改变
      在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:
      在这里插入图片描述
      在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:

      <!--配置service对象-->
      <bean id="transferService"
      class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
      </bean>
      
    • 2). 不同作用范围的生命周期
      单例模式:singleton

      对象出⽣:当创建容器时,对象就被创建了。
      对象活着:只要容器在,对象⼀直活着。
      对象死亡:当销毁容器时,对象就被销毁了。
      ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
      

      多例模式:prototype

      对象出⽣:当使⽤对象时,创建新的对象实例。
      对象活着:只要对象在使⽤中,就⼀直活着。
      对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
      ⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
      
  • Bean标签属性
    在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置
    Bean标签的属性如下:
    id属性:⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
    class属性:⽤于指定创建Bean对象的全限定类名。
    name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
    factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
    factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
    scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
    init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
    destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

  • DI 依赖注⼊的xml配置

    • 依赖注⼊分类
      1). 按照注⼊的⽅式分类
      构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
      set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
      2). 按照注⼊的数据类型分类
      基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
      其他Bean类型:注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
      复杂类型(集合类型):注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。

    • 依赖注⼊的配置实现之 构造函数注⼊
      顾名思义,就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
      在这里插入图片描述
      在使⽤构造函数注⼊时,涉及的标签是 constructor-arg ,该标签有如下属性:
      name:⽤于给构造函数中指定名称的参数赋值。
      index:⽤于给构造函数中指定索引位置的参数赋值。
      value:⽤于指定基本类型或者String类型的数据。
      ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。

    • 依赖注⼊的配置实现之 set⽅法注⼊
      顾名思义,就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
      在这里插入图片描述
      在使⽤set⽅法注⼊时,需要使⽤ property 标签,该标签属性如下:
      name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)
      value:指定注⼊的数据。它⽀持基本类型和String类型。
      ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。

    • 依赖注⼊的配置实现之 复杂数据类型注⼊
      ⾸先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。
      接下来就是注⼊的⽅式的选择,只能在构造函数和set⽅法中选择,我们的示例选⽤set⽅法注⼊。
      在这里插入图片描述
      在这里插入图片描述
      在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的 value 标签内部可以直接写值,也可以使⽤ bean 标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
      在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。
      同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ property 标签中不能使⽤ ref 或者 bean 标签引⽤对象。

3.1.3 xml与注解相结合模式

注意:

  1. 实际企业开发中,纯xml模式使⽤已经很少了
  2. 引⼊注解功能,不需要引⼊额外的jar
  3. xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
  4. 哪些bean的定义写在xml中,哪些bean的定义使⽤注解

第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池
⾃⼰开发的bean定义使⽤注解

  • xml中标签与注解的对应(IoC)
    xml形式对应的注解形式
    标签@Component(“accountDao”),注解加在类上。
    bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;
    另外,针对分层代码开发提供@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已
    标签的scope属性@Scope(“prototype”),默认单例,注解加在类上
    标签的 init-method 属性@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
    标签的destory-method属性@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法
  • DI 依赖注⼊的注解实现⽅式
    • 1). @Autowired(推荐使⽤)
      @Autowired为Spring提供的注解,需要导⼊包org.springframework.beans.factory.annotation.Autowired。
      @Autowired采取的策略为按照类型注⼊。
      public class TransferServiceImpl {
      	@Autowired
      	private AccountDao accountDao;
      }
      
      如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
      @Qualifier告诉Spring具体去装配哪个对象。
      public class TransferServiceImpl {
       	@Autowired
       	@Qualifier(name="jdbcAccountDaoImpl")
       	private AccountDao accountDao;
      }
      
      这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
    • 2). @Resource
      @Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。
      @Resource 默认按照 ByName ⾃动注⼊。
      public class TransferService {
      	 @Resource
      	 private AccountDao accountDao;
      	 
      	 @Resource(name="studentDao")
      	 private StudentDao studentDao;
      	 
      	 @Resource(type="TeacherDao")
      	 private TeacherDao teacherDao;
      	 
      	 @Resource(name="manDao",type="ManDao")
      	 private ManDao manDao;
      } 
      
      a). 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
      b). 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
      c). 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
      d). 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
      注意:
      @Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
      <dependency>
      	<groupId>javax.annotation</groupId>
      	<artifactId>javax.annotation-api</artifactId>
      	<version>1.3.2</version>
      </dependency>
      

3.1.4 纯注解模式

改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动
对应注解:
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器

3.2 Spring IOC⾼级特性

3.2.1 lazy-Init 延迟加载

Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singletonbean。
比如:

<bean id="testBean" class="cn.lagou.LazyBean" />
// 该bean默认的设置为:
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init="false",⽴即加载,表示在spring启动时,⽴刻进⾏实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
也可以在容器层次中通过在 元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:

<beans default-lazy-init="true">
 <!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
应⽤场景

1). 开启延迟加载⼀定程度提⾼容器启动和运转性能
2). 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

3.2.1 FactoryBean 和 BeanFactory

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;此处我们重点分析FactoryBean。
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
	@Nullable
	// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
	T getObject() throws Exception;
	
	@Nullable
	// 返回FactoryBean创建的Bean类型
	Class<?> getObjectType();
	
	// 返回作⽤域是否单例
	default boolean isSingleton() {
		return true;
	}
}

Company类

package com.lagou.edu.pojo;
public class Company {
	private String name;
 	private String address;
 	private int scale;
 	// 字段get,set方法
 	// toString方
}

CompanyFactoryBean类

package com.lagou.edu.factory;
import com.lagou.edu.pojo.Company;
import org.springframework.beans.factory.FactoryBean;

public class CompanyFactoryBean implements FactoryBean<Company> {
	 private String companyInfo; // 公司名称,地址,规模
	 public void setCompanyInfo(String companyInfo) {
	 	this.companyInfo = companyInfo;
	 }
	 
	 @Override
	 public Company getObject() throws Exception {
		 // 模拟创建复杂对象Company
		 Company company = new Company();
		 String[] strings = companyInfo.split(",");
		 company.setName(strings[0]);
		 company.setAddress(strings[1]);
		 company.setScale(Integer.parseInt(strings[2]));
		 return company;
	 }
	 
	 @Override
	 public Class<?> getObjectType() {
	 	return Company.class;
	 }
	 
	 @Override
	 public boolean isSingleton() {
	 	return true;
	 }
}

xml配置:

<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
 	<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>

测试,获取FactoryBean产⽣的对象:

Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}

测试,获取FactoryBean,需要在id之前添加“&”:

Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09

3.2.1 后置处理器

Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessorBeanFactoryPostProcessor,两者在使⽤上是有所区别的。

⼯⼚初始化(BeanFactory)—> Bean对象

在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情。
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象。
SpringBean的⽣命周期

  1. BeanPostProcessor
    BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。
    在这里插入图片描述
    该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法。
    定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。

    注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。

  2. BeanFactoryPostProcessor
    BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer
    在这里插入图片描述此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法:
    在这里插入图片描述
    其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法:
    在这里插入图片描述
    ⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
    BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition。

    注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象。

3.2 Spring IoC循环依赖问题

  1. 什么是循环依赖
    循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
    在这里插入图片描述
    注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
    Spring中循环依赖场景有:

    • 构造器的循环依赖(构造器注⼊)
    • Field 属性的循环依赖(set注⼊

    其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。

  2. 循环依赖处理机制

    • 单例 bean 构造器参数循环依赖(⽆法解决)

    • prototype 原型 bean循环依赖(⽆法解决)
      对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。
      AbstractBeanFactory.doGetBean()⽅法:

      if (isPrototypeCurrentlyInCreation(beanName)) {
       	throw new BeanCurrentlyInCreationException(beanName);
      }
      
      protected boolean isPrototypeCurrentlyInCreation(String beanName) {
      	 Object curVal = this.prototypesCurrentlyInCreation.get();
      	 return (curVal != null &&
      	 (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
      	curVal).contains(beanName))));
      }
      

      在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记。

      try {
      	 //创建原型bean之前添加标记
      	 beforePrototypeCreation(beanName);
      	 //创建原型bean
      	 prototypeInstance = createBean(beanName, mbd, args);
      }
      finally {
      	 //创建原型bean之后删除标记
      	 afterPrototypeCreation(beanName);
      }
      

      总结:Spring 不⽀持原型 bean 的循环依赖。

    • 单例bean通过setXxx或者@Autowired进⾏循环依赖
      Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前。
      Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
      1). Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。

      boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
       isSingletonCurrentlyInCreation(beanName));
      if (earlySingletonExposure) {
      	 if (logger.isDebugEnabled()) {
      		 logger.debug("Eagerly caching bean '" + beanName +
      		 "' to allow for resolving potential circular references");
      	 }
       	//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
      	addSingletonFactory(beanName, new ObjectFactory<Object>() {
      		 @Override
      		 public Object getObject() throws BeansException {
      		 return getEarlyBeanReference(beanName, mbd, bean);
      		 }
      	 });
       }
      

      2). ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
      3). Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中。
      4). ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例。

      ClassA通过spring容器获取到ClassB,完成了对象初始化操作。

      5). 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。

4 Spring AOP 应⽤

AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。

4.1 AOP 相关术语

  1. 业务主线
    在讲解AOP术语之前,我们先来看⼀下下⾯这两张图,它们就是第三部分案例需求的扩展(针对这些扩展的需求,我们只进⾏分析,在此基础上去进⼀步回顾AOP,不进⾏实现)
    在这里插入图片描述
    上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
    在这里插入图片描述

  2. AOP术语

    名词解释
    Joinpoint(连接点)它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
    Pointcut(切⼊点)它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。
    Advice(通知/增强)它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知
    Target(⽬标对象)它指的是代理的⽬标对象。即被代理对象。
    Proxy(代理)它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
    Weaving(织⼊)它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
    Aspect(切⾯)它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager 就是⼀个切⾯。

    连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
    切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法。
    Advice增强
    第⼀个层次:指的是横切逻辑
    第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
    Aspect切⾯:切⾯概念是对上述概念的⼀个综合
    Aspect切⾯= 切⼊点+增强
    Aspect切⾯= 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑
    众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码

4.2 Spring中AOP的代理选择

Spring 实现AOP思想使⽤的是动态代理技术。
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB
当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。
当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术。
不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。

4.3 Spring中AOP的配置⽅式

在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。
第⼀类:使⽤XML配置
第⼆类:使⽤XML+注解组合配置
第三类:使⽤纯注解配置

4.4 Spring中AOP实现

需求:横切逻辑代码是打印⽇志,希望把打印⽇志的逻辑织⼊到⽬标⽅法的特定位置(service层transfer⽅法)

4.5 Spring 声明式事务的⽀持

编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务:通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务

4.5.1 事务概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。
例如:A——B转帐,对应于如下两条sql语句

/*转出账户减钱*/
 update account set money=money-100 where name='a';
 /**转⼊账户加钱*/
 update account set money=money+100 where name='b';

这两条语句的执⾏,要么全部成功,要么全部不成功。

4.5.2 事务的四⼤特性

  • 原⼦性(Atomicity)
    原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。
    从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败。
  • ⼀致性(Consistency)
    事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
    例如转账前A有1000,B有1000。转账后A+B也得是2000。
    ⼀致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)。
  • 隔离性(Isolation)
    事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
    ⽐如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
  • 持久性(Durability)
    持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障,也不应该对其有任何影响。

4.5.3 事务的隔离级别

不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题

  • 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。

  • 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)。

    场景:
    事务1查询所有⼯资为1w的员⼯的总数,查询出来了10个⼈,此时事务尚未关闭
    事务2财务⼈员发起,新来员⼯,⼯资1w,向表中插⼊了2条数据,并且提交了事务
    事务1再次查询⼯资为1w的员⼯个数,发现有12个⼈

  • 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)。

    场景:
    员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭
    财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务
    员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读

数据库共定义了四种隔离级别:

  • Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化)
    隔离级别 最⾼
  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣)
    隔离级别第⼆, 该机制下会对要update的⾏进⾏加锁
  • Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。
    隔离级别第三
  • Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交)
    隔离级别最低

注意:级别依次升⾼,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ(可重复读)
查询当前使⽤的隔离级别: select @@tx_isolation;
设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前
mysql连接会话的,并不是永久改变的)

4.5.4 事务的传播⾏为

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。
A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为:

事务行为描述
PROPAGATION_REQUIRED如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择
PROPAGATION_SUPPORTS⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值