目 录CONTENT

文章目录

spring知识点概览

FatFish1
2024-12-18 / 0 评论 / 0 点赞 / 119 阅读 / 0 字 / 正在检测是否收录...

spring概述

  • 提供两个开发技术:IOC、AOP+事务处理

  • 框架整合:MyBatis、MyBatis-Plus、Hibernate、Structs、Structs2等

  • 技术:底层框架Spring Framework;简化开发框架spring boot;分布式开发框架spring cloud

spring framework

IOC - 控制反转

假设有一个需求要我们开发一个Controller-Service-Dao的后端架构,由Controller负责处理web请求,调用对应的service执行具体操作,通过Dao查询数据库

如果没有IOC,我们的开发方式是:

  • 每个Controller主动创建Service实例,Service再创建Dao实例

如果Controller多了,且都在复用Service,且Service还有多种实现类,创建的实例就会非常多,总结开发问题可能有:

  • 创建了许多重复对象,造成大量资源浪费;

  • 耦合性强,更换实现类需要改动多个地方;

  • 创建和配置组件工作繁杂,给组件调用方带来极大不便

其实问题的根源在于:调用方主动进行参与了组件的创建和配置工作

IOC控制反转的含义就是,调用方不再主动进行组件创建,而是交给第三方容器完成,在使用时被动由第三方容器注入一个

相当于齿轮,原本两个齿轮是互相卡死的,一个坏掉,另一个也坏掉,有了IOC容器,则齿轮全都与IOC容器耦合,如果一个齿轮坏掉了,另一个齿轮不至于全部崩盘

为了控制对象创建,spring提供了控制创建对象的容器,即IoC容器,用于存放对象,IoC容器就是IoC思想中的外部,被创建或管理在IoC容器中的对象统称为bean

如果bean之间存在依赖关系,spring的ioc容器还能提供依赖注入的能力(Dependency Injection-DI)

DI - 依赖注入

spring的IOC容器提供的依赖注入有:

  • setter注入:在bean标签中通过配置property标签给属性属性赋值,实际上就是通过反射调用set方法完成属性的注入

  • 构造器注入:使用constructor-arg标签进行属性注入即可

  • 注解属性注入:例如@Autowired等

循环依赖和三级缓存

因为springIOC支持依赖注入,就有可能出现A依赖B,B依赖A这种场景,就是循环依赖

除此之外,还有可能是更大的环,只要是闭环就有可能最终产生循环依赖

解决循环依赖的方案是三级缓存,具体可以参考三级缓存相关源码部分

IOC容器 - bean的创建流程

bean创建流程中的扩展点

在refresh流程对ApplicationContext进行扩展 - ApplicationContextInitializer#initialize

  • 实现方法:实现ApplicationContextInitializer#initialize接口,仅针对ConfigureableApplicationContext类型

  • 触发时机:ApplicationContext#refresh()流程之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理

  • 调用点:常用于SpringMVC的servlet初始化流程FrameworkServlet#configureAndRefreshWebApplicationContext

在refresh流程中新增BeanPostProcessor - AbstractApplicationContext#postProcessBeanFactory

  • 实现方法:AbstractApplicationContext#postProcessBeanFactory是一个默认空实现的方法,是AbstractApplicationContext提供的一个上下文级别的方法,需要自行重写AbstractApplicationContext

  • 触发时机:在加载了BeanDefinition之后执行。往往用于一些顶层设计,例如补充一些BeanPostProcessor进去

  • 调用点:AbstractApplicationContext#refresh方法直接调用

在refresh流程中对BeanDefinition进行扩展 - BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry

  • 实现方法:实现BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry

  • 触发时机:在beanDefinition加载后,bean创建之前对bd做一次扩展,常用于添加一些组件

  • 调用点:在ApplicationContext#refresh()方法中调用AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法,会触发BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 方法的调用

http://www.chymfatfish.cn/archives/applicationcontext#invokebeanfactorypostprocessors

在refresh流程中对BeanFactory进行扩展 - BeanFactoryPostProcessor#postProcessBeanFactory

  • 实现方法:实现BeanFactoryPostProcessor#postProcessBeanFactory

  • 触发时机:在beanDefinition加载后,bean创建之前对BeanFactory做一次扩展,用来定制和修改BeanFactory的内容

  • 调用点:和上面一个一样,在ApplicationContext#refresh方法中调用AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法,会触发BeanFactoryPostProcessor#postProcessBeanFactory 方法的调用

  • 与上面一个的区别:一个注重bd的调整,补充组件,一个注重BeanFactory的定制

