Spring中AOP相关的API及源码解析,原来AOP是这样子的

Spring中AOP相关的API及源码解析

本系列文章:

读源码,我们可以从第一行读起

你知道Spring是怎么解析配置类的吗?

配置类为什么要添加@Configuration注解?

谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

这篇文章,我们来谈一谈Spring中的属性注入

推荐阅读:

Spring官网阅读 | 总结篇

Spring杂谈

本系列文章将会带你一行行的将Spring的源码吃透,推荐阅读的文章是阅读源码的基础!

因为本文会涉及到动态代理的相关内容,如果对动态代理不是很了解的话,参考文章:

动态代理学习(一)自己动手模拟JDK动态代理

动态代理学习(二)JDK动态代理源码分析

前言

之所以写这么一篇文章主要是因为下篇文章将结束Spring启动整个流程的分析,从解析配置到创建对象再到属性注入最后再将创建好的对象初始化成为一个真正意义上的Bean。因为下篇文章会设计到AOP,所以提前单独将AOP的相关API及源码做一次解读,这样可以降低阅读源码的障碍,话不多说,我们进入正文!

一个使用API创建代理的例子

在进入API分析前,我们先通过两个例子体会下如何使用API的方式来创建一个代理对象,对应示例如下:

  1. 定义通知
public class DmzAfterReturnAdvice implements AfterReturningAdvice {
	@Override
	public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
		System.out.println("after invoke method [" + method.getName() + "],aop afterReturning logic invoked");
	}
}

public class DmzAroundAdvice implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("aroundAdvice invoked");
		return invocation.proceed();
	}
}

public class DmzBeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("before invoke method [" + method.getName() + "],aop before logic invoked");
	}
}

public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
	@Override
	public void run() {
		System.out.println("running!!!!");
	}
}
  1. 切点
public class DmzPointcut implements Pointcut {
	@Override
	@NonNull
	public ClassFilter getClassFilter() {
		// 在类级别上不进行拦截
		return ClassFilter.TRUE;
	}

	@Override
	@NonNull
	public MethodMatcher getMethodMatcher() {
		return new StaticMethodMatcherPointcut() {
			@Override
			public boolean matches(@NonNull Method method, Class<?> targetClass) {
				// 对于toString方法不进行拦截
				return !method.getName().equals("toString");
			}
		};
	}
}
  1. 目标类
public class DmzService {
	@Override
	public String toString() {
		System.out.println("dmzService toString invoke");
		return "dmzService";
	}

	public void testAop(){
		System.out.println("testAop invoke");
	}
}
  1. 测试代码
public class Main {
	public static void main(String[] args) {

		ProxyFactory proxyFactory = new ProxyFactory();

		// 一个Advisor代表的是一个已经跟指定切点绑定了的通知
        // 在这个例子中意味着环绕通知不会作用到toString方法上
		Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());

		// 添加一个绑定了指定切点的环绕通知
		proxyFactory.addAdvisor(advisor);

		// 添加一个返回后的通知
		proxyFactory.addAdvice(new DmzAfterReturnAdvice());

		// 添加一个方法执行前的通知
		proxyFactory.addAdvice(new DmzBeforeAdvice());

		// 为代理类引入一个新的需要实现的接口--Runnable
		proxyFactory.addAdvice(new DmzIntroductionAdvice());

		// 设置目标类
		proxyFactory.setTarget(new DmzService());

		// 因为要测试代理对象自己定义的方法,所以这里启用cglib代理
		proxyFactory.setProxyTargetClass(true);

		// 创建代理对象
		Object proxy = proxyFactory.getProxy();

		// 调用代理类的toString方法,通过控制台查看代理逻辑的执行情况
		proxy.toString();

		if (proxy instanceof DmzService) {
			((DmzService) proxy).testAop();
		}

		// 判断引入是否成功,并执行引入的逻辑
		if (proxy instanceof Runnable) {
			((Runnable) proxy).run();
		}
	}
}

这里我就不将测试结果放出来了,大家可以先自行思考这段程序将输出什么。接下来我们就来分析上面这段程序中所涉及到的API,通过这些API的学习相信大家可以彻底理解上面这段代码。

API介绍

Pointcut(切点)

对应接口定义如下:

public interface Pointcut {
	
    // ClassFilter,在类级别进行过滤
	ClassFilter getClassFilter();
    
	// MethodMatcher,在方法级别进行过滤
	MethodMatcher getMethodMatcher();
	
    // 一个单例对象,默认匹配所有
	Pointcut TRUE = TruePointcut.INSTANCE;

}

切点的主要作用是定义通知所要应用到的类跟方法,上面的接口定义也很明显的体现了这一点,我们可以将其拆分成为两个部分

  • ClassFilter,接口定义如下:
public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

ClassFilter的主要作用是在类级别上对通知的应用进行一次过滤,如果它的match方法对任意的类都返回true的话,说明在类级别上我们不需要过滤,这种情况下,通知的应用,就完全依赖MethodMatcher的匹配结果。

  • MethodMatcher,接口定义如下:
public interface MethodMatcher {

