【拉勾笔记】 spring 学习笔记

231 阅读21分钟

Spring高级框架

Spring 概述

Spring的优势

  • 方便解耦,简化开发
  • AOP编程的支持
  • 声明式事务控制
  • 方便程序的测试
  • 方便集成各种优秀框架
  • 降低JavaEE API的使用难度
  • 源码是经典的Java学习范例

spring的核心结构

  • 数据处理模块

  • Web模块

  • AOP/Aspect模块

  • Core Container模块

  • Test模块

核心思想 Ioc 和 AOP

IoC

  • 什么是IoC

    IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现。我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使⽤哪个对象,去问IoC容器要。即我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列事情)

  • IoC解决了什么问题

    Ioc解决了对象间耦合的问题

  • Ioc和DI的区别

    DI:dependancy injection (依赖注入),依赖注入和IOC描述的是同一件事情,表示IOC 容器根据这个类需要的依赖对象,给这个类注入对应的对象。可以看到依赖注入是站在IOC容器的角度。而IOC控制翻转是站在类或者Bean 的角度,将控制创建对象的权利交给了IOC容器。

AOP

AOP:Aspect oridented programming(面向切面编程)。就是为了将不同业务间的相同业务带来抽离出来做成一个切面。然后通过动态代理的的方式将这部分相同的业务代码注入到原业务逻辑中,从而实现原业务逻辑的增强。从根本上解耦,避免横切逻辑代码的重复。

Spring IoC 高级应用