http://www.chymfatfish.cn/archives/applicationcontext#invokebeanfactorypostprocessors

在doCreateBean流程之前对bean初始化流程的短路扩展 - InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

  • 实现方法:实现InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

  • 触发时机:加载完bd,要创建bean,在doCreateBean(创建三步)流程之前,createBean方法中还有两次短路的机会。这里是前置短路。AOP的targetSource创建流程是从这里实现的。

  • 调用点:在AbstractAutowireCapableBeanFactory#createBean方法中调用AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation方法,进而调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation触发

在doCreateBean流程中对bean初始化流程的前置扩展 - BeanPostProcessor#postProcessBeforeInitialization

  • 实现方法:实现BeanPostProcessor#postProcessBeforeInitialization

  • 触发时机:bean实例化方法AbstractAutowireCapableBeanFactory#doCreatebean的三步流程中,完成bean实例化、属性注入,还没有进行初始化时调用

  • 调用点:是在doCreateBean第三步AbstractAutowireCapableBeanFactory#initializeBean 中调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization 引入的

在doCreateBean的第三步初始化流程中执行的初始化完成后扩展 - InitializingBean#afterPropertiesSet

  • 实现方法:实现InitializingBean#afterPropertiesSet接口

  • 触发时机:bean实例化方法AbstractAutowireCapableBeanFactory#doCreatebean的三步流程中,完成bean实例化、属性注入,初始化流程开始后完成了默认初始化方法后调用,用于对刚初始化的bean做一些自定义的初始化扩展

  • 调用点:在doCreateBean(创建三步)的第三步AbstractAutowireCapableBeanFactory#initializeBean方法中,会调用AbstractAutowireCapableBeanFactory#invokeInitMethods对bean进行初始化,其中调用此扩展

  • 注意:此扩展的执行时机是在postProcessBeforeInitialization前置处理器和postProcessAfterInitialization后置处理器之间,算是一种对bean初始化方法的扩展

bean初始化完成后 - BeanPostProcessor#postProcessAfterInitialization

  • 实现方法:实现BeanPostProcessor#postProcessAfterInitialization

  • 触发时机:所有可以完成bean加载的场景后面都会调用,例如

    • 断路操作 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 完成后会触发

    • 正常调用AbstractAutowireCapableBeanFactory#initializeBean完成bean加载也会触发,用于对完全填充好的bean做后置扩展,例如AOP的创建

  • 调用点:较多

doCreateBean完成后的扩展操作 - SmartInitializingSingleton#afterSingletonsInstantiated

  • 实现方法:实现SmartInitializingSingleton#afterSingletonsInstantiated方法的单例bean

  • 触发时机:在doCreateBean完成后,用于在Spring容器启动完成时进行扩展操作

  • 调用点:在DefaultListableBeanFactory#preInstantiateSingletons中触发,点我跳转

spring配置文件中的常用标签

beans、bean、alias、import是最常用的四个标签,四大标签解析主题框架可以参考后文:

http://www.chymfatfish.cn/archives/springxml#parsedefaultelement---%E8%A7%A3%E6%9E%90import%E3%80%81alias%E3%80%81bean%E3%80%81beans%E6%A0%87%E7%AD%BE

beans标签

spring配置文件的主体

profile属性

profile属性是做环境配置用的,在java启动时指定当前启动环境的值,这样可以快速实现切换部署环境变量、比如数据库配置等,就会根据对应profile加载对应的bean,例如:

<beans profile="dev,qa">
        <bean id="hadoopSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
            <property name="driverClassName" value="${hadoopDriverName}"></property>
            <property name="url" value="${hadoopUrl}"></property>
            <property name="username" value="${hadoopUserName}"></property>
            <property name="password" value="${hadoopPass}"></property>
        </bean>
</beans>

而在web应用中,web.xml中做如下配置:

<context-param>
    <param-name>Spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

profile属性的加载参考源码部分:

http://www.chymfatfish.cn/archives/springxml#doregisterbeandefinitions---%E8%A7%A3%E6%9E%90profile%E6%A0%87%E7%AD%BE

bean标签

最常用的标签,用于配置spring托管的bean

默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功