	boolean matches(Method method, @Nullable Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

MethodMatcher中一共有三个核心方法

  • matches(Method method, @Nullable Class<?> targetClass),这个方法用来判断当前定义的切点跟目标类中的指定方法是否匹配,它可以在创建代理的时候就被调用,从而决定是否需要进行代理,这样就可以避免每次方法执行的时候再去做判断
  • isRuntime(),如果这个方法返回true的话,意味着每次执行方法时还需要做一次匹配
  • matches(Method method, @Nullable Class<?> targetClass, Object... args),当之前的isRuntime方法返回true时,会调用这个方法再次进行一次判断,返回false的话,意味这个不对这个方法应用通知

Advice(通知)

环绕通知(Interception Around Advice)

接口定义如下:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

在上面接口定义的invoke方法中,MethodInvocation就是当前执行的方法,当我们调用invocation.proceed就是在执行当前的这个方法,基于此,我们可以在方法的执行前后去插入我们自定义的逻辑,比如下面这样

// 执行前的逻辑
doSomeThingBefore();
Object var = invocation.proceed;
doSomeThingAfter();
// 执行后的逻辑
retrun var;

前置通知(Before Advice)

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

跟环绕通知不同的是,这个接口中定义的方法的返回值是void,所以前置通知是无法修改方法的返回值的。

如果在前置通知中发生了异常,那么会直接终止目标方法的执行以及打断整个拦截器链的执行

后置通知(After Returning Advice)

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

后置通知相比较于前置通知,主要有以下几点不同

  • 后置通知可以访问目标方法的返回值,但是不能修改
  • 后置通知是在方法执行完成后执行

异常通知(Throws Advice)

public interface ThrowsAdvice extends AfterAdvice {

}

异常通知中没有定义任何方法,它更像一个标记接口。我们在定义异常通知时需要实现这个接口,同时方法的签名也有要求

  1. 方法名称必须是afterThrowing
  2. 方法的参数个数必须是1个或者4个,如下:
public class OneParamThrowsAdvice implements ThrowsAdvice {

    // 如果只有一个参数,那么这个参数必须是要进行处理的异常
    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

public class FourParamThrowsAdvice implements ThrowsAdvice {
	
    // 如果定义了四个参数,那么这四个参数分别是
    // 1.m:目标方法
    // 2.args:执行目标方法所需要的参数
    // 3.target:目标对象
    // 4.ex:具体要处理的异常
    // 并且参数类型必须按照这个顺序定义
    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

我们可以在一个异常通知中定义多个方法,在后续的源码分析中我们会发现,这些方法最终会被注册成对应的异常的handler,像下面这样

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

引入通知(Introduction Advice)

引入通知的主要作用是可以让生成的代理类实现额外的接口。例如在上面的例子中,我们为DmzService创建一个代理对象,同时为其定义了一个引入通知

public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
	@Override
	public void run() {
		System.out.println("running!!!!");
	}
}

在这个引入通知中,我们为其引入了一个新的需要实现的接口Runnable,同时通知本身作为这个接口的实现类。

通过这个引入通知,我们可以将生成的代理类强转成Runnable类型然后执行其run方法,同时,run方法也会被前面定义的前置通知,后置通知等拦截。

为了更好的了解引入通知,我们来需要了解下DelegatingIntroductionInterceptor这个类。见名知意,这个类就是一个委托引入拦截器,因为我们要为代理类引入新的接口,因为着我们要提供具体的实现的逻辑,而具体的实现的逻辑就可以被委托给这个DelegatingIntroductionInterceptor

我们可以看看它的源码

public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
		implements IntroductionInterceptor {
	
    // 实际实现了引入逻辑的类
	@Nullable
	private Object delegate;
	
    // 对外提供了一个带参的构造函数,通过这个构造函数我们可以传入一个
    // 具体的实现类
	public DelegatingIntroductionInterceptor(Object delegate) {
		init(delegate);
	}
    // 对子类暴露了一个空参的构造函数,默认将自身作为实现了引入逻辑的委托类
    // 我们上面的例子中就是使用的这种方法
	protected DelegatingIntroductionInterceptor() {
		init(this);
	}
	
    // 对这个类进行初始化,要通过实际的实现类来找到具体要实现的接口
	private void init(Object delegate) {
		Assert.notNull(delegate, "Delegate must not be null");
		this.delegate = delegate;
        
        // 找到delegate所有实现的接口
		implementInterfacesOnObject(delegate);
		
        // 因为我们可能会将DelegatingIntroductionInterceptor本身作为委托者
        // Spring的设计就是不对外暴露这两个接口
        // 如果将其暴露,意味着我们可以将代理类强转成这种类型
		suppressInterface(IntroductionInterceptor.class);
		suppressInterface(DynamicIntroductionAdvice.class);
	}

	// 引入通知本身也是基于拦截器实现的,当执行一个方法时需要判断这个方法
    // 是不是被引入的接口中定义的方法,如果是的话,那么不能调用目标类的方法
    // 而要调用委托类的方法
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (isMethodOnIntroducedInterface(mi)) {
			Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
			// 这里是处理一种特殊情况,方法的返回值是this的时候
            // 这里应该返回代理类
			if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
				Object proxy = ((ProxyMethodInvocation) mi).getProxy();
				if (mi.getMethod().getReturnType().isInstance(proxy)) {
					retVal = proxy;
				}
			}
            // 其余情况下直接将委托类的执行结果返回
			return retVal;
		}
        // 执行到这里说明不是引入的方法,这是Spring提供了一个扩展逻辑
        // 正常来说这个类只会处理引入的逻辑,通过这个方法可以对目标类中的方法做拦截
        // 不常用
		return doProceed(mi);
	}

	protected Object doProceed(MethodInvocation mi) throws Throwable {
		return mi.proceed();
	}

}

通过查看这个类的源码我们可以发现,所谓的引入其实就是在方法执行的时候加了一层拦截,当判断这个方法是被引入的接口提供的方法的时候,那么就执行委托类中的逻辑而不是目标类中的方法

关于通知的总结

通过上文的分析我们可以发现,通知总共可以分为这么几类

  1. 普通的通知(前置,后置,异常等,没有实现MethodInterceptor接口)
  2. 环绕通知(实现了MethodInterceptor接口)
  3. 引入通知(需要提供额外的引入的信息,实现了MethodInterceptor接口)

上面的分类并不标准,只是为了方便大家记忆跟理解,虽然我们普通的通知没有直接实现MethodInterceptor接口,但其实它的底层也是依赖于拦截器来完成的,大家可以看看下面这个类

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}
	
    // 根据传入的一个前置通知,创建一个对应的拦截器
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

	private final MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

	// 实际上还是利用拦截器,在方法执行前调用了通知的before方法完成了前置通知
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}

}

Advisor (绑定通知跟切点)

一个Advisor实际上就是一个绑定在指定切点上的通知。在前面的例子我们可以发现,有两种添加通知的方式

// 一个Advisor代表的是一个已经跟指定切点绑定了的通知
// 在这个例子中意味着环绕通知不会作用到toString方法上
Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());

// 添加一个绑定了指定切点的环绕通知
proxyFactory.addAdvisor(advisor);

// 添加一个返回后的通知
proxyFactory.addAdvice(new DmzAfterReturnAdvice());

一种是直接添加了一个Advisor,还有一种是添加一个Advice,后者也会被转换成一个Advisor然后再进行添加,没有指定切点的通知是没有任何意义的

public void addAdvice(Advice advice) throws AopConfigException {
    int pos = this.advisors.size();
    // 默认添加到集合的最后一个位置
    addAdvice(pos, advice);
}

// 这个方法添加通知
public void addAdvice(int pos, Advice advice) throws AopConfigException {
    Assert.notNull(advice, "Advice must not be null");
    
    // 如果是一个引入通知,那么构建一个DefaultIntroductionAdvisor
    // DefaultIntroductionAdvisor会匹配所有类
    if (advice instanceof IntroductionInfo) {
        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
    }
    // 不能直接添加一个不是IntroductionInfo的DynamicIntroductionAdvice(动态引入通知)
    else if (advice instanceof DynamicIntroductionAdvice) {
        throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
    }
    else {
        // 如果是普通的通知,那么会创建一个DefaultPointcutAdvisor
        // DefaultPointcutAdvisor所定义的切点会匹配所有类以及所有方法
        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
    }
}

ProxyCreatorSupport

这个类的主要作用是为创建一个AOP代理对象提供一些功能支持,通过它的getAopProxyFactory能获取一个创建代理对象的工厂。

// 这里我只保留了这个类中的关键代码
public class ProxyCreatorSupport extends AdvisedSupport { 

    private AopProxyFactory aopProxyFactory;

    // 空参构造,默认会创建一个DefaultAopProxyFactory
    // 通过这个ProxyFactory可以创建一个cglib代理或者jdk代理
    public ProxyCreatorSupport() {
        this.aopProxyFactory = new DefaultAopProxyFactory();
    }

    // 通过这个方法可以创建一个具体的代理对象
    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        // 实际就是使用DefaultAopProxyFactory来创建一个代理对象
        // 可以看到在调用createAopProxy方法时,传入的参数是this
        // 这是因为ProxyCreatorSupport本身就保存了创建整个代理对象所需要的配置信息
        return getAopProxyFactory().createAopProxy(this);
    }
}

image-20200701231849588

另外通过上面的UML类图还能看到,ProxyCreatorSupport继承了AdvisedSupportAdvisedSupport继承了ProxyConfig

ProxyConfig

其中ProxyConfig是所有的AOP代理工厂的父类,它包含了创建一个AOP代理所需要的基础的通用的一些配置信息

// 这里省略了一些getter跟setter方法
public class ProxyConfig implements Serializable {
	