基础知识

  • IoC基础

    • BeanFactory与ApplicationContext的区别

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

    • 启动IoC容器的方式

      • Java环境下启动IoC容器

        ClassPathXmlApplicationContext :从类的根路径加载配置文件(相对路径)。 FileSystemXmlApplicationContext: 从磁盘路径加载配置文件(绝对路径)。 AnnotationConfigApplicationContext:纯注解模式下启动spring 容器。

        • 纯注解:

          new Annotation ConfigApplicationContext(SpringConfig.class);
          
      • Web环境下启动IoC容器

        • xml

          在web.xml 中配置ContextLoaderListener 监听器去加载xml

          <context-param>
           	<param-name>contextConfigLocation</param-name>
           	<param-value>classpath:applicationContext.xml</param-value>
           </context-param>
           <!--使⽤监听器启动Spring的IOC容器-->
           <listener>
           	<listener-class>org.springframework.web.context.ContextLoaderListener
           	</listener-class>
           </listener>
          
        • 纯注解

          ContextLoaderListener 监听器去加载注解配置类

          <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
           <context-param>
           	<param-name>contextClass</param-name>
           	<param-value>org.springframework.web.context.support.AnnotationConfigWebAppli
          cationContext
          	</param-value>
           </context-param>
           <!--配置启动类的全限定类名-->
           <context-param>
           	<param-name>contextConfigLocation</param-name>
           	<param-value>com.lagou.edu.SpringConfig</param-value>
           </context-param>
           <listener>
             <!--使用监听器启动Spring的IOC容器-->
           	<listener-class>org.springframework.web.context.ContextLoaderListener
           	</listener-class>
           </listener>
          
          
    • 实例化Bean的三种⽅式

      • 1.使用无参构造器(推荐)

        在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。

      • 为了将自己new的类加入SpringIoC容器管理

        • 2.静态方法

          <!--使⽤静态⽅法创建对象的配置⽅式-->
          <bean id="transferService" class="com.lagou.factory.BeanFactory"
           factory-method="getTransferService"></bean>
          
        • 3.实例化⽅法创建

          <!--使⽤实例⽅法创建对象的配置⽅式-->
          <bean id="beanFactory"
          class="com.lagou.factory.instancemethod.BeanFactory"></bean>
          <bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
          
    • Bean的作用范围及不同作用范围的生命周期

    Bean 在创建的时候可以通过scope 来选择作用范围。

    单例模式(singleton): 在创建容器时,也就是项目启动初始化阶段,对象就会被创建。只有当容器销毁时,对象才会被销毁,也就是说和IOC容器的生命周期是一样的。

    多例模式(prototype): 在需要使用这个对象时,就会创建新的对象实例。ioc 容器只负责创建 对象,不负责销毁。当对象一直被使用时,就会存活,当对象没有使用时,就会等待JVM 垃圾回收销毁。

    • 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配置

      • 依赖注入分类

        • 按照注⼊的⽅式分类

          • 构造函数注入

                      <!--name:按照参数名称注入,index按照参数索引位置注入-->
             <!--<constructor-arg index="0" ref="connectionUtils"/>
                    <constructor-arg index="1" value="zhangsan"/>
                    <constructor-arg index="2" value="1"/>
                    <constructor-arg index="3" value="100.5"/>-->
            
                    <constructor-arg name="connectionUtils" ref="connectionUtils"/>
                    <constructor-arg name="name" value="zhangsan"/>
                    <constructor-arg name="sex" value="1"/>
                    <constructor-arg name="money" value="100.6"/>
            
          • set方法注入

        • 按照注⼊的数据类型分类

          • 基本类型和String

            <property name="" value=""></property>
            
          • 其他Bean类型

            <property name="" ref=""></property>
            
          • 复杂类型(集合类型)

                <property name="myArray">
                        <array>
                            <value>array1</value>
                            <value>array2</value>
                            <value>array3</value>
                        </array>
                    </property>
            
                    <property name="myMap">
                        <map>
                            <entry key="key1" value="value1"/>
                            <entry key="key2" value="value2"/>
                        </map>
                    </property>
            
                    <property name="mySet">
                        <set>
                            <value>set1</value>
                            <value>set2</value>
                        </set>
                    </property>
            
                    <property name="myProperties">
                        <props>
                            <prop key="prop1">value1</prop>
                            <prop key="prop2">value2</prop>
                        </props>
                    </property>
            
            
    • DI依赖注入的xml与注解结合模式

      注意:

      1)实际企业开发中,纯xml模式使⽤已经很少了

      2)引⼊注解功能,不需要引⼊额外的jar 3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始

      4)哪些bean的定义写在xml中,哪些bean的定义使⽤注解 第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池 自己开发的bean定义使用注解

      • xml中标签与注解的对应(IoC)

      • DI依赖注入的注解实现方式

        • @Autowired

          • 结合@Qualifier("")
        • @Resource

          @Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。 @Resource 默认按照 ByName ⾃动注⼊。 @Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包

      • 开启注解扫描

        引入spring context命名空间

         <beans    
                ...
                xmlns:context="http://www.springframework.org/schema/context"
            http://www.springframework.org/schema/context
                ...>
             <!--开启注解扫描,base-package指定扫描的包路径-->
            <context:component-scan base-package="com.lagou.edu"/>
        </beans>
           
        
    • 纯注解模式

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

      <!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      

高级特性

  • lazy-Init 延迟加载

    Bean的延迟加载(延迟创建) ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前 实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。 设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第⼀次向容器 通过 getBean 索取 bean 时实例化的。 xml:lazy-init 在容器层次中通过在 元素上使⽤ "default-lazy-init" annotation:@Lazy

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

  • 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;
     }
    }
    

    我们自定义一个CompanyFactoryBean让其实现FactoryBean接口,并将其配置到Spring容器中。

    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>
    
  • 后置处理器

    Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和 BeanFactoryPostProcessor,两者在使⽤上是有所区别的。 ⼯⼚初始化(BeanFactory)—> Bean对象 在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情 在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处 理做⼀些事情 注意:对象不⼀定是springbean,⽽springbean⼀定是个对象

    • BeanFactoryPostProcessor

    调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成 BeanDefinition对象.所以可以进行一些definition处理。BeanFactoryPostProcessor接口只提供了一个方法 postProcessBeanFactory(ConfigurableListableBeanFactory var1) 。在ConfigurableListableBeanFactory中可以取得BeanDefinition。 经典应用:替换占位符

    • BeanDefinition ->解析xml

      BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition

    • SpringBean生命周期图

    • BeanPostProcessor

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