bean标签的常见属性

  • id:给对象在容器中提供一个唯一标识。用于获取对象。

  • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。

  • scope:指定对象的作用范围。

    • singleton :默认值,单例的.

    • prototype :多例的.

  • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.

  • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.

  • global session :WEB 项目中,应用在 Portlet 环境.没有 Portlet 环境 globalSession 相当于 session.

  • init-method:指定类中的初始化方法名称。

  • destroy-method:指定类中销毁方法名称。

  • factory-bean关键字表示如果该bean用工厂创建,那工厂类是哪个bean

  • factory-method关键字表示如果该bean是工厂创建,那工厂类哪个方法创建

  • beans中的default-lazy-init属性代表懒加载,即只有使用这个实例才会加载他,如果只是spring创建一个对象,相当于只创建了一个空壳。如果修饰的类只使用了其static方法,也就是没有使用实例,那这种场景就会导致NoClassDefFound报错

  • constructor-arg:注入属性,该标签自动调用构造方法

    • index关键字表示构造方法注入的参数顺序,从0开始

    • ref关键字表示用哪个bean注入

    • 如果bean是工厂类工厂方法构造的,那么constructor-arg表示入参

  • property:也可以注入属性,该标签自动调用的是set方法

作用域

在bean标签可以通过scope属性指定对象的的作用域

  • scope=“singleton” 表示当前bean是单例模式(默认饿汉模式,Spring容器初始化阶段就会完成此对象的创建;当在bean标签中设置 lazy-init="true"变为懒汉模式)

  • scope=“prototype” 表示当前bean为非单例模式,每次通过Spring容器获取此bean的对象时都会创建一个新的对象

id、name、alias属性 - 名称相关

id用于指定这个bean被get的时候的名字是什么,一个接口多个实现类,调用方调用接口,又要指定注入时,可以使用。要注意在一个xml中bean的id是不可重复的

name和alias指定名称和别名。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#beandefinitionholder-parsebeandefinitionelement---%E8%A7%A3%E6%9E%90bean%E6%A0%87%E7%AD%BE%E7%9A%84id%E3%80%81name%E3%80%81alias%E5%B1%9E%E6%80%A7

class、parent属性 - 类定义相关

class用来定义类的全限定名,子bean不用定义该属性

parent属性是子类bean定义它所引用的父类bean的,有parent时class会失效,子类bean继承父类bean所有属性。

abstract属性默认为false,用来定义bean是否是抽象bean。它表示这个bean一般不会被实例化。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#abstractbeandefinition-parsebeandefinitionelement---%E8%A7%A3%E6%9E%90bean%E6%A0%87%E7%AD%BE%E7%9A%84class%E3%80%81parent%E3%80%81description%E5%B1%9E%E6%80%A7

singleton、scope 、lazy-init、abstract、depends-on属性 - 加载相关

singleton默认为true,是老版本属性,现在已不推荐使用。

scope属性是现在使用的,值有singleton和prototype,singleton为默认值,代表单例模式,prototype非单例,即每个注入和getBean都是创建一个新的对象。

<bean id="prototypeTest" class="com.gty.bean.PrototypTest" scope="prototype"/>

lazy-init为懒加载,默认为false,当设置为true时将不会在spring启动时ApplicationContext启动时实例化,而是在第一次getBean的时候实例化。懒加载仅针对scope=”singleton”的bean起作用。

depends-on指向的bean先于此bean加载,晚于此bean销毁。尽管可能二者没有什么实际的依赖或注入关系,但使用depends-on依旧可以强制获得加载时间上的先后顺序。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsebeandefinitionattributes---%E8%A7%A3%E6%9E%90%E5%9F%BA%E7%A1%80%E5%B8%B8%E7%94%A8%E5%B1%9E%E6%80%A7

autowire、autowire-candidate、primary属性 - 依赖注入相关

autowire和@Autowired作用相同,只不过是在xml中配置的。

autowire-candidate属性默认为true,如果设置为false,则该bean不能通过autowire注入到别的bean中。

primary属性是用来反屏蔽autowire-candidate的,通过primary注解,可以让autowire-candidate在指定的类上面不生效。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsebeandefinitionattributes---%E8%A7%A3%E6%9E%90%E5%9F%BA%E7%A1%80%E5%B8%B8%E7%94%A8%E5%B1%9E%E6%80%A7

init-method、destroy-method、factory-method、factory-bean属性 - 初始化相关

init-method它的作用是在创建一个bean之后调用该方法,初始化方法必须是一个无参方法。

destroy-method的作用是在销毁bean之前可以执行指定的方法。注意:必须满足scope=“singleton”,并且destroy方法参数个数不能超过1,并且参数类型只能为boolean。

