spring 注解 aop 详细介绍

来源:互联网 发布:用c 计算矩阵特征值 编辑:程序博客网 时间:2024/05/16 08:45

基于注解的Spring

  1. .Web.xml 配置
  • Web容器加载Spring配置

<!-- Spring application*.xml资源-->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

classpath*:/Spring/applicationContext*.xml

</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


  • Spring MVC配置

<servlet>

<servlet-name>springmvc</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:/SpringMVC/applicationContext-mvc.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>springmvc</servlet-name>

<url-pattern>*.jhtml</url-pattern>

</servlet-mapping>


  • 处理由JavaBeans Introspector的使用而引起的缓冲泄露的配置

<listener>

<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>

</listener>


  • 解决乱码的配置

<filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<!-- 会影响到response中的字符编码-->

<param-name>forceEncoding</param-name>

<param-value>true</param-value>

</init-param>

</filter>

<filter>

<filter-name>encodingConvertFilter</filter-name>

<filter-class>net.sage.filter.EncodingConvertFilter</filter-class>

<init-param>

<param-name>fromEncoding</param-name>

<param-value>ISO-8859-1</param-value>

</init-param>

<init-param>

<param-name>toEncoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

</filter>

   

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>*.jsp</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>*.jhtml</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>encodingConvertFilter</filter-name>

<url-pattern>*.jhtml</url-pattern>

</filter-mapping>


  • 使用SpringWeb.xml的完整配置

<!-- Spring application*.xml资源-->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

classpath*:/applicationContext*.xml

</param-value>

</context-param>


  <filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<param-name>forceEncoding</param-name>

<param-value>true</param-value>

</init-param>

</filter>


<filter>

<filter-name>encodingConvertFilter</filter-name>

<filter-class>net.sage.filter.EncodingConvertFilter</filter-class>

<init-param>

<param-name>fromEncoding</param-name>

<param-value>ISO-8859-1</param-value>

</init-param>

<init-param>

<param-name>toEncoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

</filter>

  

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>*.jsp</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>*.jhtml</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>encodingConvertFilter</filter-name>

<url-pattern>*.jhtml</url-pattern>

</filter-mapping>


<!-- Spring MVC -->

<servlet>

<servlet-name>springmvc</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:/applicationContext-mvc.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>springmvc</servlet-name>

<url-pattern>*.jhtml</url-pattern>

</servlet-mapping>


<!-- Spring 加载application*.xml -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<!-- 处理由JavaBeans Introspector的使用而引起的缓冲泄露 -->

<listener>

<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>

</listener>


<session-config>

<session-timeout>30</session-timeout>

</session-config>


<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.jsp</welcome-file>

</welcome-file-list>


<error-page>

<error-code>404</error-code>

<location>/common/resource_not_found.jhtml</location>

</error-page>


  1. .Application.xml配置
  • 基于Spring注解定义Bean的配置
  1. 1.添加命名空间

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


  1. 2.扫描类包以应用注解定义Bean

通常如此配置:

<context:component-scan base-package="com.sage"/>

如果仅希望扫描特定的类,而非基包下的所有类,可以使用resource-pattern属性过滤出特定的类:

<!-- 基包为com.sage,默认情况下resource-pattern属性的值为”**/*.class”,即基包中的所有类。这里设置为”anno/*.clss”Spring仅会扫描基包里anno子包中的类-->

<context:component-scan base-package="com.sage" resource-pattern="anno/*.class"/>

如果使用resource-pattern属性满足不你的要求,可以使用<context:component-scan>的过滤子元素实现更灵活的过滤:

<context:component-scan base-package="com.sage">

<context:include-filtertype=”regex”expression="com\.sage\.anno\..*"/>

<context:exclude-filtertype=aspectjexpression="com.sage..*Controller+"/>

</context:component-scan>

过滤表达式

类别

示例

说明

annotation

com.sage.XxxAnnotation

所有标注了XxxAnnotation的类。该类型采用目标类是否标注了某个注解进行过滤

assignable

com.sage.XxxService

所有继承或扩展了XxxService的类。该类型采购目标类是否继承或扩展某个特定类进行过滤。

aspectj

Com.sage..*Service+

所有类名以Service结束的类及继承或扩展它们的类。该类型采购AspectJ表达式进行过滤。

regex

Com\.sage\.anno\..*

所有com.sage.anno类包下的类。该类型采购正则表达式根据目标类名进行过滤。

Custom

com.sage.XxxTypeFilter

采购XxxTypeFilter通过代码的代工根据过滤规则。该类必须实现org.springframework.core.type.TypeFilter接口。



<context:component-scan />application.xml中可以配置多个。


  1. 3.最终配置

<?xml version="1.0"encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:component-scan base-package="com.sage"/>

</beans>


  • AOP 配置
  1. 1.添加命名空间

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:aop="http://www.springframework.org/schema/aop”

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">


  1. 2.基于@AspectJ切面的驱动器配置

<aop:aspectj-autoproxy/>

如果使用的是CGLib动态代理,可以将其proxy-target-class属性设置为”true”,如:

<aop:aspectj-autoproxy proxy-target-class=”true”/>



  1. 3.最终配置

<?xml version="1.0"encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:aop="http://www.springframework.org/schema/aop" 

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<aop:aspectj-autoproxy/>

</beans>


  • 事务配置
  1. 1.添加命名空间

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:aop="http://www.springframework.org/schema/aop" 

xmlns:tx="http://www.springframework.org/schema/tx" 

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"

http://www.springframework.org/schema/tx 

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

  1. 2. 使用@Transactional注解的配置


<!-- 加载Properties属性文件-->

<context:property-placeholderlocation="classpath*:/database.properties"ignore-resource-not-found="true"ignore-unresolvable="true"/>


<!-- 配置一个数据源-->

<bean id="proxoolDataSource"class="org.logicalcobwebs.proxool.ProxoolDataSource">

<property name="driver">

     <value>${driver_class}</value>

</property>

<property name="driverUrl">

<value>${driver_url}</value>

</property>

<property name="user"value="${username}"/>

<property name="password"value="${password}"/>

<!-- user,password相同,也可加入其他参数,这里的user,password不能去掉,这可能是proxoolbug -->

<property name="delegateProperties">

<value>user=${username},password=${password}</value>

</property>

<!-- 允许最大连接数,超过了这个连接,再有请求时,就排在队列中等候,最大的等待请求数由maximum-new-connections决定--> 

<property name="maximumConnectionCount"value="50"/>

<!-- 最小连接数--> 

<property name="minimumConnectionCount"value="5"/>

<!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回收,超时的销毁--> 

<property name="houseKeepingSleepTime"value="120000"/>

<!-- 最少保持的空闲连接数--> 

<property name="prototypeCount"value="3"/>

<!-- 调试时显示的别名-->

<property name="alias"value="${username}"></property>

<!-- 跟踪调试--> 

<property name="trace"value="true"/>

<!-- proxool自动侦察各个连接状态的SQL -->

<property name="houseKeepingTestSql">

<value>select 1 from dual</value>

</property>

</bean>     


<!-- 配置基于数据源的事务管理器 -->

<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTrancationManager">

<propertyname="dataSource"ref="dataSource"/>

</bean>


<!-- 开启@Transactional事务注解,对@Transactional注解的Bean进行加工处理,以织入事务管理切面 -->

<tx:annotation-driven transaction-manager="transactionManager" />

  1. 3.最终配置

<?xml version="1.0"encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" 

xmlns:aop="http://www.springframework.org/schema/aop" 

xmlns:tx="http://www.springframework.org/schema/tx" 

xmlns:p="http://www.springframework.org/schema/p" 

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"

http://www.springframework.org/schema/tx 

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

<!-- 加载Properties属性文件-->

<context:property-placeholderlocation="classpath*:/database.properties"ignore-resource-not-found="true"ignore-unresolvable="true"/>


<!-- 配置一个数据源-->

<bean id="proxoolDataSource"class="org.logicalcobwebs.proxool.ProxoolDataSource">

<property name="driver">

     <value>${driver_class}</value>

</property>

<property name="driverUrl">

<value>${driver_url}</value>

</property>

<property name="user"value="${username}"/>

<property name="password"value="${password}"/>

<!-- user,password相同,也可加入其他参数,这里的user,password不能去掉,这可能是proxoolbug -->

<property name="delegateProperties">

<value>user=${username},password=${password}</value>

</property>

<!-- 允许最大连接数,超过了这个连接,再有请求时,就排在队列中等候,最大的等待请求数由maximum-new-connections决定--> 

<property name="maximumConnectionCount"value="50"/>

<!-- 最小连接数--> 

<property name="minimumConnectionCount"value="5"/>

<!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回收,超时的销毁--> 

<property name="houseKeepingSleepTime"value="120000"/>

<!-- 最少保持的空闲连接数--> 

<property name="prototypeCount"value="3"/>

<!-- 调试时显示的别名-->

<property name="alias"value="${username}"></property>

<!-- 跟踪调试--> 

<property name="trace"value="true"/>

<!-- proxool自动侦察各个连接状态的SQL -->

<property name="houseKeepingTestSql">

<value>select 1 from dual</value>

</property>

</bean>     


<!-- 配置基于数据源的事务管理器 -->

<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTrancationManager">

<propertyname="dataSource"ref="dataSource"/>

</bean>


<!-- 扫描com.sage.service -->

<context:component-scan base-package="com.sage.service"/>


<!-- 开启@Transactional事务注解,对@Transactional注解的Bean进行加工处理,以织入事务管理切面 -->

<tx:annotation-driven transaction-manager="transactionManager" />

</beans>


  1. .Spring 注解
  • @Component

使用@Component注解在类声明处对类进行标注,它可以和在XML中配置达到同样的效果。

package com.test;

import org.springframework.stereotype.Component;

@Component("a")

public class A {

...

}

等同于

<bean id="a"class="com.test.A" />


除了@Component外,Spring还提供了3个功能基本与之相同的注解,它们分别用于对DAOService以及Web层的Controller进行注解,所以也称为注解了Bean的衍型stereotype注解:

  • @Repository:用于对DAO实现类进行标注;
  • @Service:用于对Service实现类进行标注;
  • @Controller:用于对Controller实现类进行标注。

之所以要在@Component之外提供这三个特殊的注解,是为了让标注类本身的用途清晰化,你完全可以用@Component替代这三个特殊的注解。但是推荐使用特定的注解标注特定的BeanSpring在后续版本中可能会分别对这三个特殊的注解功能进行增强。


  • @Repository

用于对DAO实现类进行标注;


  • @Service

用于对Service实现类进行标注;


  • @Controller

用于对Web层的Controller实现类进行标注;


  • @AutoWired

使用@autoWired注解可以实现Bean的依赖注入。

该注解默认按类型匹配的方法,在容器中查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入到@AutoWired标注的变量中。

如果窗口中没有一个和标注变量类型匹配的BeanSpring容器启动时将报NoSuchBeanDefinitionException异常。如果希望Srping即使找不到匹配的Bean完成注入也不要抛出异常,那么可以使用它的required属性:@AutoWired(required=false),默认情况下,required属性为true

package com.test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;


@Component("a")

public class A {

@Autowired(required=false)

private B b;

...

}


对类方法进行标注:

@Autowired可以对类成品变量及方法的入库进行标注,:

package com.test;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;


@Component

public class A {

private Bar bar;

private Car car; 

@Autowired

publicvoid setBar(Bar bar){

this.bar = bar;

}

@Autowired

publicvoid int(Bar bar, Car car){

this.bar = bar;

this.car = car;

}

}


对集合类进行标注:

如果对类中集合类的变量或方法入参进行@Autowired标注,Spring会将容器中类型匹配的所有Bean都自动注入进来。如:

package com.test;


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;


@Component

public class T {

@Autowired

public List<Plugin> plugins;

public List<Plugin> getPlugins(){

return plugins;

}

}

Spring如果发现变量是一个集合类,它就会将容器中匹配集合元素类型的所有Bean都注入进来。这里,Plugin为一个接口,它拥有两个实现类,分别是APluginBPlugin,这两个实现类都通过@Component标注为Bean,则Spring会将这两个Bean都注入到plugins中。



  • @Qualifier

如果容器中有一个以上匹配的Bean时,则可以和将@Autowired注解和@Qualifier注解一起使用:

package com.test;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Component;


@Component("a")

public class A {

@Autowired(required=false)

@Qualifier("b")

private B b;

...

}

这时,假设容器有两个类型为com.test.BBean,一个名为”b”,另一个名称”bb”Spring会注入名为”b”Bean


进类方法进行标注:

package com.test;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Component;


@Component

public class A {

private Bar bar;

private Car car; 

@Autowired

@Qualifier("bar")

publicvoid setBar(Bar bar){

this.bar = bar;

}

@Autowired

publicvoid int(@Qualifier("bar")Bar bar, Car car){

this.bar = bar;

this.car = car;

}

}


  • @Resource @Inject

Spring还支持JSR-250中定义的@ResourceJSR-330中定义的@Inject注解,这两个注解和@Autowired注解功能类似,都是对类变量及方法入参提供自动注入。

@Resource要求提供一个Bean名称的属性,如果为空,则自动采用标注处的变量名或方法名作为Bean的名称:

package com.test;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;


@Component

public class A {

private Bar bar;

@Resource("bar")

publicvoid setBar(Bar bar){

this.bar = bar;

}

}


@Inject注解和@autowired一样也是按类型匹配注入Bean的,只不过它没有required属性。

可见不管是@resource还是@Inject注解,其功能都没有@Autowired丰富,因此除非必要,大可不必在乎这两个注解。


  • @Scope

通过入参指定Bean的作用范围,默认为单例(singletion)。可选择的值有:

  • singletion

单据模式。每次获取(注入)都是同一个实例。

  • prototype

原型模式。每一次获取(注入)都是新的实例。

  • request web应用环境相关的作用域

Request作用域的Bean对应一个Http请求和生命周期,这样,每次Http请求调用到Bean时,Spring容器创建一个新的Bean实例,请求处理完毕后,销毁这个实例。

  • session web应用环境相关的作用域

Session作用域的Bean对应一个Session请求和生命周期。Bean实例的作用域横跨整个Http SessionSession中所有Http请求都共享同一个Bean实例。当Http Session结束后,实例才被销毁。

  • globalSession web应用环境相关的作用域

书中所写:globalSession作用域类似于Session作用域,不过仅在PortletWeb应用中使用。Portlet规范定义了全局Session的概念,它被组成portlet Web应用的所有子Portlet共享。如果不在Portlet Web应用环境下,globalSession自然就等价于session作用域了。

 

使用Web应用环境相关的Bean作用域

需要在Web容器中进行额外配置:

在高版本的Web窗口中使用Http请求监听器进行配置:

<listener>

   listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>

</listener>


在低版本的Web容器中(before Servlet2.3)使用Http请求过滤器进行配置:

<filter>

<filter-name>requestContextFilter</filter-name>

<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>requestContextFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


  • @PostConstruct @PreDestory

@PostConsturct注解是用于标注初始化方法的,相当于xml配置中的init-method属性;@PreDestory注解是用于标注销毁方法的,相当于xml配置中的destory-method属性。

package com.test;

import java.util.List;

import javax.annotation.PostConstruct;

import javax.annotation.PreDestroy;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;


@Component

public class A {

private Bar bar;

public A(){

System.out.println("construct...");

}

@Resource("bar")

publicvoid setBar(Bar bar){

System.out.println("execute in setBar");

this.bar = bar;

}

@PostConstruct

privatevoid init1(){

System.out.println("execute in init1");

}

@PostConstruct

publicvoid init2(){

System.out.println("execute in init2");

}

@PreDestroy

publicvoid destory1(){

System.out.println("execute in destory1");

}

@PreDestroy

publicvoid destory2(){

System.out.println("execute in destory2");

}

}

运行这个类,将可以在控制台中看到如下输出信息:

construct...

execute in setBar

execute in init1

execute in init2

execute in destory1

execute in destory2

说明Spring先调用com.test.A的构造函数实例化Bean,再执行@Autowired进行自动注入,然后分别执行标注了@PostContruct的方法,容器关闭时,则分别执行标注了@PreDestory的方法。



  • @DependsOn

一般情况下,可以使用@Autowired注解建立对其他Bean的依赖关系,Spring负责管理这些Bean的关系,当实例化一个Bean时,Spring保存该Bean所依赖的其他Bean已经初始化。

但在某些情况下,这种Bean之间的依赖关系并不那么明显。如某个论坛有很多系统参数(如会话过期时间、缓存更新时间),这些参数用于控制系统的运行逻辑。我们使用SystemSetting类表示这些系统参数:

public class SystemSetting{

publicstatic intSESSION_TIMEOUT = 30;

publicstatic intREFRESH_CYCLE = 60;

...

}

SystemSetting中为每一个系统参数提供了默认值,但一个灵活的论坛必须提供一个管理后台,在管理后台中可以调整这些系统参数并保存到后台数据库中,在系统启动时,初始化程序从数据库后台加载这些系统参数的配置值以覆盖默认的值:

@Component("sysInit")

public class SysInit{

public SysInit(){

SystemSetting.SESSION_TIMEOUT = 10;

SystemSetting.REFRESH_CYCLE = 100;

}

}

假设论坛有一个缓存刷新管理器,它需要根据系统参数SystemSettings.REFERSH_CYCLE创建缓存刷新定时任务:

@Component("cacheManager")

public class CacheManager{

public CacheManager(){

Timer timer =new Timer();

TimerTask task =new CacheTask();

timer.schedule(task, 0, SystemSetting.REFRESH_CYCLE);

}

}

在以上的实例中,CacheManager依赖于SystemSettings,而SystemSettings的值由SysInit负责初始化,虽然CacheManager不直接依赖于SysInit,但从逻辑上看,CacheManager希望在SysInit加载并完成系统参数设置后再启动,以避免调用不到真实的系统参数值。如果这三个Bean都在Spring中定义,我们如何保存SysInitCacheManager之前进行初始化呢?

Xml配置中,Spring允许用户通过depends-on属性指定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好。

而在使用注解的配置中,我们使用@DependsOn注解来标注前置依赖,多个前置依赖可以使用数组入参:

@DependsOn({"cacheManager"})

@Component("cacheManager")

public class CacheManager{

public CacheManager(){

Timer timer =new Timer();

TimerTask task =new CacheTask();

timer.schedule(task, 0, SystemSetting.REFRESH_CYCLE);

}

}


  • @Lazy

通过@Lazy(true)注解表示Bean是否延迟初始化。


  • @Configuration@Bean@Import@ImportResource

SpringJavaConfig子项目中的类,JavaConfig旨在通过Java害的方式提供Bean的定义信息,该项目早在Spring2.0时就已经发布了1.0版本。Srping3.0基于Java类配置的核心即取材于JavaConfigJavaConfig经过若干年的努力终于修成正果,成为了Spring3.0的核心功能。

@Configuration@Bean@Import@ImportResource这四个注解都是用于该功能的。普通的POJO类只要标注了@Configruration注解,就可以为Spring容器提供Bean定义的信息了。


  1. .Spring AOP
  1. 1.application.xml中进行相关配置使用@AspectJ切面,请点击查看AOP配置
  2. 2.@AspectJ语法基础

Spring支持9@ApsectJ切点表达式函数,它们用不同的方式描述目标类的连接点,根据描述对象的不同,可以大致分为4种类型:

类别

函数

入参

说明

方法切点函数

execution()

方法匹配模式串

  表示满足某一匹配模式的所有目标类方法连接点。语法:

execution(<修饰符模式>?<返回类型模式><方法名模式><参数模式><异常模式>>)

每个模式之间用空格(“ ”)分开,如execution(public * *(..))execution(* com.sage.Waiter.*(..))execution(* com.sage..*(..))其中修饰符模式、异常模式是可以选的。这是最常用的切点函数。

  支持所有的通配符。

@annotation()

方法注解类名

  表示标注了特定注解的目标方法连接点。如@annotation(com.sage.anno.NeedTest)表示匹配任何标注了@NeedTest注解的目标类方法。

  不支持任何通配符。

方法入参切点函数

args()

类名、变量名

  通过判别目标类方法运行时入参对象的类型指定连接点。如args(com.sage.A)表示所有有且仅有一个按类型匹配于A的入参方法。

仅支持通配符”+”,且默认实现”+”通配符,所以args(com.sage.A)等价于args(com.sage.A+)

@args()

类型注解类名

  通过判别目标方法运行时入参对象的类是否标注特定注解来指定连接点。如@args(com.sage.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解。

  不支持任何通配符。

目标类切点函数

within()

类名匹配串

  表示特定域下的所有连接点。如within(com.sage.service.*)表示com.sage.service包中所有连接点,即包中所有类的所有方法,而within(com.sage.service.*Service)表示匹配com.sage.service包中所有以Service结尾的类的所有连接点。

  支持所有的通配符。

target

类名

  假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。如通过target(com.sage.Waiter)定义的切点,Waiter以及Waiter的实现类及子类中的所有连接点都匹配该切点。

  仅支持通配符”+”,且默认实现”+”通配符,所以target(com.sage.A)等价于target(com.sage.A+)

@within()

类型注解类名

  假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点,如@within(com.sage.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter以及Waiter的实现类或子类的所有连接点都匹配。

  不支持任何通配符。

@target()

类型注o解类名

  目标类标注了特定注解,则目标类所有连接点匹配该切点。如@target(com.sage.Monitorable),假如Waiter类标注了@Monitorable注解,那么只匹配Waiter类,Waiter实现类或其子类并不匹配。

  不支持任何通配符。

代理类切点函数

this()

类名

  代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点。在一般情况下,使用this()target()两者是等效的;它们的不同不处,我们使用例子来说明:com.sage.NaiveWaiter实现Waiter接口,又通过引介增强引入com.sage.Seller接口中的方法,而this(com.sage.Seller)支持通过引介增强产生的NaiveWaiter代理对象,而target(com.sage.Seller)是不支持的。

  仅支持通配符”+”,且默认实现”+”通配符,所以this(com.sage.A)等价于this(com.sage.A+)


在函数入参中使用通配符

  • *

匹配任意字符,但它只能匹配上下文中的一个元素;如com.sage.service.*Service,表示com.sage.service包中所有以Service结尾的类。

  • ..

匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用;如com.sage..*,表示com.sage.service包及其子孙包中所有的类。

  • +

表示按类型匹配指定类的所有类(类本身、实现类及子类),必须跟在类名后面,如com.sage.Car+,表示com.sage.Car类本身,以及com.sageCar的实现类和子类。


  • 支持所有通配符的有:execution()within()
  • 仅支持+通配符的有:args()this()target()
  • 不支持通配符的有:@args()@within()@target@annotation()


逻辑运算符

  • &&

与操作符,相当于切点的次运算;如within(com.sage.*) && args(java.lang.String)表示com.sage包下(不包含其子孙包)的所有类拥有一个java.lang.String类型入参的方法。

如果在SpringXML配置文件中使用切点表达款,由于&XML特殊字符,所以需要使用转义字符&amp;&amp;表示。为了使用方便,Spring提供了一个等效的运算符”and”。如within(com.sage.*) and args(java.lang.String)

  • ||

或操作符,相当于切点的并集运算。如within(com.sage.*) || args(java.lang.String),表示com.sage.包下所有类的方法,或者所有拥有一个java.lang.String类型入参的方法。

within(com.sage.*) or args(java.lang.String)与上述表达式一样。

  • !

非操作符,相当于切点的反集运算,如!within(com.sage.*)表示所有不在com.sage包下的方法。

“ not within(com.sage.*)”与述表达式一样,注意:如果表达式是”not within(com.sage.*)”将产生解析错误,这应该是Spring的一个Bug,在表达式开头添加空格后则可以通过:” not within(com.sage.*)”

  • and && 一样。
  • or || 一样
  • not ! 一样。


  1. 3.增强类型
  • @Before

前置增强,在目标方法执行之前被调用。Before注解类拥有两个属性:

  • value:该成员用于定义切点。
  • argNames:由于无法通过Java反射机制获取方法入参名,所以如果在Java编译时未启用高度信息或需要在运行期解析,就必须通过这个属性指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。
  • @AfterReturning

后置增强,在目标方法执行之后被调用。AfterReturning注解类拥有四个属性:

  • value:该属性用于定义切点。
  • pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut属性看成成是value的同义词。
  • returning:将目标对象方法的返回值绑定给增强的方法。
  • argNames:如前所述。
  • @Around

环绕增强,在目标方法执行之前被调用,需要在增加方法中通过ProceedingJoinPoint.proceed()方法执行目标方法。Around注解类拥有两个属性:

  • value:该属性用于定义切点。
  • argNames:如前所述。
  • @AfterThrowing

抛出增加,在目标方法抛出指定的异常时被调用。AfterThrowing注解类拥有四个属性:

  • value:该属性用于定义切点。
  • pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut属性看成成是value的同义词。
  • throwing:将抛出的异常绑定到增强方法中。
  • argNames:如前所述。
  • @After

Final增强,不管是抛出异常或是正常退出,该增强都会得到执行,该增强一般用于释放资源,相当于try{}finally{}控制流。After注解类拥有两个属性:

  • value:该属性用于定义切点。
  • argNames:如前所述。
  • @DeclareParents

引介增强。它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某接口的代理。该增强有点难理解,下面会通过一个实例来说明。DeclareParents拥有两个属性:

  • value:该属性用于定义切点,它表示在哪个目标类上添加引介增强。
  • defaultImpl:默认的接口实现类。

请看以下两个接口及其实现类:

 

假设我们希望NaiveWaiter能够同时充当售货员的角色,即通过切面技术为NaiveWaiter新增Seller接口的实现。实现代码如下:

package com.test;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.DeclareParents;


@Aspect

public class EnableSellerAspect {


@DeclareParents(value="com.sage.NaiveWaiter", defaultImpl=SmartSeller.class)

public Seller seller;

}

运行以下测试代码:

package com.test;

import org.springframework.context.ApplicationContext;


public class EnableSellerAspectTest {

publicstatic void main(String[] args) {

...

ApplicationContext ctx = ...;

Waiter waiter = (Waiter)ctx.getBean("waiter");

System.out.println(waiterinstanceof Waiter);

Seller seller = (Seller)waiter;

System.out.println(sellerinstanceof Seller);

}

}

代码成功执行,并输出以下信息:

true

true

可见,NaiveWaiter已经成功地新增了Seller接口的实现。


  1. 4.@args()@within()@target()详细介绍
  • @args()

该函数接受一个注解类的类名,当方法的运行时入参对象标注了指定的注解时,方法匹配切点。如图:

 

T0T1T2T3具有如图所示的继承关系,假设目标类方法的签名为fun(T1 t),它的入参为T1,而切面的切点定义为@args(M)T2类标注了@M。当fun(T1 t)传入对象是T2T3时,则方法匹配@args(M)所声明定义的切点。

再看下面的情况,假设方法签名是fun(T1 t),入参为T1,而标注@M注解的类是T0,当fun(T1 t)传入T1T2T3的实例时,均不匹配切点@args(M),如图:

 

  • @target()

该函数匹配任意标注了@M的目标类,不包含其实现类和子类。

@target(M)切点的匹配规则如图:

 

  • @within()

该函数匹配任意标注了@M的目标类及其子孙类。

@within(M)切点的匹配规则如图:

 


  1. 5.参数绑定

为了方便讲解,我们假设可供被增强的目标类包括7个类,这些类都在com.sage.*包中,如图:

 

  • 绑定连接点方法入参

args()this()target()@args()@within()@target()@annotation()这七个函数除了可以指定类名外,还可以指定参数名,将瞟对象连接点的方法入参绑定到增强的方法中。

其中args()用于绑定连接点方法的入参;@annotation()用于绑定连接点方法的注解对象;@args()用于绑定连接点方法入参的注解。来看一个args()绑定参数的实例:

package com.test;

import org.aspectj.lang.annotation.Before;


public class TestAspect {

@Before("target(com.sage.NaiveWaiter)&&args(name,num,..)")

publicvoid bindJoinPointParams(int num, String name){

System.out.println("---bindJoinPointParams()---");

System.out.println("name:"+name);

System.out.println("num:"+num);

System.out.println("---bindJoinPointParams()---");

System.out.println("以下是目标方法的输出:");

}

}

当前args()函数入参为参数名时,共包括两方面的信息:

  • 连接点匹配规则信息:连接点方法第一个入参是String类型,第二个入参是int类型;
  • 连接点方法入参和增强方法入参的绑定信息:连接点方法的第一个入参绑定到增强方法的name参数上,第二个入参绑定到增强方法的num入参上。


运行下面的测试代码:

ApplicationContext ctx = new ...;

NaiveWaiter naiveWaiter = ctx.getBean("naiveWaiter");

naiveWaiter.smile("John", 2);

System.out.println("结束!");


将在控制台看到以下输出信息:

---bindJoinPointParams()---

name:John

num:2

---bindJoinPointParams()---

以下是目标方法的输出:

NaiverWaiter:smile to Johe 2 times.

结束!

可见增强方法按预期绑定了NaiveWaiter.smile(String name, int times)方法的运行期入参.

为了保存实例能成功执行,必须启用CGLib动态代码,在XML配置文件中如此配置:<aop:aspectj-autoproxy proxy-target-class=”true”/>


  • 绑定代理对象

使用this()target()可绑定被代理对象实例,在通过类实例名绑定对象时,还依然具有原来连接点匹配的功能,只不过类名是通过增强方法中同名入参的类型间接决定的。

这里通过this()函数来了解对象绑定的用法:

package com.test;

import org.aspectj.lang.annotation.Before;


public class TestAspect {

@Before("this(waiter)")

publicvoid bindProxyObj(Waiter waiter){

System.out.println("---bindProxyObj()---");

System.out.println(waiter.getClass().getName());

System.out.println("---bindProxyObj()---");

System.out.println("以下是目标方法的输出:");

}

}

运行下面的测试代码:

ApplicationContext ctx = new ...;

Waiter naiveWaiter = ctx.getBean("naiveWaiter");

naiveWaiter.greetTo("John");

System.out.println("结束!");


将在控制台看到以下输出信息:

---bindProxyObj()---

com.sage.NaiveWaiter$$EnhancerByCGLIB$$6758801b

---bindProxyObj()---

以下是目标方法的输出:

NaiverWaiter:greet to John.

结束!


target()函数的代理对象绑定跟上面一样。


  • 绑定类注解对象

@within()@target()函数可以将目标类的注解对象绑定到增强方法中,下面通过@within()函数演示注解绑定的操作:

package com.test;

import org.aspectj.lang.annotation.Before;


public class TestAspect {


@Before("@within(m)")

publicvoid bindAnnoObj(Monitorable m){

System.out.println("---bindAnnoObj()---");

System.out.println(m.getClass().getName());

System.out.println("---bindAnnoObj()---");

System.out.println("以下是目标方法的输出:");

}

}


NaiveWaiter类中标注了@Monitorable注解,所有NaiveWaiter Bean匹配切点,其Monitorable注解对象将绑定到增强方法中。运行下面的测试代码:

ApplicationContext ctx = new ...;

Waiter naiveWaiter = ctx.getBean("naiveWaiter");

naiveWaiter.greetTo("John");

System.out.println("结束!");


将在控制台看到以下输出信息:

---bindAnnoObj()---

$Proxy3

---bindAnnoObj()---

以下是目标方法的输出:

NaiverWaiter:greet to John.

结束!


从输出信息中可以看出,使用CGLib代理NaiveWaiter时,其类的注解Monitorable对象也被代理了。


  • 绑定返回值

在后置增强中,可以通过returning绑定连接点方法的返回值:

@AfterReturning(value="target(com.sage.SmartSeller)", returning="retVal")

publicvoid bindReturnValue(int retVal){

System.out.println("---bindReturnValue()---");

System.out.println("return value:" + retVal);

System.out.println("---bindReturnValue()---");

System.out.println("以下是目标方法的输出:");

}


Returning属性值的名称必须跟增强方法入参的名称相同,此外,returning属性值的类型必须和连接点方法的返回值类型匹配。运行下面的测试代码:

ApplicationContext ctx = new ...;

SmartSeller seller = ctx.getBean("seller");

seller.sell("John","beer");

System.out.println("结束!");


可以看到以下输出信息:

---bindReturnValue()---

return value:100

---bindReturnValue()---

以下是目标方法的输出:

SmartSeller:Johe sell to beer.

结束!


  • 绑定抛出的异常

和通过切点函数绑定连接点信息不同,连接点抛出的必须使用@AfterThrowing注解的throwing属性进行绑定:

@AfterThrowing(value="target(com.sage.SmartSeller)", throwing="e")

publicvoid bindException(NullPointerException e){

System.out.println("---bindException()---");

System.out.println("exception:" + e.getMessage());

System.out.println("---bindException()---");

System.out.println("以下是目标方法的输出:");

}

SmartSeller中添加一个抛出异常的方法:

public class SmartSell implements Seller {

publicvoid checkBill(String billId){

if(billId==null)throw new NullPointerException("billId is null.");

}

}


运行下面的测试代码:

ApplicationContext ctx = new ...;

SmartSeller seller = ctx.getBean("seller");

seller.checkBill(null);

System.out.println("结束!");


可以看到以下输出信息:

---bindException()---

exception:billId is null

---bindException()---

以下是目标方法的输出:

SmartSeller:Johe sell to beer.

结束!



  1. 6.增强顺序

一个连接点可以同时匹配多个切点,切点对应的增强在连接点上的织入顺序是如何安排的呢?这个问题需要分3种情况讨论:

  • 如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入;
  • 如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序号决定(顺序号小的先织入);
  • 如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,织入的顺序是不确定的。

我们可以通过下图描述这种织入的规则:

 

切面类AB都实现了Ordered接口,A切面类对应序号为1B切面类对应序号为2A切面类按顺序定义了3个增强,B切面类按顺序定义两个增强,这5个增强对应的切点都匹配某个目标类的连接点,则增强织入的顺序为上图中虚线所示。


  1. 7.访问连接点信息

AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环线增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint来访问到连接点上下文的信息。

  • JoinPoint
  • java.lang.Object[] getArgs() :获取连接点方法运行时入参列表;
  • org.aspectj.lang.Signature getSignature() :获取连接点的方法签名对象;
  • java.lang.Object getTarget() :获取连接点所在的目标对象;
  • java.lang.Object getThis() :获取代理对象本身。


  • ProceedingJoinPoint

ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:

  • java.lang.Object proceed() throws java.lang.Throwable :通过反射执行目标对象的连接点处的方法;
  • java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable :通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。


看一个具体的实例:

package com.test;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;


public class TestAspect {


@Around("execution(* geetTo(..)) && target(com.sage.NaiveWaiter)")

publicvoid joinPointAccess(ProceedingJoinPoint p){

System.out.println("---joinPointAccess()---");

System.out.println("args[0]:" + p.getArgs()[0]);

System.out.println("target class:" + p.getTarget().getClass());

System.out.println("以下是目标方法的输出:");

p.proceed();

System.out.println("---joinPointAccess()---");

}

}


执行以下测试代码:

ApplicationContext ctx = new ...;

Waiter waiter = ctx.getBean("naiveWaiter");

waiter.greetTo("John");

System.out.println("结束!");


可以看到以下输出信息:

---joinPointAccess()---

args[0]:John

target class:com.sage.NaiveWaiter

以下是目标方法的输出:

NaiveWaiter:greet to John.

---joinPointAccess()---

结束!


  1. 8.命名切点

在前面所举的例子中,切点直接声明在杨增强处,这种切点声明方式称为匿名切点,匿名切点只能在声明处使用。如果希望在其他地方重用一个切点,可以通过@Pointcut注解以及切面类方法对切点进行命名,如下:

package com.test;

import org.aspectj.lang.annotation.Pointcut;


public class TestNamePointcut {


@Pointcut("within(com.sage.*)")

privatevoid inPackage(){}

@Pointcut("execution(* greetTo(..))")

protectedvoid greetTo(){}

@Pointcut("inPackage() && greetTo()")

publicvoid inPkgGreetTo(){}

}


在上述代码中定义了3个命名切点,命名切点的使用类方法作为切点的名称,此外方法的访问修饰符还控制了切点的可引用性,这种可引用性和类方法的可访问性相同,如private的切点只能在本类中引用,public的切点可以在任何类中引用。命名切公利用方法名和访问修饰符的信息,所以习惯上,方法的返回类型为void,并且方法体为空。

通过下图可以更直观的了解命名切点的结构:

 


在上述代码中inPkgGreetTo()的切点引用了同类中的greetTo()切点,而inPkgGreetTo()切点可以被任何类引用。我们还可以扩展TestNamePointcut类,通过类的继承关系定义更多的切点。

命名切点定义好之后,就可以在定义切面时通过名称引用切点,看下面的实例:

package com.test;

import org.aspectj.lang.annotation.Before;


public class TestAspect {


@Before("TestNamePointcut.inPkgGreetTo()")

publicvoid pkgGreetTo(){

System.out.println("- pkgGreetTo() executed! -");

}

@Before("!target(com.sage.NaiveWaiter) && TestNamePointcut.inPkgGreetTo()")

publicvoid pkgGreetToNotNaiveWaiter(){

System.out.println("- pkgGreetToNotNaiveWaiter() executed! -");

}

}

可以看出,命名切点可以使用复合运算和匿名切点一起使用。


  1. .Spring 事务管理
  1. 1.数据并发的问题

一个数据库可能拥有多个访问客户端,这些客户端都可以以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类,包括3类数据读问题(脏读、不可重复读、幻象读)以及2类数据更新问题(第一类丢失更新、第二类丢失更新)。下面,我们分别通过实例讲解引发问题的场景。

  • 脏读(dirty read

A事务读取B事务尚未提交的更改数据,并在这个数据的基础上操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不承认的。来看取款事务和转账事务并发时引发的脏读场景:

时间

转账事务A

取款事务B

T1


开始事务

T2

开始事务


T3


查询账户余额为1000

T4


取出500元,把余额改为500

T5

查询账户余额为500(脏读)


T6


撤销事务,余额恢复为1000

T7

汇入100元,把余额改为600


T8

提交事务


在这个场景中,B希望取款500元而后又撤销了动作,而A住相同的账户中转账100元,就因为A事务读取了B事务尚未提交的数据,因而造成账户白白丢失了500元。在Oracle数据库中,不会发生脏读的情况。


  • 不可重复读(unrepeatable read

不可重复读是批A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致:

时间

取款事务A

转账事务B

T1


开始事务

T2

开始事务


T3


查询账户余额为1000

T4

查询账户余额为1000


T5


取出100元,把余额改为900

T6


提交事务

T7

查询账户余额为900元(和T4读取的不一致)


在同一事务中,T4时间点和T7时间点读取账户存款余额不一样。


  • 幻象读(phantom read

A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,举一个例子,假设银行系统在同一个事务中,两次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致:

时间

统计金额事务A

转账事务B

T1


开始事务

T2

开始事务


T3

统计总存款数为10000


T4


新增一个存款账户,存款为100

T5


提交事务

T6

再次统计总存款数为10100元(幻象读)


如果新增数据刚好满足事务的查询条件,这个新数据就进入了事务的视野,因而产生了两个统计不一致的情况。

幻象读和不可重复读是两个容易混淆的概念,前者是指读到了其他已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除),为了避免这两个情况,采取的对策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新新增数据,则往往需要添加表级锁-- 将整个表锁定,防止新增数据(Oracle使用多版本数据的方式实现)。


  • 第一类丢失更新

A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可以造成很严重的问题,通过下面的账户取款转账就可以看出来:

时间

取款事务A

转账事务B

T1

开始事务


T2


开始事务

T3

查询账户余额为1000


T4


查询账户余额为1000

T5


汇入100元把余额改为1100

T6


提交事务

T7

取出100元把余额改为900


T8

撤销事务


T9

余额恢复为100元(丢失更新)


A事务在撤销时,不小心B事务已经转入账户的金额给抹去了。

  • 第二类丢失更新

A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:

时间

转账事务A

取款事务B

T1


开始事务

T2

开始事务


T3


查询账户余额为1000

T4

查询账户余额为1000


T5


取出100元,把余额改为900

T6


提交事务

T7

汇入100


T8

提交事务


T9

把余额改为1100元(丢失更新)


上面的例子里由于转账事务A覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户账户将损失100元。


  1. 2.数据库锁机制

数据并发会引发很多问题,在一些场合下有些问题是允许的,但在另外一些场合下可能却是致命的。数据库通过锁的机制解决并发访问的问题,虽然不同的数据库在实现细节上存在差别,但原理基本上是一样的。

按锁定的对象的不同,一般可以分为表锁定和行锁定,前者对整个表进行锁定,而后者对表中特定行进行锁定。从并发事务锁定的关系上看,可以分为共享锁定和独占锁定。共享锁定会防止独占锁定,但允许其他的共享锁定。而独占锁定既防止苍的独占锁定,也防止其他的共享锁定。为了更改数据,数据库必须在进行更新的行上施加行独占锁定,INSERTUPDATEDELETESELECT FOR UPDATE语句都会隐式采用必要的行锁定。下面我们介绍一下Oracle数据库常用的5种锁定:

  • 行共享锁定:

一般通过SELECT FOR UPDATE语句隐式获得行共享锁定,在Oracle中用户也可以通过LOCK TABLE IN ROW SHARE MODE语句显式获得行共享锁定。行共享锁定并不防止对数据行进行更新的操作,但是可以防止其他会话获取独点性数据表锁定。允许进行多个并发的行共享和行独占性锁定,还允许进行数据表的共享锁定或者表共享行独占锁定。

  • 行独占锁定:

通过一条INSERTUPDATEDELETE语句隐式获取,或者通过一条LOCK TABLE IN ROW EXCLUSIVE MODE 语句显式获取。这个锁定可以防止其他会话获取一个表共享锁定、行共享锁定、表共享行独占锁定、行独占锁定、表独占锁定。

  • 表共享锁定:

通过LOCAK TABLE IN SHARE MODE语句显式获得。这种锁定可以防止其他会话获取行独占锁定(INSERTUPDATEDELETE),或者防止其他表共享行独占锁定或表独占锁定,它允许在表中拥有多个行共享和表共享锁定。该锁定可以让会话具有对表事务级一致性访问,因为其他会话在用户提交匮乏回溯该事务并释放对该表的锁定之前不能更改这个被锁定的表。

  • 表共享行独占:

通过LOCK TABLE IN SHARE ROW EXCLUSIVE MODE语句显式获得。这种锁定可以防止其他会话获取一个表共享、行独占或者表独占锁定,这允许其他行共享锁定。这种锁定类似于表共享锁定,只是一次只能对一个表旋转一个表共享行独占锁定。如果A会话拥有该锁定,则B会话可以执行SELECT FOR UPDATE操作,如果B会话试图更新选择的行,则需要等待。

  • 表独占

通过LOCK TABLE IN EXCLUSIVE MODE显式获得。这个锁定防止其他会话对该表的任何其他锁定。


  1. 3.事务隔离级别

尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。些外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的。

ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的,如下所示:

隔离级别

脏读

不可重复读

幻象读

第一类丢失更新

第二类丢失更新

READ UNCOMMITED

允许

允许

允许

不允许

允许

READ COMMITED

不允许

允许

允许

不允许

允许

REPEATABLE READ

不允许

不允许

允许

不允许

不允许

SERIALIZABLE

不允许

不允许

不允许

不允许

不允许

事务的隔离级别和数据库并发性是对立的,两者此增彼减。一般来说,使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,而使用SERIALIZABLE隔离级别的数据库并发性最低。

SQL 92定义READ UNCOMMITED主要是为了提供非阻塞坊的能力,Oracle虽然也支持READ UNCOMMITED,但它不支持脏读,因为Oracle使用多版本机制彻底解决了在非阻塞读到脏数据的问题并保证读的一致性,所以OracleREAD COMMITED隔离级别就已经满足了SQL 92标准的REFEATABLE READ隔离级别。

SQL 92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据就用的需要选择适合的隔离等级。


  1. 4.Spring的事务管理器实现类

通过JDBC的事务管理知识,我们知道事务只能被提交或回滚(或回滚到某个保存点后提交),Spring高层事务抽象接口org.springframework.transaction.PlatformTransactionManager很好的描述了事务管理的这个概念,以下是其代码:

public interface PlatformTransactionManager {

TransactionStratus getTransaction(TransactionDefinition definition)throws TransactionException;

void commit(TransactionStratus status)throws TransactionException;

void rollback(TransactionStratus status)throws TransactionException;

}


PlatformTransactionManager只定义了3个接口方法,它们是SPIService Provider Interface)高层次的接口方法。这些访问都没有和JNDI绑定在一起,可以像Spring容器中普通的Bean一样对待PlatformTransactionManager实现者。下面我们来了解PlatformTransactionManager方法的功能:

  • TransactionStratus getTransaction(TransactionDefinition definition)

该方法根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStratus描述这个事务的状态。

  • void commit(TransactionStratus status)

根据事务的状态提交事务,如果事务状态已经被标识为rollback-only,该方法将执行一个回滚事务的操作。

  • void rollback(TransactionStratus status)

将事务回滚。当commit()方法抛出异常时,rollback()会被隐式调用。


Spring将事务管理委托给底层具体的持久化实现框架完成。因此,Spring为不同的持久化框架提供了PlatformTransactionManager接口的实现类,如下所示:

事务

说明

org.springframework.orm.jpa.JpaTransactionManager

使用JPA进行持久化时,使用该事务管理器

Org.springframework.orm.hibernate3.HibernateTransactionManager

使用Hibernate3.0版本进行持久化,使用该事务管理器

org.springframework.jdbc.datasource.DataSourceTransactionManager

使用Spring JDBCiBatis等基于DataSource数据源的持久化技术时,使用该事务管理器

org.springframework.orm.jdo.JdoTransactionManager

使用JDO进行持久化时,使用该事务管理器

org.springframework.transaction.jta.JtaTransactionManager

具有多个数据源的全局事务使用该事务管理器(不管理采用何种持久化技术)

这些事务管理器都是对特定事务实现框架的代理,这样,我们就可以通过Spring所提交的高级抽象对不种类的事务实现使用相同的方式进行管理,而不用关心具体的实现。

要实现事务,首先要在Spring中配置好相应的事务管理器,为事务管理器指定数据资源以及一些其他事务管理控制属性。下面让我们看一个JDBCiBatis的事务管理器配置:

...

<bean id="dataSource"class="org.logicalcobwebs.proxool.ProxoolDataSource"

p:driver="${driver_class}"

p:driverUrl="${driver_url}"

p:user="${username}"

p:password="${password}"

p:delegateProperties="user=${username},password=${password}"

/>

<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="dataSource"/>

...


在幕后,DataSourceTransactionManager使用DataSourceConnectioncommit()rollback()等方法管理事务。


  1. 5.Spring的事务同步管理器

SpringJDBCConnectionHibernateSession等访问数据库的连接或会话对象统称为资源。这些资源在同一时刻是不能多线程共享的,为了让DaoService类可能做到SingletonSpring的事务同步管理器类org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal管理为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。事务同步管理器是Spring事务管理的基石部分,不管用户使用编程式事务管理,还是声明式事务管理,都离不开事务同步管理器。

Spring框架为不同的持久化技术提供了一套从TransactionSynchronizationManager中获取对应线程绑定资源的工具类,如下所示:

待久化技术

线程绑定资源获取工具

Spring JDBCiBatis

org.springframework.jdbc.datasource.DataSourceUtils

Hibernate 3.0

org.springframework.orm.Hibernate3.SessionFactoryUtils

JPA

org.springframework.jpa.EntityManagerFactoryUtils

JDO

org.springframework.orm.jdo.PersistenceManagerFactoryUtils

这些工具类都提供了静态的方法,通过这些当前线程绑定的资源,如果DataSourceUtils.getConnection(DataSource dataSource)可以从数据源中获取和当前线程绑定的Connection,而HibernateSessionFactoryUtils.getSession(SessionFactory sessionFactory, boolean allowCreate)则从指定的SessionFactory中获取和当前线程绑定的Session

当需要脱离模版类,手工操作底层持久技术的原生API时,就需要通过这些工具类获取线程绑定的资源,而不应该直接从DataSourceSessionFactory中获取。因为后者不能获得和本线程相关的资源,因此无法让数据操作参与到本线程相关的事务环境中。

这些工具类还有另外一个重要的用途:将特定异常转换为SpringDAO异常。

Spring为不同的持久化技术提供了模版类,模板类在内部通过资源获取工具类间接访问TransactionSynchronizationManager中的线程绑定资源。所以如果Dao使用模版类进行持久化操作,这些Dao就可以配置成singleton。如果不使用模板类,也可以直接通过资源获取工具类访问线程相关的资源。

下面,我们就揭开TransactionSynchronizationManager的层层面纱,探寻其中的奥秘:

public abstract class TransactionSynchronizationManager{

//①②③④⑤用于保存每个事务线程对应的ConnectionSession等类型的资源

privatestatic final ThreadLocalresources = new ThreadLocal();

//②用于保存每个事务线程对应事务的名称

privatestatic final ThreadLocalcurrentTransactionName =new ThreadLocal();

//③用于保存每个事务线程对应事务的read-only状态

privatestatic final ThreadLocalcurrentTransactionReadOnly =new ThreadLocal();

//④用于保存每个事务线程对应事务的隔离级别

privatestatic final ThreadLocalcurrentTransactionIsolationLevel =new ThreadLocal();

//⑤用于保存每个事务线程对应事务的激活态

privatestatic final ThreadLocalactualTransactionActive =new ThreadLocal();

...

}

TransactionSynchronizationManagerDaoService类中影响线程安全的所有状态统一抽取到该类中,并用ThreadLocal进行替换,从此Dao(必须基于模板类或资源获取工具类创建的Dao)和Service(必须采用Spring事务管理机制)摘掉了非线程安全的帽子,完成了脱胎换骨式的身份转变。


  1. 6.Spring的事务传播行为

当我们调用一个基于SpringService接口方法(如UserService#addUser())时,它将运行于Spring管理的事务环境中,Service接口方法可能会在内部调用其他的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况,Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。事务传播是Spring进行事务管理的重要概念,其重要性怎么强调都不为过。但是事务传播行为也是被误解最多的地方,接下来我们将详细分析不同事务传播行为的表现形式,掌握它们之间的区别。

SpringTransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,如下所示:

事务传播行为类型

说明

PROPAGATION_REQUIRED

  如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS

  支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

  使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

  新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

  以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

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

PROPAGATION_NESTED

  如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作-- 新建一个事务。

(关于PROPAGATION_NESTEDPROPAGATION_REQUIRES_NEW区别可以点击http://www.07net01.com/linux/spring_PROPAGATION_REQUIRES_NEW_he_PROPAGATION_NESTED_499731_1373007731.html查看)


  1. 7.使用注解配置声明式事务
  • XML配置

具体配置可以看事务配置


  • @Transactional的属性

基于@Transactional注解的配置和基于XML的配置方式一样,它拥有一组普适性很强的默认事务属性,我们往往可以直接使用这些默认的属性就可以了:

  • 事务传播行为:PROPAGATION_REQUIED
  • 事务隔离级别:ISOLATION_DEFAULT;使用数据库默认的事务隔离级别。(Oracle默认的隔离级别是READ COMMITEDMysql默认的隔离级别是REPEATABLE READ)。
  • 读写事务属性:读/写事务;
  • 超时时间:依赖于底层的事务系统的默认值;
  • 回滚设置:任何运行期异常引发回滚,任何检查型异常不会引发回滚。

因为这些默认设置在大多数情况下都是适用的,一般不需要手工设置事务注解的属性。当前,Spring允许我们通过手工设定属性值覆盖默认值。下面就是@Transactional的属性说明:

属性名

说明

  propagation

  事务传播行为,通过以下枚举类提供合法值:

  ora.springframework.transaction.annotation.Propagation

  例如:@Transactional(propagation=Propagation.REQUIRES_NEW)

  isolation

  事务隔离级别,通过以下枚举类提供合法值:

  org.springframework.transaction.annotation.Isolation

  例如:@Transactional(isolation=Isolation.READ_COMMITTED)

  readOnly

  事务读写性,boolean型,例如:@Transactional(readOnly=true)

  timeout

  超时时间,int型,以秒为单位,例如:@Transactional(timeout=10)

  rollbackFor

  一组异常类,遇到时进行回滚,类型为:Class<? extends Throwable>[],默认为{}

  例如:@Transactional(rollbackFor={SQLException.class}),多个异常之间可用逗号分隔。

rollbackForClassName

  一组异常类名,遇到时进行回滚,类型为String[],默认值为{}。例如:

  @Transactional(rollbackForClassName={“Exception”})

noRollbackFor

  一组异常类,遇到时不回滚,类型为:Class<? extends Throwable>[],默认为

noRollbackForCassName

  一组异常类名,遇到时不回滚,类型为String[],默认值为{}


  • @Transactional如何使用

在何必使用@Transactional注解

@Transactional注解可以被应用于接口定义和接口方法、类定义、和类的public方法上。

Spring建议在业务实现类上使用@Transactional注解,当然我们也可以在业务接口上使用@Transactional注解。但这样会留下一容易被忽视的隐患。因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被实现的业务类继承,如果通过以下的配置启用子类代理:

<tx:annotation-driven transaction-manager=”txManager” proxy-target-class=”true” />

业务类不会添加事务增强,照样工作在非事务的环境下。举一个具体的实例:如果使用子类代理,假设用户为BbtForum接口标注了@Transactional注解,其实现类BbtForumImpl依旧不会启用事务机制。

因此,Spring建议在具体业务类上使用@Transactional注解。这样不管理<tx:annotation-driven>proxy-target-class属性配置为truefalse,业务类都会启用事务机制。


在方法处使用注解

方法处的注解会覆盖类定义处的注解,如果有些方法需要使用特殊的事务属性,则可以在类注解的基础上,提供方法注解:


//①类级的注解,适用于类中所有public的方法

@Transactional

public class BbtForumImpl implements BbtForum {

//②提供额外的注解信息,它将覆盖①处的类级注解

@Transactional(readOnly=true)

public Forum getForum(int forumId){

}

}


②处的方法注解提供了readOnly事务属性的设置,它将覆盖类级注解中默认的readOnly=false设置。


在不同的事务管理器

在一般情况下,一个应用仅需使用到一个事务管理器就可以了。如果希望在不同的地方使用不同的事务管理器,则可以通过如下的方法实现:


public class MultiForumService{

//①使用名为topic的事务管理器

@Transactional("topic")

publicvoid addTopic(Topic topic){

...

}

//②使用名为forum的事务管理器

@Transactional("forum")

publicvoid updateForum(Forum forum){

...

}

}


topicforum的事务管理器可以在XML中分别定义,如下所示:


<bean id="forumTxManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="forumDataSource"> <!--①使用不同的数据源 -->

<qualifiervalue="forum"/> <!--②为事务管理器标识一个名字 -->

</bean>

<bean id="topicTxManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="topicDataSource"> <!--③使用不同的数据源 -->

<qualifiervalue="topic"/> <!--④为事务管理器标识一个名字 -->

</bean>


①处,为事务管理器指定了一个数据源,每个事务管理器都可以绑定一个独立的数据源。在②处,指定了一个可被@Transactional注解引用的事务管理器标识。

在一两处使用带标识的@Transactional也许挺适合的,但如果到处都使用,则显得比较哆嗦。可以自定义一个绑定到特定事务管理器的注解,然后直接使用这个自定义的注解进行标识:


@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Transactional("forum")//①绑定到forum的事务管理器中

public @interface ForumTransactional{

}


@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Transactional("topic")//②绑定到topic的事务管理器中

public @interface TopicTransactional{

}


完成定义后,就可以用以下的方式对原来的代码进行调整了:


public class MultiForumService{

//①使用名为topic的事务管理器

@TopicTransactional

publicvoid addTopic(Topic topic){

...

}

//②使用名为forum的事务管理器

@ForumTransactional

publicvoid updateForum(Forum forum){

...

}

}


  1. .Spring MVC
  1. 1.配置

web.xml中添加以下配置:

<context-param><!-- -->

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:spring/applicationContext-*.xml</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<servlet><!-- -->

<servlet-name>ierp</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

</servlet>

<servlet-mapping><!-- -->

<servlet-name>ierp</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>


①处,通过contextConfigLocation参数指定业务层Spring容器的配置文件(多个配置文件使用逗号分隔),ContextLoaderListener是一个ServletContextListener,它通过contextConfigLocation参数所指定的Spring配置文件启动业务层Spring容器.

②处,配置了名为ierpDispatcherServlet,它默认自动加载/WEB-INF/ierp-servlet.xml(<servlet-name>-servlet.xml)Spring配置文件,启动Web层的Spring容器。

③处通过<servlet-mapping>指定DispatcherServlet处理所有URLHTTP请求。

我们知道多个Spring容器之间可设置为父子级的关系,实现良好的解耦。在这里,”Web”Spring容器将作为业务层”Spring容器的子容器,退”Web容器可以引用业务层窗口的Bean,而业务层容器却访问不到”Web容器的Bean

需要提醒的是,一个web.xml可以配置多个DispatcherServlet,通过其<servlet-mapping>的配置,让每个DispatcherServlet处理不同的请求。

DispatcherServlet遵循契约优于配置的原则,在大多数情况下,你无须进行额外的配置,只需按契约行事即可。

如果你确实要对DispatcherServlet的默认规则进行调整,DispatcherServlet是敞开胸怀的。下面是常用的一些配置参数,可以通过<servlet><init-param>指定。

  • namespaceDispatcherServlet对应的命名空间,默认为”<servlet-name>”,用以构造Spring配置文件的路径。显式指定该属性后,配置文件对应的路径为:WEB-INF/<namespace>.xml而非WEB-INF/<servlet-name>-servlet.xml。如果将namespace设置为ierp2,则对应的Spring配置文件为WEB-INF/ierp2.xml
  • contextConfigLocation:如果DispatcherServlet上下文对就的Spring配置文件有多个,则可使用该属性按照Spring资源路径的方式指定。如”classpath:ierp.xml,classpath:ierp2.xml”DispatcherServlet将使用类路径下的ierp.xmlierp2.xml这两个配置文件初始化WebApplicationContext
  • publishContextboolean类型属性,默认值为trueDispatcherServlet根据该属性决定是否将WebApplicationContext发布到ServletContext的属性列表中,以便调用者可借由ServletContext找到WebApplicationContext初值,对应的属性名为DispatcherServlet#getServletContextAttributeName()返回的值。
  • publishEventsboolean类型属性。当DispatcherServlet处理完一个请求后,是否需要身容器发布一个ServletRequestHandledEvent事件,默认为true。如果窗口中没有任何事件监听器,可以将些属性设置为false,以便提高运行性能。



Spring Web上下文中配置FreeMarker

配置如下:

<beanclass="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer" 

p:templateLoaderPath="/WEB-INF/template"1-a

p:defaultEncoding="UTF-8"> 1-b 

<propertyname="freemarkerSettings">1-c

<props>

<propkey="classic_compatible">true</prop>

</props>

</property>

</bean>

<beanclass="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"

p:order="5" 2-a

p:suffix=".ftl"2-b

p:contentType="text/html; charset=utf-8"/> 2-c


1,我通过FreeMarkerConfigurer配置了FreeMarker的环境,首先在1-a处指定了模版文件的存放路径.由于我们的模版文件采用UTF-8编码格式,所以必须显工配置defaultEncoding属性,否则将采用系统默认的编码,造成乱码的现象.

FreeMarker拥有丰富的自定义属性,用户可以通过freemarkerSettings进行统一的设置,需要阅读FreeMarker的参考文档,以获取可配置的属性信息,这些属性名称都采用小写并用”_”连接,1-c处配置的class_compatible属性非常重要,否则当FreeMarker模版碰到值为null的对象属性时,将抛出异常.classic_compatible设置为true,FreeMarker将采购类似于JSTL标签的行为处理模型属性数据:返回一个空白字符串,而非抛出系统异常.

在搭建好FreeMarker的环境后,就可以通过FreeMarkerViewResolver视图解析器将”userListFtl”的逻辑视图名解析为一个对应的FreeMarkerView视图对象.

2-a外指定该视图解析器的优先级,它将优先于InteralResourceViewResolver执行(因为InteralResourceViewResolver默认的优先级最低).2-b处指定了后缀,这新”userListFtl”的逻辑视图名将解析为”/WEB-INF/template/userListFtl.ftl”的视图对象。由于userListFtl.ftl模版最终产生的html,且我们希望使用UTF-8编码格式输出内容,所以需要通过2-c处的contentType属性进行相应配置,如果不指定该属性,输出的HTML将会产生中文乱码的问题。


  1. 2.DispatcherServlet内部逻辑

现在有一个问题:Spring如何将上下文中的SpringMVC组件装配到DispatcherServlet中?通过查看DispatcherServletinitStrategies()的代码,一切就真相大白了:

protectedvoid initStrategies(ApplicationContext context) {

initMultipartResolver(context);//①初始化上传文件解析器(直译为多部分解析器)

initLocaleResolver(context);//②初始化本地化解析器

initThemeResolver(context);//③初始化主题解析器

initHandlerMappings(context);//④初始处理器映射器

initHandlerAdapters(context);//⑤初始化处理器适配器

initHandlerExceptionResolvers(context);//⑥初始化处理器异常解析器

initRequestToViewNameTranslator(context);//⑦初始化请求到视图名翻译器

initViewResolvers(context);//⑧初始化视图解析器

}


initStrategies()方法将在WebApplicationContext初始化后自动执行,些时Spring上下文中的Bean已经初始化完毕。该方法的工作是通过反射机制查找并装配Spring容器中用户显式自定义的组件Bean,如果找不到再装配默认的组件实例。

Spring MVC定义了一套默认的组件实现类,也就是说即使在Spring窗口中没有显工定义组件BeanDispatcherServlet也会装配好一套可用的默认组件。在org.springframework.web.servlet.jar包的org.springframework.web.servlet包路径下拥有一个DispatcherServlet.properties配置文件,该文件指定了DispatcherServletr所使用的默认组件。


##本地化解析器

org.springframework.web.servlet.LocaleResolver=

org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver


##主题解析器

org.springframework.web.servlet.ThemeResolver=

org.springframework.web.servlet.theme.FixedThemeResolver


##处理器映射

org.springframework.web.servlet.HandlerMapping=

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\

org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping


##处理器适配器

org.springframework.web.servlet.HandlerAdapter=

org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter


##异常处理器

org.springframework.web.servlet.HandlerExceptionResolver=

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver


##视图名称翻译器

org.springframework.web.servlet.RequestToViewNameTranslator=

org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator


##视图解析器

org.springframework.web.servlet.ViewResolver=

org.springframework.web.servlet.view.InternalResourceViewResolver


如果用户希望采用非默认类型的组件,则只需在Spring配置文件中配置自定义的组件Bean即可,Spring MVC一旦发现上下文中拥有用户自定义的组件,就不会使用默认的组件。概言之,当DispatcherServlet初始化后,就会自动扫描上下文的Bean,根据名称或类型匹配的机制查找自定义的组件,找不到时则使用DispatcherServlet.properties定义的默认组件。通过下表可以进一步深入了解DispatcherServlet装配每种组件的过程:

组件类型

发现机制

文件上传解析器

☆)

  1. 1.查找名为multipartResolver类型为MultipartResolverBean作为该类型组件;
  2. 2.没有默认的实现。

如果用户没有在上下文中显示定义这一类型的组件,DispatcherServlet中将不会拥有该类型的组件

本地化解析器

☆)

  1. 1. 查找名为localeResolver类型为LocaleResolverBean作为该类型组件;
  2. 2.如果1)找不到,使用默认的实现类(AcceptHeaderLocaleResolver)创建该类型组件。

主题解析器

☆)

  1. 1.查找名为themeResolver类型为LocaleResolverBean作为组件;
  2. 2.如果1)找不到,使用默认的实现类(FixedThemeResolver)

处理器映射器

★)

  1. 1.如果detectAllHanderMappings属性为true(默认为true),根据类型匹配机制查找上下文及父Spring容器中所有类型为HandlerMappingBean,将它们作为该类型组件;
  2. 2.如果detectAllHanderMappings属性为false,查找名为handlerMapping类型为HandlerMappingBean作为该类型组件;
  3. 3.如果通过以上两种方式都找不到,使用BeanNameUrlHandlerMapping实现类创建该类型的组件

处理器适配器

★)

  1. 1)如果detectAllHandlerAdapters属性为true(默认为true),根据类型匹配机制查找上下文及父Spring容器中所有类型为HandlerAdapterBean,将它们作为该类型组件;
  2. 2)如果detectAllHandlerAdapters属性为false,查找名为handlerAdapter类型为HandlerAdapterBean作为该类型组件;
  3. 3)如果通过以上两种方式都找不到,使用DispatcherServlet.properties配置文件中指定的三个实现类分别创建一个适配器,添加到适配器列表中

处理器异常解析器

★)

  1. 1.如果detectAllHandlerExceptionResolvers属性为true(默认为true),根据类型匹配(HandlerExceptionResolver)机制查找上下文及父Spring容器中所有匹配的Bean作为该类型组件;
  2. 2.如果detectAllHandlerExceptionResolvers属性为false,查找名为handlerExceptionResolver类型为HandlerExceptionResolverBean作为该类型组件;
  3. 3.如果通过以上两种方式都找不到,查找DispatcherServlet.properties中定义的默认实现类,不过该文件中没有对应处理器异常解析器的默认实现类(用户可以更改属性文件)

视图名翻译器

☆)

  1. 1)查找名为viewNameTranslator类型为RequestToViewNameTranslatorBean作为该类型组件;
  2. 2)如果1)找不到,使用默认的实现类(DefaultRequestToViewNameTranslator)创建该类型的组件

视图解析器

★)

  1. 1.如果detectAllViewResolvers属性为true(默认为true),根据类型匹配(ViewResolver)机制查找上下文及父Spring容器中所有茶杯的BEan作为该类型组件;
  2. 2.如果detectAllViewResolvers属性为false,查找名为viewResolver类型为ViewResolverBean作为该类型组件;
  3. 3.如果通过以上两种方式都找不到,通过DispatcherServlet.properties中定义的默认实现类(InternalResouveViewResolver)创建该类型的组件

有些组件最多只允许一个实例,如MultipartResolverLocaleResolver等,上表使用☆进行标注。而另一类型的组件允许存在多个,如HandlerMappingHandlerAdapter等,上表使用★进行标注。同一类型的组件如果存在多个,它们之间的优先级顺序如何确定呢?这些组件都实现了orag.springframework.core.Ordered接口,可通过order属性确定优先顺序,值越小优先级越高。


  1. 3.MVC注解
  • @Controller
  • @RequestMapping
  • @PathVariable
  • @RequestParam
  • @CookieValue
  • @RequestHeader
  • @SessionAttributes
  • @ModelAttribute


  1. 4.HttpMessageConverter<T>


  1. 5.@ResponseBody@RequestBodyHttpEntityResponseEntityRestTemplate


  1. 6.返回Json
  • Spring XML配置

Spring MVC提供了几个处理XML\JSON格式的请求/响应消息的HttpMessageConverterMappingJacksonHttpMessageConverter是处理JSON格式的请求或响应消息的,下面我们就JSON格式举个例子,在Spring XML配置文件中添加以下配置:

<bean

class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

<propertyname="messageConverters">

<list>

<!--之前默认的 --> 1

<beanclass="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>

<beanclass="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>

<beanclass="org.springframework.http.converter.StringHttpMessageConverter"/>

<beanclass="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>

<!--添加处理JSON -->2-a

<beanclass="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">

<propertyname="supportedMediaTypes">2-b

<list>

<value>application/json;charset=UTF-8</value>2-c

</list>

</property>

</bean>

</list>

</property>

</bean> 

1处配置的AnnotationMethodHandlerAdapter默认的加载的HttpMessageConverter2-a添加处理JSON格式的请求或响应消息。2-b是表示响应的MediaType类型,2-c处添加JSON格式的类型。

装配好处理JSONHttpMessageConverter后,我们在控制器中编写相应的方法,请看下面。


  • 代码实现

package com.sage.ierp.controller;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import com.sage.jfianl.model.User;


@Controller

publicclass JsonController {


@ModelAttribute("user")

public User getUser(){

User user = new User();

user.setName("Saleson Lee");

user.setAge(25);

return user;

}

@RequestMapping("json")

public@ResponseBody User json(@ModelAttribute("user") User user){

return user;

}

@RequestMapping("json2")

public ResponseEntity<User> json2(@ModelAttribute("user") User user){

returnnew ResponseEntity<User>(user, HttpStatus.OK);

}

}


  1. 7.处理模型数据



  1. 8.拦截器




  1. .Spring 上传附件



Ibates

  1. .Ibates 配置
  2. .Ibates CRUD
  3. .Ibates事务管理
  4. .Ibates 分布
  5. .Spring MVC + Ibates架构


测试

  1. .Junit + Spring测试
  2. .Junit + Spring + Ibates测试
  3. .Junt + Spring MVC测试
0 0