    // 是否开启cglib代理,默认不开启使用jdk动态代理
	private boolean proxyTargetClass = false;

    // 是否启用优化,默认为false,按照官网对这个参数的解释
    // 这个优化是针对cglib,如果设计为true的话,会做一些侵入性的优化
    // 是否开启在jdk代理的情况下没有影响
    // 官网中特地说明了,除非对cglib的优化非常了解,否则不要开启这个参数
	private boolean optimize = false;
	
    // 生成的代理类是否需要实现Advised接口,这个接口可以向外提供操作通知的方法
    // 如果为false会实现
    // 为true的话,不会实现
	boolean opaque = false;
	
    // 是否将当前的配置类暴露到一个线程上下文中,如果设置为true的话
    // 可以通过AopContext.currentProxy()来获取到当前的代理对象
	boolean exposeProxy = false;
    
    // 标志着是否冻结整个配置,如果冻结了,那么配置信息将不允许修改
	private boolean frozen = false;
}

AdvisedSupport

当我们为某个对象创建代理时,除了需要上面的ProxyConfig提供的一些基础配置外,起码还需要知道

  1. 需要执行的通知是哪些?
  2. 目标对象是谁?
  3. 创建出来的代理需要实现哪些接口?

而这些配置信息是由AdvisedSupport提供的,AdvisedSupport本身实现了Advised接口,Advised接口定义了管理通知的方法。


在了解了上面的API后我们来看看Spring提供了几种创建AOP代理的方式

  1. ProxyFactoryBean
  2. ProxyFactory
  3. Auto-proxy

ProxyFactoryBean的方式创建AOP代理

使用示例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>

	<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>

	<bean id="dmzProxy"
		  class="org.springframework.aop.framework.ProxyFactoryBean">
        <!---->
		<property name="proxyInterfaces" value="java.lang.Runnable"/>
		<property name="proxyTargetClass" value="true"/>
 		<property name="target" ref="dmzService"/>
		<property name="interceptorNames">
			<list>
				<value>aroundAdvice</value>
			</list>
		</property>
	</bean>

</beans>
// 目标类
public class DmzService {
	@Override
	public String toString() {
		System.out.println("dmzService toString invoke");
		return "dmzService";
	}

	public void testAop(){
		System.out.println("testAop invoke");
	}
}

// 通知
public class DmzAroundAdvice implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("aroundAdvice invoked");
		return invocation.proceed();
	}
}

public class SourceMain {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext cc =
				new ClassPathXmlApplicationContext("application-init.xml");
		DmzService dmzProxy = ((DmzService) cc.getBean("dmzProxy"));
		dmzProxy.testAop();
	}
}

ProxyFactoryBean介绍

跟普通的FactoryBean一样,这个类的主要作用就是通过getObject方法能够获取一个Bean,不同的是这个类获取到的是代理后的Bean。

我们查看这个类的继承关系可以发现

image-20200701181049929

这个类除了实现了FactoryBean接口以及一些Aware接口外,额外还继承了ProxyCreatorSupport类。它是一个factoryBean,所以我们重点就关注它的getObject方法即可。

public Object getObject() throws BeansException {
    // 初始化通知链
    // 这里主要就是将在XML中配置的通知添加到
    // AdvisedSupport管理的配置中去
    initializeAdvisorChain();
    if (isSingleton()) {
        // 如果是单例的,那么获取一个单例的代理对象
        return getSingletonInstance();
    }
    else {
        if (this.targetName == null) {
            logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
        }
        // 如果是原型的,获取一个原型的代理对象
        return newPrototypeInstance();
    }
}

关于这段代码就不做过多分析了,它其实就两步(不管是哪种方式创建代理,都分为这两步)

  1. 完善创建代理需要的配置信息
  2. 创建代理

其中配置信息分为两部分,其一是AppConfig管理的通用的配置信息,其二是AdvisedSupport管理的通知信息。通用的配置信息我们可以直接在XML中配置,例如在上面的例子中我们就配置了proxyTargetClass属性,而通知信息即使我们在XML中配置了也还需要做一层转换,在前面我们也提到过了,所有的Advice都会被转换成Advisor添加到配置信息中。

ProxyFactory的方式创建AOP代理

使用示例(略,见开头)

ProxyFactory介绍

image-20200702084419039

从上面我们可以看出,ProxyFactory也继承自ProxyCreatorSupport,从之前的例子我们也能感受到,使用它的API来创建一个代理对象也是要先去设置相关的配置信息,最后再调用创建代理的方法

我们之后要分析的自动代理内部就是通过创建了一个ProxyFactory来获取代理对象的。

我们可以对比下ProxyFactoryBeanProxyFactory在创建代理对象时的代码

  • ProxyFactory
public Object getProxy() {
    // 调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象
    // 然后调用AopProxy对象的getProxy方法
    return createAopProxy().getProxy();
}
  • ProxyFactoryBean
private synchronized Object getSingletonInstance() {
    if (this.singletonInstance == null) {
        this.targetSource = freshTargetSource();
        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
            Class<?> targetClass = getTargetClass();
            if (targetClass == null) {
                throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
            }
            setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
        }
        super.setFrozen(this.freezeProxy);
        // 重点就看这里
        // 这里调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象
        // 而getProxy方法就是调用创建的AopProxy的getProxy方法
        this.singletonInstance = getProxy(createAopProxy());
    }
    return this.singletonInstance;
}

protected Object getProxy(AopProxy aopProxy) {
    return aopProxy.getProxy(this.proxyClassLoader);
}

综上,我们可以得出结论,不管是通过哪种方式创建AOP代理,核心代码就一句

createAopProxy().getProxy()

这句代码也是我们接下来源码分析的重点

Auto-proxy(实现自动AOP代理)

自动代理机制的实现其实很简单,就是通过Bean的后置处理器,在创建Bean的最后一步对Bean进行代理,并将代理对象放入到容器中。

实现自动代理的核心类就是AbstractAutoProxyCreator。我们来看看它的继承关系

image-20200702103750263

为了更好的体会自动代理的作用,我们对它的三个具体的实现类来进行分析,分别是

  1. BeanNameAutoProxyCreator
  2. DefaultAdvisorAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator

BeanNameAutoProxyCreator

使用示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>

	<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>

	<bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>

    <!--使用很简单,只要配置一个BeanNameAutoProxyCreator即可-->
	<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" name="autoProxyCreator">
        <!--使用cglib代理-->
		<property name="proxyTargetClass" value="true"/>
         <!--对所有以dmz开头的bean进行自动代理-->
		<property name="beanNames" value="dmz*"/>
        <!--添加两个通知-->
		<property name="interceptorNames">
			<list>
				<value>beforeAdvice</value>
				<value>aroundAdvice</value>
			</list>
		</property>
	</bean>

</beans>
public class SourceMain {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext cc =
				new ClassPathXmlApplicationContext("application-init.xml");
		DmzService dmzProxy = ((DmzService) cc.getBean("dmzService"));
		dmzProxy.testAop();
	}
}
// 程序打印:
// before invoke method [testAop],aop before logic invoked
// aroundAdvice invoked
// testAop invoke

DefaultAdvisorAutoProxyCreator

使用示例