factory-method、factory-bean是针对使用工厂模式生产的bean。指定工厂类和工厂方法后可以不使用class属性标注bean的类型,就可以返回工厂方法返回对象。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsebeandefinitionattributes---%E8%A7%A3%E6%9E%90%E5%9F%BA%E7%A1%80%E5%B8%B8%E7%94%A8%E5%B1%9E%E6%80%A7

meta子标签

是bean的额外声明,以键值对的形式存储,不存入bean的属性中,当需要使用其中信息可以通过BeanDefinition::getAttribute(key)方法获取。

<bean id="myTestBean" class="bean.MyTestBean">
    <meta key="testStr" value="aaaaaaaa"/>
</bean>

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsemetaelements---%E8%A7%A3%E6%9E%90meta%E5%B1%9E%E6%80%A7

look-method子标签

方法查找。通过@Autowired可以把bean注入到对象中,通过lookup-method可以把bean当作返回对象注入到方法结果中。甚至可以对抽象方法生效。案例如下:

// 调用方
public abstract class GetBeanTest {
    public void showMe(){
        this.getBean().showMe();
    }

    public abstract User getBean();
}
// 注入方
package test.lookup.bean;
public class Teacher {
    public void showMe(){
        System.out.println("i am Teacher");
    }
} 
// xml配置
<bean id="getBeanTest"  class="test.lookup.app.GetBeanTest">
    <lookup-method name="getBean" bean="teacher"/>
</bean>
<bean id="teacher" class="test.lookup.bean.Teacher"/> 

这样就把Teacher注入到了getBean方法种,使方法被调用时,自动获取到Teacher类对象。如果需要修改,只需修改teacer bean对应的class映射即可。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parselookupoverridesubelements---%E8%A7%A3%E6%9E%90lookup-method

replaced-method子标签

当一个类实现了MethodReplacer接口,实现reimplement方法,就具备了替换的能力。例如:

// 要被替换的方法
public class TestChangeMethod {
    public void changeMe(){
        System.out.println("changeMe");
    }
}
public class TestMethodReplacer implements MethodReplacer{
    @Override
    public Object reimplement(Object obj, Method method, Object[] args)throws Throwable {
        System.out.println("我替换了原有的⽅法");
        return null;
    }
}
// xml配置
<bean id="testChangeMethod" class="test.replacemethod.TestChangeMethod">
    <replaced-method name="changeMe" replacer="replacer"/>
</bean>
<bean id="replacer" class="test.replacemethod.TestMethodReplacer"/> 

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsereplacedmethodsubelements

constructor-arg子标签

<beans>
    <!-- 默认的情况下是按照参数的顺序注⼊,当指定index索引后就可以改变注⼊参数的顺序 -->
    <bean id="helloBean" class="com.HelloBean">
        <constructor-arg index="0">
            <value>注入1</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>注入2</value>
        </constructor-arg>
    </bean>
    ... ...
</beans>

这是一个最简单的构造函数的例子,HelloBean类的构造函数需要注入两个String参数,分别是注入1和注入2。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parseconstructorargelements--%E8%A7%A3%E6%9E%90constructor-arg

还有比较复杂的构造函数,即使用子元素作为填充的例如:

<constructor-arg>
    <map>
        <entry key="key" value="value" />
    </map>
</constructor-arg>

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsepropertysubelement---%E8%A7%A3%E6%9E%90%E5%AD%90%E5%85%83%E7%B4%A0

也有直接使用属性和值的例如:

<constructor-arg value="a" >

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsepropertyvalue---%E8%A7%A3%E6%9E%90%E5%B1%9E%E6%80%A7%E5%80%BC

idref子标签

constructor-arg和 property的子标签,idref元素用来将容器内其它bean的id传给<constructor-arg> <property>标签,同时提供错误验证功能。

<bean id="p2" class="com.pojo.Person">
    <property name="pname">
        <idref bean="c3"/>
    </property>
</bean>

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsepropertysubelement---%E8%A7%A3%E6%9E%90%E5%AD%90%E5%85%83%E7%B4%A0

merge属性

merge属性用于将子类的数组元素和父类的相同元素合并,而非覆盖。例如:

// 父类
public class Student {
    private String name;
    private List<Integer> nums;
    ……
}
<!--    xml配置-->
<bean id="baseStudent" class="Student">
    <property name="name"><value>zhangsan</value></property>
    <property name="nums">
        <list>
            <value>1</value>
            <value>2</value>
            <value>3</value>
        </list>
    </property>