Spring IoC 源码深度剖析

设计优雅

  • 设计模式

注意:原则、方法和技巧

  • 原则

    定焦原则:抓主线 宏观原则:站在上帝视⻆,关注源码结构和业务流程(淡化具体某⾏代码的编写细节)

  • 方法和技巧

    断点(观察调⽤栈) 反调(Find Usages) 经验(spring框架中doXXX,做具体处理的地⽅)

Spring IoC容器初始化主体流程

  • IoC容器体系

  • Bean生命周期关键时机点

    AbstractApplicationContext#refresh()函数

  • Spring IoC容器初始化主流程

    @Override
    public void refresh() throws BeansException, IllegalStateException {
     synchronized (this.startupShutdownMonitor) {
         // 第⼀步:刷新前的预处理
         prepareRefresh();
         /*
         第⼆步:
         获取BeanFactory;默认实现是DefaultListableBeanFactory
         加载BeanDefition 并注册到 BeanDefitionRegistry
         */
         ConfigurableListableBeanFactory beanFactory =
        obtainFreshBeanFactory();
         // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加
        载器等)
         prepareBeanFactory(beanFactory);
         try {
             // 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
             postProcessBeanFactory(beanFactory);
             // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
             invokeBeanFactoryPostProcessors(beanFactory);
             // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
            ⾏
             registerBeanPostProcessors(beanFactory);
             // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
             initMessageSource();
             // 第⼋步:初始化事件派发器
             initApplicationEventMulticaster();
             // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
             onRefresh();
             // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
             registerListeners();
             /*
             第⼗⼀步:
             初始化所有剩下的⾮懒加载的单例bean
             初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
             填充属性
             初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
             调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
             */
             finishBeanFactoryInitialization(beanFactory);
             /*
             第⼗⼆步:
             完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
            件 (ContextRefreshedEvent)
             */
             finishRefresh();
        }
    

BeanFactory创建流程

  • 获取BeanFactory子流程

    在容器初始化主体流程的refresh()方法的第二步 obtainFreshBeanFactory() 获取BeanFactory

    AbstractRefreshableApplicationContextprotected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    		refreshBeanFactory();
    		//获取Beanfactory
    		return getBeanFactory();
    	}
    
    
    protected final void refreshBeanFactory() throws BeansException {
    		//判断BeanFactory 中是否存在Bean。存在就销毁Bean 并关闭beanfactory
    		if (hasBeanFactory()) {
    			destroyBeans();
    			closeBeanFactory();
    		}
    		try {
    			// 创建一个DefaultListableBeanFactory
    			DefaultListableBeanFactory beanFactory = createBeanFactory();
    			//序列化beanFactory的id
    			beanFactory.setSerializationId(getId());
    
    			//自定义Bean工厂的一些属性
    			customizeBeanFactory(beanFactory);
    			//加载应用的beanDefitions
    			loadBeanDefinitions(beanFactory);
    			synchronized (this.beanFactoryMonitor) {
    				this.beanFactory = beanFactory;
    			}
    		}
    		catch (IOException ex) {
    			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    		}
    	}
    。
    

    时序图如下:

  • BeanDefinition加载解析及注册子流程

    Resource定位:指对BeanDefifinition的资源定位过程。通俗讲就是找到定义 Javabean 信息的XML⽂

    件,并将其封装成Resource对象。

    BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefifinition

    所谓注册就是将 BeanDefinition 交给BeanFactory中维护的一个CurrentHashMap管理。

Bean创建流程

在初始化容器的第十一步中,

