profile是Spring3.1提供的一个新的配置项,在下面的测试示例中,又有使用了多种AOP配置方式,为了将各种配置方式进行对比在此使用了profile.在测试用例中通过使用@ActiveProfiles("four")注解指定profile的值。
Aop 是Spring 框架的核心功能之一。
Advice 通知时定义在该连接点做什么,为切面增强提供织入接口。Advice是AOP联盟定义的一个接口,在Spring中对其进行了扩展,提供了更为具体的接口如:BeforeAdvice,AfterAdvice,ThrwosAdvice等。
Pointcut切点,决定advice通知应该作用于哪个连接点,也就是说Pointcut来定义需要增强方法的集合,这些集合的选取可以按照一定的规则来完成。Spring中默认提供了JdkRegexpMethodPointcut,基于正则表达式去匹配增强方法。
Advisor通知器,在完成对既定目标的增强切面的设计(advice)和关注点的设计(Pointcut)以后,还需要将Advice和Pointcut两者关联起来,完成这个工作的就是Advisor. 通过Advisor,可以定义在哪个关注点上使用哪个通知。在Spring 中默认提供了一个DefultPointcutAdvisor,并以此为例完成AOP demo.
在Spring AOP 使用中可以通过ProxyFactoryBean来配置目标对象和切面的行为。在ProxyFactoryBean中通过interceptorNames属性来配置已经定义好的advisor.虽然名字是interceptor实际上就是配置advisor.在ProxyFactoryBean中需要为target目标对象生成一个动态代理对象proxy,从而为AOP切面的编织做好准备工作。
ProxyFactoryBean对象中创建一个代理对象的过程是:
- addGlobalAdvisor
- getSingletonInstance
/** * 返回一个代理对象。当客户端从这个FactoryBean中获取bean时调用该接口 */ public Object getObject() throws BeansException { //初始化通知器链 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."); } //返回以prototype实例 return newPrototypeInstance(); } } /** * 返回一个单例的代理对象 */ private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // 根据AOP框架来判断需要代理的接口 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); this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; } /** * 创建一个通知器链 */ private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { //如果通知器链已经初始化则直接返回 if (this.advisorChainInitialized) { return; } // 如果没有配置过通知器则抛出异常 if (!ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); } //如果没有指定targetSource则最后一个通知器不能是全局通知器 if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { throw new AopConfigException("Target required after globals"); } // 通过通知器名称获取通知器对象 for (String name : this.interceptorNames) { if (logger.isTraceEnabled()) { logger.trace("Configuring advisor or advice '" + name + "'"); } //如果通知器名称是以*结尾的且beanFactory不适ListableBeanFactory则抛出异常,否则添加全局通知器 if (name.endsWith(GLOBAL_SUFFIX)) { if (!(this.beanFactory instanceof ListableBeanFactory)) { throw new AopConfigException( "Can only use global advisors or interceptors with a ListableBeanFactory"); } addGlobalAdvisor((ListableBeanFactory) this.beanFactory, name.substring(0, name.length() - GLOBAL_SUFFIX.length())); } else { //非全局通知器 Object advice; //如果该beanFactory是单例的或者是通知器是单例的则从beanFactory中获取单例的通知器,否则获取一个原型的通知器 if (this.singleton || this.beanFactory.isSingleton(name)) { advice = this.beanFactory.getBean(name); } else { advice = new PrototypePlaceholderAdvisor(name); } //添加到通知器链中 addAdvisorOnChainCreation(advice, name); } } } //设置通知器初始化完成标识 this.advisorChainInitialized = true; } /** * 添加所有的通知器和切点 */ private void addGlobalAdvisor(ListableBeanFactory beanFactory, String prefix) { //获取beanFactory中定义的所有的通知器 String[] globalAdvisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class); // 获取beanFactory中定义的所有的Interceptor String[] globalInterceptorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class); List<Object> beans = new ArrayList<Object>(globalAdvisorNames.length + globalInterceptorNames.length); Map<Object, String> names = new HashMap<Object, String>(beans.size()); //遍历所有的通知器 for (String name : globalAdvisorNames) { Object bean = beanFactory.getBean(name); beans.add(bean); names.put(bean, name); } //遍历所有拦截器 for (String name : globalInterceptorNames) { Object bean = beanFactory.getBean(name); beans.add(bean); names.put(bean, name); } //排序 OrderComparator.sort(beans); //遍历所有,如果拦截器或通知器是以指定的前缀开始的则添加到通知器中 for (Object bean : beans) { String name = names.get(bean); if (name.startsWith(prefix)) { addAdvisorOnChainCreation(bean, name); } } } /** * 添加一个通知器 */ private void addAdvisorOnChainCreation(Object next, String name) { //转换为一个通知器 Advisor advisor = namedBeanToAdvisor(next); if (logger.isTraceEnabled()) { logger.trace("Adding advisor with name '" + name + "'"); } //添加通知器 addAdvisor(advisor); } }
在ProxyFactoryBean中 getSingletoneInstance中有调用一个createAopProxy(),该方法是定义在ProxyCreatorSupport类中
protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); }
DefaultAopProxyFactory,这里是实际创建代理的地方
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 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."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } if (!cglibAvailable) { throw new AopConfigException( "Cannot proxy target class because CGLIB2 is not available. " + "Add CGLIB to the class path or specify proxy interfaces."); } return CglibProxyFactory.createCglibProxy(config); } else { return new JdkDynamicAopProxy(config); } }
通过上述代码可以发现Spring AOP中,是通过CGLIB或者JDK 动态代理实现的。
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { throw new AopConfigException("No advisors and no TargetSource specified"); } this.advised = config; }到此我们已经完成了一个代理对象创建。当我们创建一个代理对象后调用代理对象时则会触发InvocationHandler接口的invoke方法
/** * InvocationHandler接口的invoke实现方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; //获取TargetSource TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { //如果目标对象没有实现equals方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } //如果目标对象没有实现hashCode if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // 获取目标对象 target = targetSource.getTarget(); //如果目标对象不是null,则初始化targetClass if (target != null) { targetClass = target.getClass(); } // 获取这个方法的拦截器链 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //如果拦截器链为空则表示这个方法配置配拦截器,则直接调用target对应的方法 if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { //如果有配置拦截器,那么需要构建一个ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // 沿着拦截器链进行处理 retVal = invocation.proceed(); } if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } }
在上述的代码中有一个getInterceptorsAndDynamicInterceptionAdvice()获取拦截器链
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { MethodCacheKey cacheKey = new MethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey); if (cached == null) { cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this, method, targetClass); this.methodCache.put(cacheKey, cached); } return cached; }
protected ReflectiveMethodInvocation( Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; this.method = BridgeMethodResolver.findBridgedMethod(method); this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; }
public Object proceed() throws Throwable { // We start with an index of -1 and increment early. // 从-1的索引位置开始先自增后执行拦截器,如果当前索引已经是最后一个则直接调用这个连接点 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } //获取指定索引位置的拦截器 Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); // 如果拦截器是InterceptorAndDynamicMethodMatcher if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; //如果拦截器匹配方法成功则实行该拦截器 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // 如果匹配失败则跳过该拦截器,执行下一个拦截器 return proceed(); } } else { // 如果是一个拦截器则直接调用 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
到此我们已经可以对Spring AOP 有一个基本的认识!下面是一个DEMO的实现
/** * 方法调用前的权限拦截器 * @author zhangwei_david * @version $Id: AuthInterceptor.java, v 0.1 2015年7月10日 上午10:18:12 zhangwei_david Exp $ */ public class AuthInterceptor implements MethodBeforeAdvice { /** * @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method, java.lang.Object[], java.lang.Object) */ public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("调用 方法:" + method); } }
/** * * @author zhangwei_david * @version $Id: AopDemo.java, v 0.1 2015年7月10日 上午10:21:20 zhangwei_david Exp $ */ public class AopDemo implements Say { public void say() { System.out.println("hello"); } }
/** * * * @author zhangwei_david * @version $Id: SpringAopTest.java, v 0.1 2015年7月10日 上午10:51:16 zhangwei_david Exp $ */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath*:META-INF/spring/aop-beans.xml") public class SpringAopTest { @Autowired private Say aopDemo; @Test public void test() { aopDemo.say(); } }
2015-07-10 10:40:14 [ main:375 ] - [ INFO ] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@190a65e: defining beans [authInterceptor,authPoint,authAdvisor,aopDemo,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy 调用 方法:public abstract void com.cathy.demo.spring.aop.Say.say() hello 2015-07-10 10:40:14 [ Thread-0:494 ] - [ INFO ] Closing org.springframework.context.support.GenericApplicationContext@ee68d8: startup date [Fri Jul 10 10:40:14 CST 2015]; root of context hierarchy 2015-07-10 10:40:14 [ Thread-0:494 ] - [ INFO ] Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@190a65e: defining beans [authInterceptor,authPoint,authAdvisor,aopDemo,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
其他的配置方式如下:
/** * 方法调用前的权限拦截器 * @author zhangwei_david * @version $Id: AuthInterceptor.java, v 0.1 2015年7月10日 上午10:18:12 zhangwei_david Exp $ */ @Component @Aspect public class BeforeInterceptor { @Before("execution(* *.*(..))") public void before(JoinPoint joinPoint) throws Throwable { System.out.println("before [target" + joinPoint.getTarget() + ", signature:" + joinPoint.getSignature()); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:task="http://www.springframework.org/schema/task" xmlns:util="http://www.springframework.org/schema/util" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.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 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd " default-autowire="byType"> <!-- 使用ProxyFactoryBean 实现aop --> <beans profile="one"> <!-- 定义一个通知 --> <bean id="authInterceptor" class="com.cathy.demo.spring.aop.AuthInterceptor" /> <!-- 定义切点,匹配所有方法 --> <bean id="authPoint" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*" /> </bean> <!-- 定义通知 器 --> <bean id="authAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut" ref="authPoint" /> <property name="advice" ref="authInterceptor" /> </bean> <bean id="aopDemo" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="com.cathy.demo.spring.aop.AopDemo" /> </property> <property name="interceptorNames" value="auth*" /> </bean> </beans> <!-- 优化配置,使用DefaultAdvisorAutoProxyCreator --> <beans profile="two"> <!-- 定义一个通知 --> <bean id="authInterceptor" class="com.cathy.demo.spring.aop.AuthInterceptor" /> <!-- 定义通知 器 --> <bean id="authAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern" value=".*" /> <property name="advice" ref="authInterceptor" /> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" p:usePrefix="true" p:advisorBeanNamePrefix="auth" /> <bean id="aopDemo" class="com.cathy.demo.spring.aop.AopDemo" /> </beans> <!-- 使用aop命名空间进行配置 --> <beans profile="three"> <bean id="authInterceptor" class="com.cathy.demo.spring.aop.BeforeInterceptor" /> <bean id="aopDemo" class="com.cathy.demo.spring.aop.AopDemo" /> <aop:config> <aop:aspect ref="authInterceptor"> <aop:before method="before" pointcut="execution(* *.*(..))" /> </aop:aspect> </aop:config> </beans> <!-- 自动扫描 @Aspectj --> <beans profile="four"> <aop:aspectj-autoproxy /> <context:annotation-config /> <context:component-scan base-package="com.cathy.demo.spring.*" /> </beans> </beans>
/** * * * @author zhangwei_david * @version $Id: SpringAopTest.java, v 0.1 2015年7月10日 上午10:51:16 zhangwei_david Exp $ */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath*:META-INF/spring/aop-beans.xml") @ActiveProfiles("four") public class SpringAopFourTest { @Autowired private Say aopDemo; @Test public void test() { aopDemo.say(); } }
相关推荐
#### 五、Spring AOP 的源码分析 Spring AOP的核心在于`Advisor`和`Advice`的实现,这些类通过`ProxyFactoryBean`或`CglibAopProxy`等代理工厂类生成代理对象。 1. **`Advisor`**:封装了`Pointcut`和`Advice`,是...
Spring AOP,全称Aspect Oriented Programming(面向切面编程),是Spring框架的重要组成部分,它为应用程序提供了一种模块化和声明式的方式来处理横切关注点,如日志、事务管理、性能监控等。在传统的面向对象编程...
通过这样的配置,我们可以实现在运行时动态地记录方法的执行情况,这对于调试、性能分析以及问题排查都极其有用。此外,如果需要针对不同环境(如开发、测试、生产)定制日志级别,可以通过环境变量或者不同配置文件...
Spring Boot中AOP注解方式源码分析。 Spring Boot 1.x版本和2.x版本AOP默认配置的移动。Spring AOP多种代理机制相关核心类介绍先介绍一些Spring Aop中一些核心类,大致分为三类: advisorCreator ,从spring ioc的...
在Spring源码分析中,理解AOP的设计原理对于理解整个框架的运作非常有帮助。通过AOP,我们可以在不修改源代码的情况下,通过预编译或运行时动态代理的方式为程序动态地添加功能。例如,常见的非业务需求如日志、事务...
Spring框架是开源的轻量级Java应用程序框架,主要目标是简化企业级应用程序的开发。它通过一套完整的设计模式和约定,实现了业务...通过深入分析Spring源码,我们可以更好地理解和利用这些功能来优化我们的应用程序。
以上只是Spring源码分析的部分内容,实际源码中还包括Spring的其他模块,如Spring Batch(批处理)、Spring Security(安全)、Spring Integration(集成)等。理解并掌握Spring源码,有助于我们更好地利用Spring...
- Spring AOP更强大,支持多种代理方式和通知类型,但可能带来额外的性能开销。 总结来说,代理模式是软件设计中的一个重要概念,JDK动态代理和Spring AOP则是Java世界中实现这一模式的常见手段。理解并掌握它们,...
通过阅读《Spring Security3.pdf》和《spring security3 源码分析.pdf》这两份文档,你可以对Spring Security 3的内部工作机制有更深入的理解,从而更好地在项目中运用这个强大的安全框架。同时,这也会帮助你在面临...
### Spring源码分析知识点 #### 一、Spring框架概述 Spring框架是一个全面的企业级应用开发框架,它通过一系列模块化的组件来支持不同的应用场景和技术需求。Spring的核心价值在于提供了一种简洁的方式来解决企业...
《Spring源码分析》 Spring框架作为Java领域中不可或缺的一部分,其强大之处在于它提供了丰富的功能,包括依赖注入(Dependency Injection,简称DI)、面向切面编程(Aspect-Oriented Programming,简称AOP)、事务...
Spring5源码分析笔记旨在深入理解Spring的工作原理,帮助开发者提升技能,优化代码,以及更好地利用其核心特性。以下是对Spring5源码的一些关键知识点的详细解释: 1. **依赖注入(Dependency Injection,DI)**:...
8. **源码分析**:深入理解Spring和Spring MVC的源码,有助于优化配置和解决问题。例如,了解DispatcherServlet如何调度请求,以及HandlerMapping如何找到合适的处理器。 9. **工具**:IDEA、Eclipse等集成开发环境...
第三部分深入探讨了Spring框架的内部结构、设计原理以及项目源码分析。最后一部分则探讨了开源领域的相关话题,如开源框架设计的思考、开源社区的互动等。 本书还指出,通过EasyJF开源交流社区的论坛和SVN仓库可以...
7. **源码分析** 对于深入理解Spring Security的工作原理,查看源码是十分有帮助的。例如,`AccessDecisionManager`的实现类,如`AffirmativeBased`和`UnanimousBased`,以及`AccessDecisionVoter`接口,它们共同...
容器如何读取配置文件并解析成bean定义,以及如何根据这些定义创建和管理bean,是源码分析的重点。 关于bean的生命周期,Spring提供了初始化回调方法(如init-method属性)、后处理器(BeanPostProcessor)以及销毁...
### Tom Spring5源码分析 #### 一、Spring框架中常用的设计模式 ##### 1. 设计模式概览 在软件工程领域,设计模式是指针对某一类问题的最优解决方案。通常所说的23种经典设计模式涵盖了面向对象设计的各个方面,...
通过阅读和分析Spring 1.2.6的源码,不仅可以学习到Spring的核心设计原则,还能了解到设计模式的运用,例如工厂模式、单例模式、观察者模式等。同时,这也是提升Java编程技巧和理解框架底层运作的好机会。在实际的...
源码分析是理解Spring工作原理的关键,能帮助开发者深入掌握如何在实际项目中有效地使用和优化Spring。下面将对Spring框架的核心组件、设计理念以及源码中的关键部分进行详细的解释。 1. **依赖注入(DI)** - ...
Struts作为MVC(Model-View-Controller)架构的一部分,主要用于处理前端请求和控制业务流程,而Spring则是一个全面的后端框架,提供依赖注入、AOP(面向切面编程)、事务管理等多种功能。这两者结合使用,可以构建...