spring

2019-12-17  本文已影响0人  G_XM_38d3

IOC

Bean的装配

自动化装配

自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。

Spring从两个角度来实现自动化装配:

通过Java代码装配bean

想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component@Autowired注解的,因此就不能使用自动化装配的方案了。

通过@Configuration@Bean

通过XML装配bean

该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。

    <!-- 1.通过其它bean -->
    <constructor-arg ref="compactDisc">
    <!-- 2.通过参数 -->
    <constructor-arg value="sgt"/>
    <constructor-arg value="beatles"/>
    <!-- 3.通过list/set -->
    <constructor-arg>
        <list>
            <value> value1</value>
            <value> value2</value>
            <value> value3</value>
            <ref bean="bean1"></ref>
            <ref bean="bean2"></ref>
            <ref bean="bean3"></ref>
        </list>
        <set>
            <value> value4</value>
            <ref bean="bean4"></ref>
        </set>
    </constructor-arg>
</bean>

当Spring遇到这个<bean>元素时,它会创建一个CDPlayer实例。<constructor-arg>元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。

使用<constructor-arg>元素进行构造器参数的注入。除了使用“ref”属性来引用其他的bean,还可以使用value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。

<list>元素是<constructor-arg>的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>元素用来指定列表中的每个元素。也可以使用<ref>元素替代<value>,实现bean引用列表的装配。

<bean id="cdPlayer" class="soundSystem.CDPlayer">
    <property name="compactDisc" ref="compactDisc" />
    <property name="title" value="july"/>
    <property name="artist" value="jay"/>
    <property name="tracks">
        <list>
            <value> value1</value>
            <value> value2</value>
            <value> value3</value>
        </list>
    </property>
</bean>

<property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。在本例中,它引用了ID为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法。

bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。

在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。

有时候,可能会发现,你所使用的类是易变的(mutable),它们会保持一些状态,因此重用是不安全的。在这种情况下,将class声明为单例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题。

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。

运行时值注入

将一个值注入到bean的属性或者构造器参数中。

在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。

Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称。

AOP

Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。如果你的AOP需求超过了简单的方法调用(如构造器或属性拦截),那么你需要考虑使用AspectJ来实现切面。

AOP术语

通知(Advice)

切面的工作被称为通知,通知定义了切面是什么以及何时使用。

Spring切面可以应用5种类型的通知:

连接点(Join point)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Poincut)

如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。

我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

切面(Aspect)

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

引入(Introduction)

引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。

在目标对象的生命周期里有多个点可以进行织入:

Spring AOP

代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

因为Spring基于动态代理,所以Spring只支持方法连接点。这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。而且它不支持构造器连接点,我们就无法在bean创建时应用通知。

关于环绕通知

环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。

关于这个新的通知方法,它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPointproceed()方法。需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要的效果,但更多的情况是你希望在某个点上执行被通知的方法。

有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试。

SpringMVC

spring mvc流程.png