AbstractApplicationContext#refresh()#finishBeanFactoryInitialization#preInstantiateSingletons将初始化所有剩下的Singleton beans

	/**
	 * Finish the initialization of this context's bean factory,
	 * initializing all remaining singleton beans.
	 */
	protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}

		// Register a default embedded value resolver if no bean post-processor
		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}

		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}

		// Stop using the temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(null);

		// Allow for caching all bean definition metadata, not expecting further changes.
		beanFactory.freezeConfiguration();

		/*
		* 实例化所有........*/
		// Instantiate all remaining (non-lazy-init) singletons.
		beanFactory.preInstantiateSingletons();
	}

继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例(getbean -> doGetBean ->creatBean -> doCreatBean) ,按照Spinrg代码命名逻辑我们最终找到AbstractAutowireCapableBeanFactory#doCreateBean方法。

	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
		if (instanceWrapper == null) {
			//创建 Bean实例 ,仅仅调用构造方法,但是未设置属性
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
...
		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//Bean属性填充
			populateBean(beanName, mbd, instanceWrapper);
			//调用初始化方法,应用BeanPostProcessor后置处理器
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
...

进入initializeBean()方法

	protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			//调用XXAware方法
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
		   //调用BeanPostProcessor预初始化方法
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			//	调用init-method
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
		//   调用BeanPostProcessor后初始化方法(AOP在这里实现动态代理)
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

总结上述过程可以得到Bean生命周期图:

lazy-init延迟加载机制原理

public void preInstantiateSingletons() throws BeansException {
       ...
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		//初始化容器时并不init懒加载的Bean
		for (String beanName : beanNames) {
		...
			}
		}

​ 普通 Bean 的初始化是在容器启动初始化阶段执⾏的,而被lazy-init=true修饰的 bean 则是在从容器⾥ 第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解 析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个 BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进 ⾏初始化并依赖注⼊。

总结

  • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进行init并且依赖注⼊,当第⼀次进行getBean时候才进⾏初始化并依赖注⼊
  • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来

IoC循环依赖问题

​ 循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A 依赖于B,B依赖于C,C⼜依赖于A。

Spring中循环依赖场景有:

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

​ 因为Spring的解决循环依赖的理论基于java的引用传递,当获取对象引用用时,对象的属性是可以延后设置的,但是构造器是在获取引用之前调用,所以无法解决构造器的循环依赖。构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法,即将没有设置属性值的对象引用提前暴露。

​ Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对 象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法 之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

  • 循环依赖处理机制

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

    对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依 赖,Spring都 会直接报错处理。 单例bean通过setXxx或者@Autowired进⾏循环依赖 Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延 后设置的,但是构造器必须是在获取引⽤之前 Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对 象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法 之前就把ClassA实例化的对象通过ObjectFactory提前暴露到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);
     }
     });
     }
    

循环依赖的文章:【拉勾笔记】 spring 之循环依赖

Spring AOP 高级应用

AOP本质

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

AOP相关术语

连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接 点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点

**切入点:**指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法,Spring通过AspectJ语法描述 Advice增强: 第⼀个层次:指的是横切逻辑 第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体 的特殊时机) Aspect切面:切⾯概念是对上述概念的⼀个综合 Aspect切面= 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑

Spring中AOP的AOP代理选择

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

Spring中AOP的配置方式

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

Spring中AOP的实现

五种Advice类型:

1.前置通知

2.后置通知

2.异常通知

4.最终通知