</bean>
<bean id="Student"  parent="baseStudent">
    <property name="nums" >
        <list>
            <value>1</value>
            <value>2</value>
        </list>
    </property>
</bean>

这种场景下,子类Student获取到name是zhangsan,nums是12,如果修改Student这个bean的配置,改为<list merge=true>,则name是zhangsan,nums是12312

array、list、set标签

用于注入数组、列表、集合类型,以array为例:

value-type使用其他类型,数组子元素用ref引用bean名称

<bean class="test.ParentBean">
    <property name="subBeans">
        <array value-type="test.SubBean">
            <ref bean = “subBean1”/>
            <ref bean = “subBean2”/>
        </array>
    </property>
</bean>

value-type不加默认为String类型,子元素可以直接用value

<bean class="test.ParentBean">
    <property name="subBeanNames">
        <array>
            <value>subBean1</value>
            <value>subBean2</value>
        </array>
    </property>
</bean>

value使用type属性具有相同的功效

<bean class="test.ParentBean">
    <property name="subBeanNames">
        <array>
            <value type="int">1</value>
            <value type="int">2</value>
        </array>
    </property>
</bean>

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#parsearrayelement%2Fparselistelement%2Fparsesetelement---array%2Flist%2Fset%E8%A7%A3%E6%9E%90

map、property标签

<property name="map">
    <map>
        <entry key="1" value-ref="li"/>
        <entry key="2" value-ref="wang"/>
    </map>
</property>

先看一个property使用map子元素的样例,map子元素下面是entry子元素,有key和value-ref两种属性,通过value-ref可以把bean注入到mapvalue里面,如果是普通字符串,也可以直接使用value

http://www.chymfatfish.cn/archives/springxml#parsemapelement---map%E8%A7%A3%E6%9E%90

qualifier子标签

一般@Autowired注解用的比较多,当bean注入时是参考类,如果实现类有多个,spring会抛BeanCreationException异常。使用qualifier指定bean的id,可以消除歧义。等同于@Autowired加beanname的形式

<bean id="myTestBean" class="bean.MyTestBean">
    <qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/>
</bean>

alias标签

对bean配置别名可能不是在bean创建时就要立即配置的场景,有可能会有单独引入的需要。因此产生了alias标签。使用bean标签配置name属性和使用alias标签配置效果相同,name属性是bean的id,alias属性是对应别名:

<!--    使用bean标签-->
<bean id="testBean" name="testBean,testBean2" class="com.test"/>
<!--    使用alias标签-->
<bean id="testBean" class="com.test"/>
<alias name="testBean" alias="testBean,testBean2"/>

使用alias标签也可以做到为一个bean配置两个不同别名,方便其在不同场景下进行引用,常见场景就是一个数据源给多个环境使用。

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#processaliasregistration---%E8%A7%A3%E6%9E%90alias%E6%A0%87%E7%AD%BE

import标签

用于引用其他spring配置文件,就跟引java一样

<beans>
    <import resource="customerContext.xml" />
    <import resource="systemContext.xml" />
    ... ...
</beans>

解析流程参考源码部分:

http://www.chymfatfish.cn/archives/springxml#importbeandefinitionresource---%E8%A7%A3%E6%9E%90import%E6%A0%87%E7%AD%BE

AOP相关标签

namespace

依赖的namespace是:

xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"

基本标签

<aop:config> 用于声明开始aop的配置
|--- <aop:aspect> 用于配置切面
|--- |--- 属性: id   给切面提供一个唯一标识。
|--- |--- 属性: ref   引用配置好的通知类bean的id。
|--- |--- <aop:pointcut> 用于配置切入点表达式
|--- |--- |--- 属性: expression   用于定义切入点表达式。
|--- |--- |--- 属性: id   用于给切入点表达式提供唯一标识。
|--- |--- <aop:before> 用于配置前置通知
|--- |--- |--- 属性: method   指定通知中方法的名称。
|--- |--- |--- 属性: pointcut   定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref   用于给切入点表达式提供唯一标识。
|--- |--- <aop:after-returning> 用于配置后置通知,出现异常不调用
|--- |--- |--- 属性: method   指定通知中方法的名称。
|--- |--- |--- 属性: pointcut   定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref   用于给切入点表达式提供唯一标识。
|--- |--- <aop:after-returning> 用于配置异常通知
|--- |--- |--- 属性: method   指定通知中方法的名称。
|--- |--- |--- 属性: pointcut   定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref   用于给切入点表达式提供唯一标识。
|--- |--- <aop:after> 用于配置最终通知
|--- |--- |--- 属性: method   指定通知中方法的名称。
|--- |--- |--- 属性: pointcut   定义切入点表达式。
|--- |--- <aop:around> 用于环绕通知
|--- |--- |--- 属性: invokeMethod   指定通知中方法的名称。
|--- |--- |--- 属性: pointcut   定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref   用于给切入点表达式提供唯一标识。