[^] springMVC调用流程

  1. DispatcherServlet:`Spring MVC所有的请求都会通过一个前端控制器(front controller)Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。任务是将请求发送给Spring MVC控制器(controller)。

  2. controller控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet以会查询一个或多个处理器映射(handler mapping) 来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。

  3. 一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。控制器将业务逻辑委托给一个或多个服务对象进行处理。

  4. 在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP。控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet

  5. DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现。

  6. 既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP) ,在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。

Spring事务

所谓的事物管理指的是“按照指定事物规则执行提交或者回滚操作”,而spring的事物管理,指的是spring对事物管理规则的抽象。具体来说就是spring不直接管理事物,而是为了不同的平台(如jdbc hibernate jpa 等)提供了不同的事物管理器 ,将具体的事物管理委托给相应的平台实现,spring并不关心具体事物管理的实现,从而为事物管理提供了通用编程模型。

spring事务管理器

jdbc事物管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
 </bean>

hibernate事物管理器

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

其它事务管理器

org.springframework.orm.jpaorg.springframework.transaction.jta.JtaTransactionManagerorg.springframework.jms.connection.JmsTransactionManager

spring 事物的基本行为

事物的相关属性就定义在java.sql.Connection.TransactionDefinition类中

    /**
     * 获取传播行为
     */
    int getPropagationBehavior();

    /**
     * 获取隔离级别
     */
    int getIsolationLevel();

    /**
     * 获取超时时间
     */
    int getTimeout();

    /**
     * 获取只读属性
     */
    boolean isReadOnly();

    /**
     * 获取事物的名称 默认为classname+"."+methodName
     */
    String getName();

事物的传播行为

所谓事物的传播行为,指的是被事物标记的方法(注解或者xml声明),之间进行嵌套调用时事物的传播规则。拿jdbc事物管理器来说,就是共用同一个jdbc connection,还是新建connection,还是抛异常。这个规则就叫做事物的传播行为。

传播行为 说明
PROPAGATION_REQUIRED 如果存在事务,则加入当前事务。如果不存在事务,则新建。 默认传播行为
PROPAGATION_SUPPORTS 支持事务,如果不存在事务则以非事务的状态运行。
PROPAGATION_MANDATORY 必须存在事务,不存在抛异常。
PROPAGATION_NEVER 不支持事务,如当前存在事务则抛异常。
PROPAGATION_REQUIRES_NEW 无论当前是否存在事务,都新建事务。 仅支持JtaTransactionManager作为事务管理器。
PROPAGATION_NOT_SUPPORTED 不支持事务,如当前存在事务则将当前事物挂起,以非事务的方式运行 仅支持JtaTransactionManager作为事务管理器。
PROPAGATION_NESTED 嵌套事务。如果当前不存在事务以PROPAGATION_REQUIRED的方式运行。如果存在事务则以嵌套事务的方式运行。 仅支持DataSourceTransactionManager作为事务管理器和部分JTA事务管理器

关于PROPAGATION_NESTED

@Transactional(propagation = Propagation.REQUIRED)
    void methodA(){
        //doSomethingA()
        ((ServiceName)AopContext.currentProxy()).methodB();
    }

    @Transactional(propagation = Propagation.NESTED)
    void methodB(){
        //doSomethingB()
        //successOrNot
    }

单独调用methodB 时,相当于 PROPAGATION_REQUIRED。当调用methodA时,则以嵌套事物的方式运行,methodB作为methodA的子事物,提交和回滚都会受methodA的事物的影响。

一般我们用PROPAGATION_NESTED来执行分支逻辑。当methodB执行失败的时候,则methodA 回滚到之前的保存点,然后执行catch块中的其它业务逻辑,就像methodB从未执行过一样。这也是PROPAGATION_NESTED最常用的用法。

事物的隔离级别

事物的隔离级别,指的是并发情况下,事物之间的隔离度。不同的隔离级别,可以解决不同的隔离问题,也对应着不同的并发度,使用的时候,要结合实际情况,按需选择。

问题 描述
脏读 事物A读到了事物B未提交的数据,如果事物B的操作被回滚了,那么事物A读到的就是无效数据。
不可重复读 事物A执行select 查询到结果集R1,此时事物B执行update 并提交,事物A再执行同样的select 查询到结果集R2。两次查询到的结果集不一致。
幻读 事物A执行select 查询到结果集R1,此事事物B执行了 insert 或者 delete 操作 并提交,事物A再执行同样的select 查询到结果集R2。此时发现R2相比R1多了或者少了一些记录

spring事务隔离级别

级别 描述 脏读 不可重复读 幻读
ISOLATION_DEFAULT 使用数据库隔离级别。
ISOLATION_READ_UNCOMMITTED 读未提交 X X X
ISOLATION_READ_COMMITTED 读已提交oracle 默认隔离级别,利用快照读解决脏读 Y X X
ISOLATION_REPEATABLE_READ 可重复读mysql 默认隔离级别,除了读快照之外,事物启动后不允许执行update操作 Y Y X
ISOLATION_SERIALIZABLE 串行化 Y Y Y

事物的只读属性-readOnly

spring 事物的只读属性是通过设置,java.sql.Connection 的 readOnly 属性来实现的。当设置了只读属性后,数据库会对只读事物进行一些优化,如不启用回滚段,不启用回滚日志等。

事物的超时时间-timeout

事物的超时指的是设置一个时间,当执行时间超过这个时间后,抛异常并回滚。是通过设置一个截至时间来实现的,值得注意的是,这个超时时间只有特定情况下才会生效,如dao层使用jdbcTemplete 执行sql

回滚规则-rollbackFor

回滚规则只得是spring 事物遇到哪种异常会回滚。默认只回滚RuntimeException和Error

上一篇下一篇

猜你喜欢

热点阅读