5.环绕通知

  • 使用XML配置

    • 引入坐标

      <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-aop</artifactId>
       <version>5.1.12.RELEASE</version>
      </dependency>
      <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjweaver</artifactId>
       <version>1.9.4</version>
      </dependency>
      
    • AOP 核心配置

      <beans xmlns="
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="
              http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd
      ">
      
      
      <!--aspect   =    切入点(锁定方法) + 方位点(锁定方法中的特殊时机)+ 横切逻辑 -->
          <aop:config>
              <aop:aspect id="logAspect" ref="logUtils">
      
                  <!--切入点锁定我们感兴趣的方法,使用aspectj语法表达式-->
                  <!--<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"/>-->
                  <aop:pointcut id="pt1" expression="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
      
      
                  <!--方位信息,pointcut-ref关联切入点-->
                  <!--aop:before前置通知/增强-->
                  <aop:before method="beforeMethod" pointcut-ref="pt1"/>
                  <!--aop:after,最终通知,无论如何都执行-->
                  <!--aop:after-returnning,正常执行通知-->
                  <aop:around method="arroundMethod" pointcut-ref="pt1"/>
      
              </aop:aspect>
          </aop:config>
      
  • 使用XML+注解

    • XML 中开启 Spring 对注解 AOP 的⽀持

      <!--开启spring对注解aop的⽀持-->
      <aop:aspectj-autoproxy/>
      
    • 切面类代码:

      @Component
      @Aspect
      public class LogUtil {
       /**
      * 第⼀步:编写⼀个⽅法
       * 第⼆步:在⽅法使⽤@Pointcut注解
       * 第三步:给注解的value属性提供切⼊点表达式
       * 细节:
       * 1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。
       * 2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。
       */
           @Pointcut("execution(* com.lagou.service.impl.*.*(..))")
           public void pointcut(){}
           
           @Before("pointcut()")
           public void beforePrintLog(JoinPoint jp){
               Object[] args = jp.getArgs();
               System.out.println("前置通知:beforePrintLog,参数是:"+
              Arrays.toString(args));
           }
           @AfterReturning(value = "pointcut()",returning = "rtValue")
               public void afterReturningPrintLog(Object rtValue){
               System.out.println("后置通知:afterReturningPrintLog,返回值
              是:"+rtValue);
           }
           @AfterThrowing(value = "pointcut()",throwing = "e")
               public void afterThrowingPrintLog(Throwable e){
               System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
           }
           @After("pointcut()")
               public void afterPrintLog(){
               System.out.println("最终通知:afterPrintLog");
           }
           /**
           * 环绕通知
           * @param pjp
           * @return
           */
           @Around("pointcut()")
           public Object aroundPrintLog(ProceedingJoinPoint pjp){
               //定义返回值
               Object rtValue = null;
               try{
                   //前置通知
                   System.out.println("前置通知");
                   //1.获取参数
                   Object[] args = pjp.getArgs();
                   //2.执⾏切⼊点⽅法
                   rtValue = pjp.proceed(args);
                   //后置通知
                   System.out.println("后置通知");
                   }catch (Throwable t){
                   //异常通知
                   System.out.println("异常通知");
                   t.printStackTrace();
               }finally {
                   //最终通知
                   System.out.println("最终通知");
               }
               return rtValue;
           }
      }
      
  • 使用纯注解配置

    替换配置文件中开启的功能的配置。

    在config 中增加配置

    @EnableAspectJAutoProxy 
    
  • 关于AspectJ切⼊点表达式

    切⼊点表达式使⽤示例:

    全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
     全匹配⽅式:
     public void
    com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    om.lagou.pojo.Account)
     访问修饰符可以省略
     void
    com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    om.lagou.pojo.Account)
     返回值可以使⽤*,表示任意返回值
     *
    com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    om.lagou.pojo.Account)
    包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
     *
    ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Accou
    nt)
     包名可以使⽤..表示当前包及其⼦包
     *
    ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account
    )
     类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
     * ...(com.lagou.pojo.Account)
     参数列表,可以使⽤具体类型
     基本类型直接写类型名称 : int
     引⽤类型必须写全限定类名:java.lang.String
     参数列表可以使⽤*,表示任意参数类型,但是必须有参数
     * *..*.*(*)
     参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
     * *..*.*(..)
     全通配⽅式:
     * *..*.*(..)
    