proxy-target-class

还有一个全局标签proxy-target-class,在aop和事务中都生效的,属性值决定是基于接口的还是基于类的代理被创建,为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用

<aop:config proxy-target-class="true">

它的解析参考aop部分

http://www.chymfatfish.cn/archives/spring-aop

spring-jdbc相关标签

依赖的namespace是

xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"

标签包括:

数据源初始化相关

<jdbc:initialize-database data-source="test01db" enabled="true"  ignore-failures="NONE"
                          separator="@@" >
    <jdbc:script location="classpath:init/test01db_1.sql" />
    <jdbc:script location="classpath:init/test01db_2.sql" />
</jdbc:initialize-database>
  • 通过<jdbc:script location>标签指定数据脚本,在初始化的时候可以自动执行。数据脚本指定支持通配符,例如:classpath*:/com/foo/**/sql/*-data.sql

  • data-source参数用于选定由spring管理的数据源,可以通过注入的方式获取。

  • enabled表明初始化数据库是否执行,设置方法有两种:

    • 直接设置,例如enabled=”true”

    • 配置文件读取,例如enabled=’#{systemProperties.INITIALIZE_DATABASE}’

  • ignore-failures属性有三个值:NONE、DROPS、ALL

    • NONE表示不忽略任何错误,sql报错,初始化终止

    • DROPS表示忽略删除错误,当删除语法的表不存在,忽略该错误

    • ALL表示忽略任何错误

  • separator属性是sql语句的分隔标识,可配置,会对script起作用,也可以在jdbc:script中单个配置。优先级是script级别>jdbc级别>默认级别。

    • 默认分隔符为“;”,即sql语句之间用分号隔开,没有分号算一句。

spring中的常用工具类

ApplicationListener

由应用程序事件侦听器实现的接口。

内含onApplicationEvent方法。实现该方法,会在类加载时被执行。

但该方法可能会被加载多次,因此可以用synchronized进行保护

http://www.chymfatfish.cn/archives/applicationcontext#registerlisteners---%E5%87%86%E5%A4%87%E9%98%B6%E6%AE%B5%EF%BC%9A%E6%B3%A8%E5%86%8C%E7%9B%91%E5%90%AC%E5%99%A8

InitializingBean

扩展方法里面最常用的一个,提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法

import org.springframework.beans.factory.InitializingBean;
@Component
public class AfterPropertiesSetTest implements InitializingBean {
@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("ceshi InitializingBean");
    }
}

添加配置文件:

<bean id="testInitializingBean" class="com.TestInitializingBean" ></bean>

Main函数如下

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/com/beans.xml");
    }
}

需要注意的是:如果执行afterPropertiesSet方法抛异常出错了,则无法继续执行bean的初始化,会导致bean初始化失败

spring定时任务

使用@EnableScheduling和@Scheduled(cron = “表达式”)配置在方法上可以实现定时启动任务。

开启定时任务注解要使用@EnableScheduling加到@Configuration定义的配置类上面,或者使用xml配置文件增加<task:annotation-driven>

@Scheduled不安全。在默认情况下,spring只启动一个线程开启定时任务,所有的scheduled会按先后顺序进行执行。如果任务1本次启动阻塞了,那么任务n也会无限等待。因此要防止阻塞,可以需要搞一个线程池,给每次自启动都起一条线程

可以通过@Async、@EnableAsync注解将线程托管给spring

  • @Async修饰方法,可以让spring自己捞一个线程执行该方法。

  • @EnableAsync表示开启对异步任务的支持,可以放在springboo启动类上也可以放在自定义线程池配置上

    • @EnableAsync加在启动类上,@Async修饰方法时,spring自己从默认线程池捞一个线程执行方法,默认线程池为SimpleAsyncTaskExecutor

    • @EnableAsync加在自定义线程池上,可以将线程池托管给spring用于@Async方法捞取线程

@Configuration
@EnableAsync
public class ScheduleConfig {
    @Bean(name = “pool1”)
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(50);
        return taskScheduler;
    }
@EnableScheduling
public class TaskFileScheduleService {
    @Async(“pool1”)
    @Scheduled(cron="0 */1 * * * ?")
    public void task1(){
    .......
    }
    
    @Async
    @Scheduled(cron="0 */1 * * * ?")
    public void task2(){
    .......
    }