在上面例子的基础上我们要修改配置文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>

	<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>

	<bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>
	
	<bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzBeforeAdvisor">
		<property name="advice" ref="beforeAdvice"/>
	</bean>

	<bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzAroundAdvisor">
		<property name="advice" ref="aroundAdvice"/>
	</bean>

	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
		  id="advisorAutoProxyCreator">
        <!--这两个参数标明了我们要使用所有以dmz开头的Advisor类型的通知
		    这里必须配置是Advisor,不能是Advice或者interceptor,
			可以看到DefaultAdvisorAutoProxyCreator跟BeanNameAutoProxyCreator的区别在于
			BeanNameAutoProxyCreator需要指定要被代理的bean的名称,
			而DefaultAdvisorAutoProxyCreator不需要,它会根据我们传入的Advisor
   			获取到需要被代理的切点
		-->
		<property name="usePrefix" value="true"/>
		<property name="advisorBeanNamePrefix" value="dmz"/>
		
        <property name="proxyTargetClass" value="true"/>
	</bean>
</beans>

测试代码就不放了,大家可以自行测试,肯定是没问题的

AnnotationAwareAspectJAutoProxyCreator

我们正常在使用AOP的时候都会在配置类上添加一个@EnableAspectJAutoProxy注解,这个注解干了什么事呢?

实际就是向容器中注册了一个AnnotationAwareAspectJAutoProxyCreator

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 这里导入了一个类
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
	
	boolean proxyTargetClass() default false;

	boolean exposeProxy() default false;

}

通过@EnableAspectJAutoProxy导入了一个AspectJAutoProxyRegistrar,这个类会向容器中注册一个AnnotationAwareAspectJAutoProxyCreator,对应源码如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		
        // 在这里完成的注册
		// 最终会调用到AopUtils的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法
        // 完成AnnotationAwareAspectJAutoProxyCreator这个bd的注册
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
		
        // 解析注解的属性
        // proxyTargetClass:为true的话开启cglib代理,默认为jdk代理
        // exposeProxy:是否将代理对象暴露到线程上下文中
		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
          
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

前面已经说过了,自动代理机制实际上就是Spring在内部new了一个ProxyFactory,通过它创建了一个代理对象。对应的代码就在AbstractAutoProxyCreator中的createProxy方法内,源码如下:

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                             @Nullable Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
	// 看到了吧,这里创建了一个proxyFactory
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);

    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
	// 通过proxyFactory来创建一个代理对象
    return proxyFactory.getProxy(getProxyClassLoader());
}

关于这个类的执行流程在下篇文章中我再详细介绍,接下来我们要分析的就是具体创建AOP代理的源码了。对应的核心源码就是我们之前所提到的

createAopProxy().getProxy();

这行代码分为两步,我们逐步分析

  1. 调用AopProxyFactorycreateAopProxy()方法获取一个AopProxy对象
  2. 调用AopProxy对象的getProxy()方法

核心源码分析

createAopProxy方法分析

AopProxyFactory在Spring中只有一个默认的实现类,就是DefaultAopProxyFactory,它的对应的createAopProxy的是实现代码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
	
    // 就是通过AOP相关的配置信息来决定到底是使用cglib代理还是jdk代理
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 如果开启了优化,或者ProxyTargetClass设置为true
        // 或者没有提供代理类需要实现的接口,那么使用cglib代理
        // 在前面分析参数的时候已经说过了
        // 默认情况下Optimize都为false,也不建议设置为true,因为会进行一些侵入性的优化
        // 除非你对cglib的优化非常了解,否则不建议开启
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                                             "Either an interface or a target is required for proxy creation.");
            }
            // 需要注意的是,如果需要代理的类本身就是一个接口
            // 或者需要被代理的类本身就是一个通过jdk动态代理生成的类
            // 那么不管如何设置都会使用jdk动态代理
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        // 否则都是jdk代理
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

	// 判断是否提供代理类需要实现的接口
    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
    }

}

getProxy方法分析

从对createAopProxy方法的分析可以看到,我们要么执行的是ObjenesisCglibAopProxy中的getProxy方法,要么就是JdkDynamicAopProxygetProxy方法,二者的区别在于一个是通过cglib的方式生成代理对象,而后者则是通过jdk的方式生成动态代理。

这里我只分析一个JdkDynamicAopProxy,首先我们来看看这个类的继承关系

希望你之前已经阅读过

原创 动态代理学习(一)自己动手模拟JDK动态代理

原创 动态代理学习(二)JDK动态代理源码分析

image-20200702154037428

可以看到这个类本身就是一个InvocationHandler,这意味着当调用代理对象中的方法时,最终会调用到JdkDynamicAopProxyinvoke方法。

所以对于这个类我们起码应该关注两个方法

  1. getProxy方法
  2. invoke方法

getProxy方法源码如下:

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    // 这里获取到代理类需要实现的所有的接口
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // 需要明确是否在接口定义了hashCode以及equals方法
    // 如果接口中没有定义,那么在调用代理对象的equals方法的时候
    // 如果两个对象相等,那么意味着它们的目标对象,通知以及实现的接口都相同
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

我们再来看看到底是怎么获取到需要实现的接口的

static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
    // 第一步:获取在配置中指定的需要实现的接口
    Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
    
    // 第二步:如果没有指定需要实现的接口,但是需要代理的目标类本身就是一个接口
    // 那么将其添加到代理类需要实现的接口的集合中
    // 如果目标类本身不是一个接口,但是是经过jdk代理后的一个类
    // 那么获取这个代理后的类所有实现的接口,并添加到需要实现的接口集合中
    if (specifiedInterfaces.length == 0) {
        Class<?> targetClass = advised.getTargetClass();
        if (targetClass != null) {
            if (targetClass.isInterface()) {
                advised.setInterfaces(targetClass);
            }
            else if (Proxy.isProxyClass(targetClass)) {
                advised.setInterfaces(targetClass.getInterfaces());
            }
            specifiedInterfaces = advised.getProxiedInterfaces();
        }
    }
    
    // 第三步:为代理类添加三个默认需要实现的接口,分别是
    // 1.SpringProxy,一个标记接口,代表这个类是通过Spring的AOP代理生成的
    // 2.Advised,提供了管理通知的方法
    // 3.DecoratingProxy,用户获取到真实的目标对象
    // 这个真实对象指的是在嵌套代理的情况下会获取到最终的目标对象
    // 而不是指返回这个ProxyFactory的target
    boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
    boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
    boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
    int nonUserIfcCount = 0;
    if (addSpringProxy) {
        nonUserIfcCount++;
    }
    if (addAdvised) {
        nonUserIfcCount++;
    }
    if (addDecoratingProxy) {
        nonUserIfcCount++;
    }
    Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
    System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
    int index = specifiedInterfaces.length;
    if (addSpringProxy) {
        proxiedInterfaces[index] = SpringProxy.class;
        index++;
    }
    if (addAdvised) {
        proxiedInterfaces[index] = Advised.class;
        index++;
    }
    if (addDecoratingProxy) {
        proxiedInterfaces[index] = DecoratingProxy.class;
    }
    return proxiedInterfaces;
}

invoke方法分析

在确认了需要实现的接口后,直接调用了jdk的动态代理方法,这个我们就不做分析了,接下来我们来看看Spring是如何将通知应用到代理对象上的,对应的要分析的代码就是JdkDynamicAopProxyinvoke方法,源码如下:

// 这个方法的代码稍微有点长,代码也比较难,希望大家能耐心看完
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        // 首先处理的是hashCode跟equals方法
        // 如果接口中没有定义这两个方法,那么会调用本类中定义的equals方法
        // 前面我们也说过了,只有当两个类的目标对象,通知以及实现的接口都相等的情况下
        // equals才会返回true
        // 如果接口中定义了这两个方法,那么最终会调用目标对象中的方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            return hashCode();
        }
        
        // 也就是说我们调用的是DecoratingProxy这个接口中的方法
        // 这个接口中只定义了一个getDecoratedClass方法,用于获取到
        // 最终的目标对象,在方法实现中会通过一个while循环来不断接近
        // 最终的目标对象,直到得到的目标对象不是一个被代理的对象才会返回
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        
        // 说明调用的是Advised接口中的方法,这里只是单纯的进行反射调用
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                 method.getDeclaringClass().isAssignableFrom(Advised.class)) {

            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
		
        // 说明需要将代理类暴露到线程上下文中
        // 调用AopContext.setCurrentProxy方法将其放入到一个threadLocal中
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
		
        // 接下来就是真正的执行代理逻辑了
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
		
        // 先获取整个拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        
        // 如果没有进行拦截,直接反射调用方法
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        
        // 否则开始执行整个链条
        else {
            MethodInvocation invocation =
                new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }
	
        // 这里是处理一种特殊情况,就是当执行的方法返回值为this的情况
        // 这种情况下,需要返回当前的代理对象而不是目标对象
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
            returnType != Object.class && returnType.isInstance(proxy) &&
            !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

在上面整个流程中,我们抓住核心的两步

  1. 获取整个拦截器链
  2. 开始在拦截器链上执行方法

我们先看第一步,对应源码如下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    List<Object> cached = this.methodCache.get(cacheKey);
    if (cached == null) {
        // 调用了advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
            this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
    Advised config, Method method, @Nullable Class<?> targetClass) {

    List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);

    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());

    // 是否有引入通知
    boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);

    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

    // 获取到所有的通知
    for (Advisor advisor : config.getAdvisors()) {
        // 除了引入通知外,可以认为所有的通知都是一个PointcutAdvisor
        if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            // config.isPreFiltered:代表的是配置已经过滤好了,是可以直接应用的
            // 这句代码的含义就是配置是预过滤的或者在类级别上是匹配的
            if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                // 接下来要判断在方法级别上是否匹配
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
                    // 将通知转换成对应的拦截器
                    // 有些通知本身就是拦截器,例如环绕通知
                    // 有些通知需要通过一个AdvisorAdapter来适配成对应的拦截器
                    // 例如前置通知,后置通知,异常通知等
                    // 其中MethodBeforeAdvice会被适配成MethodBeforeAdviceInterceptor
                    // AfterReturningAdvice会被适配成AfterReturningAdviceInterceptor
                    // ThrowAdvice会被适配成ThrowsAdviceInterceptor
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);

                    // 如果是动态的拦截,会创建一个InterceptorAndDynamicMethodMatcher
                    // 动态的拦截意味着需要根据具体的参数来决定是否进行拦截
                    if (mm.isRuntime()) {
                        for (MethodInterceptor interceptor : interceptors) {
                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }
                    else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }
        else if (advisor instanceof IntroductionAdvisor) {
            // 说明是引入通知
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                // 前文我们有提到过,引入通知实际就是通过一个拦截器
                // 将方法交由引入的类执行而不是目标类
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        else {
            // 可能会扩展出一些通知,一般不会
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }

    return interceptorList;
}

在构建好拦截器链后,接下来就是真正执行方法了,对应代码就是

// 先创建一个MethodInvocation
MethodInvocation invocation =
    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 开始在拦截器链上执行这个方法
retVal = invocation.proceed();

最后的关键代码就落在了ReflectiveMethodInvocationproceed方法