Spring声明式事务的支持

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

  • 事务的四大特性

    ACID 四大特性

    • 原⼦性(Atomicity):表示事务的操作是原子的,不可分割的最小单元。要么全部执行,要么全部不执行。

    • ⼀致性(Consistency):表示事务发生钱前后,数据库的状态是一致的。

    • 隔离性(Isolation):表示事务的执行是相互隔离的,一个事务的执行不能影响另一个事务。

      比如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)

    • 持久性(Durability):事务操作改变数据库的数据是永久性的,不可以回退的,也就是说事务一旦提交,那数据库的数据就会修改。同时数据库发生故障也不会对数据产生影响。

  • 事务隔离级别

    不考虑隔离级别,会出现以下情况:即为隔离级别在解决事务并发问题 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样) 场景: 员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭 财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务 员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条 数不⼀样)

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

    MySQL的默认隔离级别是:REPEATABLE READ

  • 事务的传播行为

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

  • Spring中的事务API

    PlatformTransactionManager

    public interface PlatformTransactionManager {
     	/**
     	* 获取事务状态信息
        */
         TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException;
         /**
         * 提交事务
         */
         void commit(TransactionStatus status) throws TransactionException;
         /**
         * 回滚事务
         */
         void rollback(TransactionStatus status) throws TransactionException;
    }
    

    作用 此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层 ⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。在Spring框架中,也为我们 内置了⼀些具体策略。略,例如:DataSourceTransactionManager , HibernateTransactionManager 等等。( DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代理)来将事务控制逻辑织⼊到业务代码

  • Spring 声明式事务配置

    实质就是一个切面

    • 纯xml

      <tx:advice id="txAdvice" transaction-manager="transactionManager">
       <!--定制事务细节,传播⾏为、隔离级别等-->
       <tx:attributes>
       <!--⼀般性配置-->
       <tx:method name="*" read-only="false"
      propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
       <!--针对查询的覆盖性配置-->
       <tx:method name="query*" read-only="true"
      propagation="SUPPORTS"/>
       </tx:attributes>
       </tx:advice>
       <aop:config>
       <!--advice-ref指向增强=横切逻辑+⽅位-->
       <aop:advisor advice-ref="txAdvice" pointcut="execution(*
      com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
       </aop:config>
      
    • 半注解

      xml配置

      <!--配置事务管理器-->
      <bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManage
      r">
       <property name="dataSource" ref="dataSource"></property>
      </bean>
      <!--开启spring对注解事务的⽀持-->
      <tx:annotation-driven transaction-manager="transactionManager"/>
      

      在接⼝、类或者⽅法上添加@Transactional注解

      @Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
      
    • 全注解

      在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可

      @EnableTransactionManagement//开启spring注解事务的⽀持
      public class SpringConfiguration {
      }
      

Spring AOP 源码深度剖析

代理对象创建流程

【拉钩笔记】Spirng 之AOP源码分析

Spring声明式事务控制

@EnableTransactionManagement 注解
1)通过@import引⼊了TransactionManagementConfigurationSelector类
 它的selectImports⽅法导⼊了另外两个类:AutoProxyRegistrar和

ProxyTransactionManagementConfiguration
2)AutoProxyRegistrar类分析
 ⽅法registerBeanDefinitions中,引⼊了其他类,通过
 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引⼊

InfrastructureAdvisorAutoProxyCreator,
 它继承了AbstractAutoProxyCreator,是⼀个
后置处理器类
3)ProxyTransactionManagementConfiguration 是⼀个添加了@Configuration注解的配置类
(注册bean)
 注册事务增强器(注⼊属性解析器、事务拦截器)
 属性解析器:AnnotationTransactionAttributeSource,内部持有了⼀个解析器集合
 Set<TransactionAnnotationParser> annotationParsers;
 具体使⽤的是SpringTransactionAnnotationParser解析器,⽤来解析
@Transactional的事务属性
 事务拦截器:TransactionInterceptor实现了MethodInterceptor接⼝,该通⽤拦截
会在产⽣代理对象之前和aop增强合并,最终⼀起影响到代理对象
 TransactionInterceptorinvoke⽅法中invokeWithinTransaction会触发原有业
务逻辑调⽤(增强事务)