spring事务

spring事务有三种开启方法:

  • @Transactional注解开启

  • 实现TransactionTemplate

  • 使用jdbcTemplate自带的事务方案

实现TransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }
            }
        });
}

使用jdbcTemplate自带事务方案

原生jdbc支持事务

DataSource dataSource;
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement(sql);
ps.addBatch();
affectRow = ps.executeBatch();
connection.commit();
connection.setAutoCommit(true);

spring提供的JdbcTemplate中,只需要从JdbcTemplate中获取connection就可以开启原生事务

String sql1 = "INSERT INTO user_tmp(`id`, `username`) VALUES(22, 222)";
String sql2 = "INSERT INTO user_tmp(`id`, `username`) VALUES(1, 111)";
                        
Connection connection = jdbcTemplate.getDataSource().getConnection();
connection.setAutoCommit(false);
jdbcTemplate.batchUpdate(new String[] {sql1, sql2});

注意:NamedParameterJdbcTemplate这种支持具名参数的JdbcTemplate就不支持多sql批处理了,那么开启事务实际上就没啥用了

使用@Transactional注解

最常用,最简单,一般Mybatis等ORM组件都可以用这种方案处理

常用的属性包括:

  • propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为

  • Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。

  • isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。

  • timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。

  • readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。

事务传播

当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播,例如,方法可能继续在当前事务中执行,也可以开启一个新的事务,在自己的事务中执行。可以通过 @Transactional 注解中的 propagation 属性来定义

  • PROPAGATION_REQUIRED:默认值,当前存在事务,则加入该事务;如果当前没有事务,则创建一个

  • 新的事务

  • PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  • PROPAGATION_NESTED:如果当前存在事务,就在当前事务内执行

  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起

  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

回滚策略

回滚策略 rollbackFor ,用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。默认情况下,事务只在出现运行时异常(Runtime Exception)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。

spring管理事务边界是调用业务方法之前开始的,业务方法执行完毕后才执行提交或回滚操作。

@Transactional事务失效场景

  • 作用在非public方法上失效,但在protected和private上加不报错

  • propagation设置错误导致失效

  • rollbackFor指定回滚异常类型导致失效

  • 同个类中this调用被@transactional修饰的方法会失效,spring只对被所在类以外的代码调用,才做事务管理

  • try catch捕获异常会导致回滚失效,除非主动抛出runtimeException。此时要保证事务内容全部在try里,但catch也不能有操作,因为catch内容也会一起回滚

  • 数据库不支持事务

spring深拷贝 - BeanCopier

A. 创建BeanCopier对象

public static BeanCopier create(Class source, Class target, boolean useConverter)
  • 第一个参数source:我们要拷贝的对象

  • 第二个参数target:我们要拷贝成什么样的对象

  • 第三个参数useConverter:用户控制转换器,在下文对这个参数做解释,因为CGLIB是动态代理的,所以有控制反转

用户控制转换器,如果选择为false,它会对拷贝的对象和被拷贝的对象的类型进行判断,如果类型不同就不会拷贝,如果要使他会拷贝,就需要设置为true,自己拿到控制权自己对其进行处理,一般情况下我们都是使用的false

BeanCopier studentCopier = BeanCopier.create(Student.class, Student.class, false);
Student baseStudent = new Student(“name”, 19);
Student student = new Student();
studentCopier.copy(baseStudent, student, null);

spring上下文感知 - ApplicationContextAware

通过实现ApplicationContextAware接口让类获得上下文感知的能力

Aware顾名思义,就是意识到,一个类能意识到ApplicationContext,也就是说它可以获取spring上下文环境了

public class MyApplicationContextAware implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) {    
        this.applicationContext = applicationContext;    
    }

    // 支持对本身、父类、抽象类、接口的获取
    public Object getBean(String name, Class<?> type) {    
        return applicationContext.getBean(name, type);    
    }    
}

ReflectionUtils - 反射工具类

使用spring的反射工具类可以极大简化反射代码,据说性能比jdk原生反射要好哦

MethodFilter - 方法过滤器

方法过滤器用于创建一个在所有方法中找到想要方法的过滤器。

spring提供了一个基础过滤器,可以帮开发找到用户声明方法:

public static final MethodFilter USER_DECLARED_METHODS =
                   (method -> !method.isBridge() && !method.isSynthetic() && (method.getDeclaringClass() != Object.class));

同时提供and方法用于过滤器条件拼接,在and方法中只需要写lambda表达式即可,例如:

ReflectionUtils.USER_DECLARED_METHODS
                            .and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null));

创建方法过滤器后,spring提供了doWithMethods方法

public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf)

参数1代表要找的类,参数2是找到后的回调方法,参数3是方法过滤器。

spring反射工具在AOP流程中有很典型的使用:ReflectiveAspectJAdvisorFactory#getAdvisorMethods

ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);

其中是在aspectClass中找符合adviceMethodFilter的方法,找到加入到列表methods中。

AnnotationUtils - 注解工具类

findAnnotation - 找注解

public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType)

参数1是方法,参数2是注解类型

在spring-aop中的使用示例:AbstractAspectJAdvisorFactory#isAspect

A result = AnnotationUtils.findAnnotation(method, toLookFor);

isCandidateClass - 查看是否匹配

public static boolean isCandidateClass(Class<?> clazz, @Nullable Class<? extends Annotation> annotationType)

AnnotatedElementUtils - 注解属性工具类

findMergedAnnotationAttributes - 找方法上的注解中的属性

AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
                            Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap)

入参AnnotatedElement实际上可以传入Method对象,是它的顶层接口

参数Class就是注解类型。

返回值是AnnotationAttributes是由spring封装的注解属性。

其经典调用点就是spring事务中的调用:SpringTransactionAnnotationParser#parseTransactionAnnotation

hasAnnotation

判断类上有没有某个注解

boolean b = AnnotatedElementUtils.hasAnnotation(AspectJTest.class, Pointcut.class);

AnnotatedMetadata - 注解元数据类

    // 此方法是否标注有此注解,annotationName:注解全类名
	boolean isAnnotated(String annotationName);
	
	//取得指定类型注解的所有的属性 - 值(k-v)
	// classValuesAsString:若是true表示 Class用它的字符串的全类名来表示。可以避免Class被提前加载
	@Nullable
	Map<String, Object> getAnnotationAttributes(String annotationName);
	@Nullable
	Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
 
	// 参见这个方法的含义:AnnotatedElementUtils.getAllAnnotationAttributes
	@Nullable
	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
	@Nullable
	MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

AnnotatedTypeMetadata - 被注解类的元数据类

这个类是被注解的类的元数据类,提供的方法是找这个类上面的注解

// 判断这个类是否被某个特定注解注解了
default boolean isAnnotated(String annotationName)
// 获取这个类上面所有注解
MergedAnnotations getAnnotations();
// 获取这个类的特定注解的所有注解属性
default Map<String, Object> getAnnotationAttributes(String annotationName)

AopUtils - aop工具类

判断切面是否应用于方法

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz)

BeanFactoryUtils - Bean工厂工具类

BeanFactoryUtils工具类是在 bean 工厂上运行的便捷方法,可以方便地返回 bean 计数、bean 名称或 bean 实例

例如beansOfTypeIncludingAncestors方法可以返回给定类型或子类型的所有 bean,使用案例参考MVC部分

http://www.chymfatfish.cn/archives/spring-mvc#inithandlermappings

spring源码架构

DefaultListableBeanFactory - 核心bean工厂的血缘

可以看到一路继承下来的一些能力,包括:

  • AliasRegistry:定义对alias的简单增删改等操作。

  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。

  • SingletonBeanRegistry:定义对单例的注册及获取。

  • BeanFactory:定义获取bean及bean的各种属性。

  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。

  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。

  • BeanDefinitionRegistry:定义对BeanDefinition 的各种增删改操作。

  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对 FactoryBean的特殊处理功能。

  • ConfigurableBeanFactory:提供配置Factory的各种方法。

  • ListableBeanFactory:根据各种条件获取bean的配置清单。

  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和 ConfigurableBeanFactory的功能。

  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器。

  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapable BeanFactory进行实现。

  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。

  • DefaultListableBeanFactory:综合上面所有功能,主要是对bean注册后的处理。

其实这些内容基本上就是spring容器提供的全部核心功能了

可以说,DefaultListableBeanFactory是一个很成熟的Bean工厂,可以提供完善的Bean创建流程的支持,同时它也是自定义BeanFactory的父类,其他有各类需求的BeanFactory都是基于DefaultListableBeanFactory衍生的

0

评论区