public Object proceed() throws Throwable {
	
    // 满足这个条件,说明执行到了最后一个拦截器,那么直接反射调用目标方法
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
      return invokeJoinpoint();
   }
	
    // 获取到下一个要执行的拦截器
   Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
   if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
      // 前面构建拦截器链的时候我们可以看到,动态的拦截的话会创建一个InterceptorAndDynamicMethodMatcher
      InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
      if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
         return dm.interceptor.invoke(this);
      }
      else {
        // 如果匹配失败了,执行拦截器链中的下一个拦截逻辑
         return proceed();
      }
   }
   else {
	  // 调用拦截器中的invoke方法,可以看到这里将this作为参数传入了
      // 所以我们在拦截器中调用 MethodInvocation的proceed时又会进行入当前这个方法
      // 然后去执行链条中的下一个拦截器 
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

总结

本文主要是为下篇文章做准备,下篇文章将会结束整个IOC流程的分析,IOC的最后一步便是为Bean创建代理。本文已经分析了代理的具体创建逻辑,在下篇文章中我们主要结合Spring的启动流程来看一看Spring是如何将通知添加到创建代理的配置信息中去的。

关于整个IOC跟AOP的模块还会有两篇文章,一篇用于结束整个IOC流程,另外一篇专门探讨Spring中循环依赖的解决。完成这两篇文章中,接下来打算用5到7篇文章对Spring的事务管理进行分析!

如果我的文章能帮到你,记得点个赞哈~!

  • 29
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
1. 简介 1.1. 概览 1.2. 使用场景 2. Spring 2.0 的新特性 2.1. 简介 2.2. 控制反转(IoC)容器 2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 间层 2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6 .3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 2.7.1. 一些变化 2.7.1.1. Jar包 2.7.1.2. XML配置 2.7.1.3. Deprecated的类和方法 2.7.1.4. Apache OJB 2.7.1.5. iBatis 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. 控制反转容器 3.1. 简介 3.2. 容器和bean的基本原理 3.2.1. 容器 3.2.1.1. 配置元数据 3.2.2. 实例化容器 3.2.2.1. 组成基于XML配置元数据 3.2.3. 多种bean 3.2.3.1. 命名bean 3.2.3.2. 实例化bean 3.2.4. 使用容器 3.3. 依赖 3.3.1. 注入依赖 3.3.1.1. Setter注入 3.3.1.2. 构造器注入 3.3.1.3. 一些例子 3.3.2. 构造器参数的解析 3.3.2.1. 构造器参数类型匹配 3.3.2.2. 构造器参数的索引 3.3.3. bean属性及构造器参数详解 3.3.3.1. 直接量(基本类型、Strings类型等。) 3.3.3.2. 引用其它的bean(协作者) 3.3.3.3. 内部bean 3.3.3.4. 集合 3.3.3.5. Nulls 3.3.3.6. XML-based configuration metadata shortcuts 3.3.3.7. 组合属性名称 3.3.4. 使用depends-on 3.3.5. 延迟初始化bean 3.3.6. 自动装配(autowire)协作者 3.3.6.1. 设置Bean使自动装配失效 3.3.7. 依赖检查 3.3.8. 方法注入 3.3.8.1. Lookup方法注入 3.3.8.2. 自定义方法的替代方案 3.4. bean的作用域 3.4.1. Singleton作用域 3.4.2. Prototype作用域 3.4.3. 其他作用域 3.4.3.1. 初始化web配置 3.4.3.2. Request作用域 3.4.3.3. Session作用域 3.4.3.4. global session作用域 3.4.3.5. 作用域bean与依赖 3.4.4. 自定义作用域 3.5. 定制bean特性 3.5.1. Lifecycle接口 3.5.1.1. 初始化回调 3.5.1.2. 析构回调 3.5.2. 了解自己 3.5.2.1. BeanFactoryAware 3.5.2.2. BeanNameAware 3.6. bean定义的继承 3.7. 容器扩展点 3.7.1. 用BeanPostProcessor定制bean 3.7.1.1. 使用BeanPostProcessor的Hello World示例 3.7.1.2. RequiredAnnotationBeanPostProcessor示例 3.7.2. 用BeanFactoryPostProcessor定制配置元数据 3.7.2.1. PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的访问 3.8.4. ApplicationContext在WEB应用的实例化 3.9. 粘合代码和可怕的singleton 3.9.1. 使用Singleton-helper类 4. 资源 4.1. 简介 4.2. Resource 接口 4.3. 内置 Resource 实现 4.3.1. UrlResource 4.3.2. ClassPathResource 4.3.3. FileSystemResource 4.3.4. ServletContextResource 4.3.5. InputStreamResource 4.3.6. ByteArrayResource 4.4. ResourceLoader 4.5. ResourceLoaderAware 接口 4.6. 把Resource作为属性来配置 4.7. Application context 和Resource 路径 4.7.1. 构造application context 4.7.1.1. 创建 ClassPathXmlApplicationContext 实例 - 简介 4.7.2. Application context构造器资源路径的通配符 4.7.2.1. Ant风格的pattern 4.7.2.2. classpath*: 前缀 4.7.2.3. 其他关于通配符的说明 4.7.3. FileSystemResource 提示 5. 校验,数据绑定,BeanWrapper,与属性编辑器 5.1. 简介 5.2. 使用Spring的Validator接口进行校验 5.3. 从错误代码到错误信息 5.4. Bean处理和BeanWrapper 5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. SpringAOP代理 6.2. @AspectJ支持 6.2.1. 启用@AspectJ支持 6.2.2. 声明一个切面 6.2.3. 声明一个切入点(pointcut) 6.2.3.1. 切入点指定者的支持 6.2.3.2. 合并切入点表达式 6.2.3.3. 共享常见的切入点(pointcut)定义 6.2.3.4. 示例 6.2.4. 声明通知 6.2.4.1. 前置通知(Before advice) 6.2.4.2. 返回后通知(After returning advice) 6.2.4.3. 抛出后通知(After throwing advice) 6.2.4.4. 后通知(After (finally) advice) 6.2.4.5. 环绕通知(Around Advice) 6.2.4.6. 通知参数(Advice parameters) 6.2.4.7. 通知(Advice)顺序 6.2.5. 引入(Introductions) 6.2.6. 切面实例化模型 6.2.7. 例子 6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.3.1. 通知(Advice) 6.3.3.2. 返回后通知(After returning advice) 6.3.3.3. 抛出异常后通知(After throwing advice) 6.3.3.4. 后通知(After (finally) advice) 6.3.3.5. 通知 6.3.3.6. 通知参数 6.3.3.7. 通知顺序 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是完全用AspectJ? 6.4.2. Spring AOP使用@AspectJ还是XML? 6.5. 混合切面类型 6.6. 代理机制 6.7. 编程方式创建@AspectJ代理 6.8. 在Spring应用使用AspectJ 6.8.1. 在Spring使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. 便利的切入点实现 7.2.4.1. 静态切入点 7.2.4.2. 动态切入点 7.2.5. 切入点的基类 7.2.6. 自定义切入点 7.3. Spring的通知API 7.3.1. 通知的生命周期 7.3.2. Spring里的通知类型 7.3.2.1. 拦截around通知 7.3.2.2. 前置通知 7.3.2.3. 异常通知 7.3.2.4. 后置通知 7.3.2.5. 引入通知 7.4. Spring里的advisor(Advisor) API 7.5. 使用ProxyFactoryBean创建AOP代理 7.5.1. 基础 7.5.2. JavaBean属性 7.5.3. 基于JDK和CGLIB的代理 7.5.4. 对接口进行代理 7.5.5. 对类进行代理 7.5.6. 使用“全局”advisor 7.6. 简化代理定义 7.7. 使用ProxyFactory通过编程创建AOP代理 7.8. 操作被通知对象 7.9. 使用“自动代理(autoproxy)”功能 7.9.1. 自动代理bean定义 7.9.1.1. BeanNameAutoProxyCreator 7.9.1.2. DefaultAdvisorAutoProxyCreator 7.9.1.3. AbstractAdvisorAutoProxyCreator 7.9.2. 使用元数据驱动的自动代理 7.10. 使用TargetSources 7.10.1. 热交换目标源 7.10.2. 池化目标源 7.10.3. 原型目标源 7.10.4. ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量 8.3.5. 示例 8.3.6. 运行集成测试 8.4. 更多资源 II. 间层数据访问 9. 事务管理 9.1. 简介 9.2. 动机 9.3. 关键抽象 9.4. 使用资源同步的事务 9.4.1. 高层次方案 9.4.2. 低层次方案 9.4.3. TransactionAwareDataSourceProxy 9.5. 声明式事务管理 9.5.1. 理解Spring的声明式事务管理实现 9.5.2. 第一个例子 9.5.3. 回滚 9.5.4. 为不同的bean配置不同的事务语义 9.5.5. <tx:advice/> 有关的设置 9.5.6. 使用 @Transactional 9.5.6.1. @Transactional 有关的设置 9.5.7. 插入事务操作 9.5.8. 结合AspectJ使用 @Transactional 9.6. 编程式事务管理 9.6.1. 使用 TransactionTemplate 9.6.2. 使用 PlatformTransactionManager 9.7. 选择编程式事务管理还是声明式事务管理 9.8. 与特定应用服务器集成 9.8.1. BEA WebLogic 9.8.2. IBM WebSphere 9.9. 公共问题的解决方案 9.9.1. 对一个特定的 DataSource 使用错误的事务管理器 9.10. 更多的资源 10. DAO支持 10.1. 简介 10.2. 一致的异常层次 10.3. 一致的DAO支持抽象类 11. 使用JDBC进行数据访问 11.1. 简介 11.1.1. Spring JDBC包结构 11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口 11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. SingleConnectionDataSource类 11.3.5. DriverManagerDataSource类 11.3.6. TransactionAwareDataSourceProxy类 11.3.7. DataSourceTransactionManager类 11.4. 用Java对象来表达JDBC操作 11.4.1. SqlQuery类 11.4.2. MappingSqlQuery类 11.4.3. SqlUpdate类 11.4.4. StoredProcedure类 11.4.5. SqlFunction类 12. 使用ORM工具进行数据访问 12.1. 简介 12.2. Hibernate 12.2.1. 资源管理 12.2.2. 在Spring的application context创建 SessionFactory 12.2.3. HibernateTemplate 12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用服务器使用Hibernate的注意点 12.3. JDO 12.3.1. 建立PersistenceManagerFactory 12.3.2. JdoTemplate和JdoDaoSupport 12.3.3. 基于原生的JDO API实现DAO 12.3.4. 事务管理 12.3.5. JdoDialect 12.4. Oracle TopLink 12.4.1. SessionFactory 抽象层 12.4.2. TopLinkTemplate 和 TopLinkDaoSupport 12.4.3. 基于原生的TopLink API的DAO实现 12.4.4. 事务管理 12.5. iBATIS SQL Maps 12.5.1. iBATIS 1.x和2.x的概览与区别 12.5.2. iBATIS SQL Maps 1.x 12.5.2.1. 创建SqlMap 12.5.2.2. 使用 SqlMapTemplate 和 SqlMapDaoSupport 12.5.3. iBATIS SQL Maps 2.x 12.5.3.1. 创建SqlMapClient 12.5.3.2. 使用 SqlMapClientTemplate 和 SqlMapClientDaoSupport 12.5.3.3. 基于原生的iBATIS API的DAO实现 12.6. JPA 12.6.1. 在Spring环境建立JPA 12.6.1.1. LocalEntityManagerFactoryBean 12.6.1.2. LocalContainerEntityManagerFactoryBean 12.6.1.3. 处理多个持久化单元 12.6.2. JpaTemplate 和 JpaDaoSupport 12.6.3. 基于原生的JPA实现DAO 12.6.4. 异常转化 12.6.5. 事务管理 12.6.6. JpaDialect III. Web 13. Web框架 13.1. 介绍 13.1.1. 与其他web框架的集成 13.1.2. Spring Web MVC框架的特点 13.2. DispatcherServlet 13.3. 控制器 13.3.1. AbstractController 和 WebContentGenerator 13.3.2. 其它的简单控制器 13.3.3. MultiActionController 13.3.4. 命令控制器 13.4. 处理器映射(handler mapping) 13.4.1. BeanNameUrlHandlerMapping 13.4.2. SimpleUrlHandlerMapping 13.4.3. 拦截器(HandlerInterceptor) 13.5. 视图与视图解析 13.5.1. 视图解析器 13.5.2. 视图解析链 13.5.3. 重定向(Rediret)到另一个视图 13.5.3.1. RedirectView 13.5.3.2. redirect:前缀 13.5.3.3. forward:前缀 13.6. 本地化解析器 13.6.1. AcceptHeaderLocaleResolver 13.6.2. CookieLocaleResolver 13.6.3. SessionLocaleResolver 13.6.4. LocaleChangeInterceptor 13.7. 使用主题 13.7.1. 简介 13.7.2. 如何定义主题 13.7.3. 主题解析器 13.8. Spring对分段文件上传(multipart file upload)的支持 13.8.1. 介绍 13.8.2. 使用MultipartResolver 13.8.3. 在表单处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select标签 13.9.8. option标签 13.9.9. options标签 13.9.10. textarea标签 13.9.11. hidden标签 13.9.12. errors标签 13.10. 处理异常 13.11. 惯例优先原则(convention over configuration) 13.11.1. 对控制器的支持: ControllerClassNameHandlerMapping 13.11.2. 对模型的支持:ModelMap (ModelAndView) 13.11.3. 对视图的支持: RequestToViewNameTranslator 13.12. 其它资源 14. 集成视图技术 14.1. 简介 14.2. JSP和JSTL 14.2.1. 视图解析器 14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.3.2.1. InternalResourceViewResolver 14.3.2.2. ResourceBundleViewResolver 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 14.4.2. Context 配置 14.4.3. 创建模板 14.4.4. 高级配置 14.4.4.1. velocity.properties 14.4.4.2. FreeMarker 14.4.5. 绑定支持和表单处理 14.4.5.1. 用于绑定的宏 14.4.5.2. 简单绑定 14.4.5.3. 表单输入生成宏 14.4.5.4. 重载HTML转码行为并使你的标签符合XHTML 14.5. XSLT 14.5.1. 写在段首 14.5.1.1. Bean 定义 14.5.1.2. 标准MVC控制器代码 14.5.1.3. 把模型数据转化为XML 14.5.1.4. 定义视图属性 14.5.1.5. 文档转换 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.6.2.1. 文档视图定义 14.6.2.2. Controller 代码 14.6.2.3. Excel视图子类 14.6.2.4. PDF视图子类 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.2.1. 配置ViewResolver 14.7.2.2. 配置View 14.7.2.3. 关于报表文件 14.7.2.4. 使用 JasperReportsMultiFormatView 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7.4.1. 配置子报表文件 14.7.4.2. 配置子报表数据源 14.7.5. 配置Exporter的参数 15. 集成其它Web框架 15.1. 简介 15.2. 通用配置 15.3. JavaServer Faces 15.3.1. DelegatingVariableResolver 15.3.2. FacesContextUtils 15.4. Struts 15.4.1. ContextLoaderPlugin 15.4.1.1. DelegatingRequestProcessor 15.4.1.2. DelegatingActionProxy 15.4.2. ActionSupport 类 15.5. Tapestry 15.5.1. 注入 Spring 托管的 beans 15.5.1.1. 将 Spring Beans 注入到 Tapestry 页面 15.5.1.2. 组件定义文件 15.5.1.3. 添加抽象访问方法 15.5.1.4. 将 Spring Beans 注入到 Tapestry 页面 - Tapestry 4.0+ 风格 15.6. WebWork 15.7. 更多资源 16. Portlet MVC框架 16.1. 介绍 16.1.1. 控制器 - MVC的C 16.1.2. 视图 - MVC的V 16.1.3. Web作用范围的Bean 16.2. DispatcherPortlet 16.3. ViewRendererServlet 16.4. 控制器 16.4.1. AbstractController和PortletContentGenerator 16.4.2. 其它简单的控制器 16.4.3. Command控制器 16.4.4. PortletWrappingController 16.5. 处理器映射 16.5.1. PortletModeHandlerMapping 16.5.2. ParameterHandlerMapping 16.5.3. PortletModeParameterHandlerMapping 16.5.4. 增加 HandlerInterceptor 16.5.5. HandlerInterceptorAdapter 16.5.6. ParameterMappingInterceptor 16.6. 视图和它们的解析 16.7. Multipart文件上传支持 16.7.1. 使用PortletMultipartResolver 16.7.2. 处理表单里的文件上传 16.8. 异常处理 16.9. Portlet应用的部署 IV. 整合 17. 使用Spring进行远程访问与Web服务 17.1. 简介 17.2. 使用RMI暴露服务 17.2.1. 使用 RmiServiceExporter 暴露服务 17.2.2. 在客户端链接服务 17.3. 使用Hessian或者Burlap通过HTTP远程调用服务 17.3.1. 为Hessian配置DispatcherServlet 17.3.2. 使用HessianServiceExporter暴露你的bean 17.3.3. 客户端连接服务 17.3.4. 使用Burlap 17.3.5. 对通过Hessian或Burlap暴露的服务使用HTTP基础认证 17.4. 使用HTTP调用器暴露服务 17.4.1. 暴露服务对象 17.4.2. 在客户端连接服务 17.5. Web服务 17.5.1. 使用JAXI-RPC暴露服务 17.5.2. 访问Web服务 17.5.3. 注册bean映射 17.5.4. 注册自己的处理方法 17.5.5. 使用XFire来暴露Web服务 17.6. 对远程接口不提供自动探测 17.7. 在选择这些技术时的一些考虑 18. Enterprise Java Bean(EJB)集成 18.1. 简介 18.2. 访问EJB 18.2.1. 概念 18.2.2. 访问本地的无状态Session Bean(SLSB) 18.2.3. 访问远程SLSB 18.3. 使用Spring提供的辅助类实现EJB组件 19. JMS 19.1. 简介 19.2. 使用Spring JMS 19.2.1. JmsTemplate 19.2.2. 连接工厂 19.2.3. (消息)目的地管理 19.2.4. 消息侦听容器 19.2.4.1. SimpleMessageListenerContainer 19.2.4.2. DefaultMessageListenerContainer 19.2.4.3. ServerSessionMessageListenerContainer 19.2.5. 事务管理 19.3. 发送一条消息 19.3.1. 使用消息转换器 19.3.2. SessionCallback 和ProducerCallback 19.4. 接收消息 19.4.1. 同步接收 19.4.2. 异步接收 - 消息驱动的POJOs 19.4.3. SessionAwareMessageListener 接口 19.4.4. MessageListenerAdapter 19.4.5. 事务的多方参与 20. JMX 20.1. 介绍 20.2. 输出bean到JMX 20.2.1. 创建一个MBeanServer 20.2.2. 复用现有的MBeanServer 20.2.3. MBean的惰性初始化 20.2.4. MBean的自动注册 20.2.5. 控制注册行为 20.3. 控制bean的管理接口 20.3.1. MBeanInfoAssembler 接口 20.3.2. 使用源码级元数据 20.3.3. 使用JDK 5.0注解 20.3.4. 源代码级的元数据类型 20.3.5. 接口AutodetectCapableMBeanInfoAssembler 20.3.6. 用Java接口定义管理接口 20.3.7. 使用MethodNameBasedMBeanInfoAssembler 20.4. 控制bean的 ObjectName 20.4.1. 从Properties读取ObjectName 20.4.2. 使用 MetadataNamingStrategy 20.5. JSR-160连接器 20.5.1. 服务器端连接器 20.5.2. 客户端连接器 20.5.3. 基于Burlap/Hessian/SOAP的JMX 20.6. 通过代理访问MBeans 20.7. 通知 20.7.1. 为通知注册监听器 20.7.2. 发布通知 20.8. 更多资源 21. JCA CCI 21.1. 介绍 21.2. 配置CCI 21.2.1. 连接器配置 21.2.2. 在Spring配置ConnectionFactory 21.2.3. 配置CCI连接 21.2.4. 使用一个 CCI 单连接 21.3. 使用Spring的 CCI访问支持 21.3.1. 记录转换 21.3.2. CciTemplate 类 21.3.3. DAO支持 21.3.4. 自动输出记录生成 21.3.5. 总结 21.3.6. 直接使用一个 CCI Connection 接口和Interaction接口 21.3.7. CciTemplate 使用示例 21.4. 建模CCI访问为操作对象 21.4.1. MappingRecordOperation 21.4.2. MappingCommAreaOperation 21.4.3. 自动输出记录生成 21.4.4. 总结 21.4.5. MappingRecordOperation 使用示例 21.4.6. MappingCommAreaOperation 使用示例 21.5. 事务 22. Spring邮件抽象层 22.1. 简介 22.2. Spring邮件抽象结构 22.3. 使用Spring邮件抽象 22.3.1. 可插拔的MailSender实现 22.4. 使用 JavaMail MimeMessageHelper 22.4.1. 创建一条简单的MimeMessage,并且发送出去 22.4.2. 发送附件和嵌入式资源(inline resources) 23. Spring的定时调度(Scheduling)和线程池(Thread Pooling) 23.1. 简介 23.2. 使用OpenSymphony Quartz 调度器 23.2.1. 使用JobDetailBean 23.2.2. 使用 MethodInvokingJobDetailFactoryBean 23.2.3. 使用triggers和SchedulerFactoryBean来包装任务 23.3. 使用JDK Timer支持类 23.3.1. 创建定制的timers 23.3.2. 使用 MethodInvokingTimerTaskFactoryBean类 23.3.3. 打包:使用TimerFactoryBean来设置任务 23.4. SpringTaskExecutor抽象 23.4.1. TaskExecutor接口 23.4.2. 何时使用TaskExecutor接口 23.4.3. TaskExecutor类型 23.4.4. 使用TaskExecutor接口 24. 动态语言支持 24.1. 介绍 24.2. 第一个例子 24.3. 定义动态语言支持的bean 24.3.1. 公共概念 24.3.1.1. <lang:language/> 元素 24.3.1.2. Refreshable bean 24.3.1.3. 内置动态语言源文件 24.3.1.4. 理解dynamic-language-backed bean context的构造器注入 24.3.2. JRuby beans 24.3.3. Groovy beans 24.3.4. BeanShell beans 24.4. 场景 24.4.1. Spring MVC控制器脚本化 24.4.2. Validator脚本化 24.5. 更多的资源 25. 注解和源代码级的元数据支持 25.1. 简介 25.2. Spring的元数据支持 25.3. 注解 25.3.1. @Required 25.3.2. Spring的其它@Annotations 25.4. 集成Jakarta Commons Attributes 25.5. 元数据和Spring AOP自动代理 25.5.1. 基本原理 25.5.2. 声明式事务管理 25.5.3. 缓冲 25.5.4. 自定义元数据 25.6. 使用属性来减少MVC web层配置 25.7. 元数据属性的其它用法 25.8. 增加对额外元数据API的支持 A. XML Schema-based configuration A.1. Introduction A.2. XML Schema-based configuration A.2.1. Referencing the schemas A.2.2. The util schema A.2.2.1. <util:constant/> A.2.2.2. <util:property-path/> A.2.2.3. <util:properties/> A.2.2.4. <util:list/> A.2.2.5. <util:map/> A.2.2.6. <util:set/> A.2.3. The jee schema A.2.3.1. <jee:jndi-lookup/> (simple) A.2.3.2. <jee:jndi-lookup/> (with single JNDI environment setting) A.2.3.3. <jee:jndi-lookup/> (with multiple JNDI environment settings) A.2.3.4. <jee:jndi-lookup/> (complex) A.2.3.5. <jee:local-slsb/> (simple) A.2.3.6. <jee:local-slsb/> (complex) A.2.3.7. <jee:remote-slsb/> A.2.4. The lang schema A.2.5. The tx (transaction) schema A.2.6. The aop schema A.2.7. The tool schema A.2.8. The beans schema A.3. Setting up your IDE A.3.1. Setting up Eclipse A.3.2. Setting up IntelliJ IDEA A.3.3. Integration issues A.3.3.1. XML parsing errors in the Resin v.3 application server B. Extensible XML authoring B.1. Introduction B.2. Authoring the schema B.3. Coding a NamespaceHandler B.4. Coding a BeanDefinitionParser B.5. Registering the handler and the schema B.5.1. META-INF/spring.handlers B.5.2. META-INF/spring.schemas C. spring-beans-2.0.dtd D. spring.tld D.1. Introduction D.2. The bind tag D.3. The escapeBody tag D.4. The hasBindErrors tag D.5. The htmlEscape tag D.6. The message tag D.7. The nestedPath tag D.8. The theme tag D.9. The transform tag E. spring-form.tld E.1. Introduction E.2. The checkbox tag E.3. The errors tag E.4. The form tag E.5. The hidden tag E.6. The input tag E.7. The label tag E.8. The option tag E.9. The options tag E.10. The password tag E.11. The radiobutton tag E.12. The select tag E.13. The textarea tag F. Spring 2.0 开发手册文化项目 F.1. 声明 F.2. 致谢 F.3. 参与人员及任务分配 F.4. Spring 2.0 正式版开发手册翻译说明 F.5. 项目历程 F.5.1. Spring 2.0 RC2 开发手册翻译项目 F.5.2. Spring 2.0 正式版开发手册翻译项目

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值