`
ywg2008
  • 浏览: 46475 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

AOP介绍

阅读更多
 AOP(Aspect Oroented Programming,面向切面编程)是消除代码重复的一种方法。

      AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。

为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。

使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:
java 代码
 
  1. abstract class Worker{  
  2.   abstract void locked();  
  3.   abstract void accessDataObject();  
  4.   abstract void unlocked();  
  5. }  
AOP的缺点
 
      accessDataObject()方法需要有“锁”状态之类的相关代码。

      Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。
重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。

      仔细研究这个应用的“锁”,它其实有下列特性:
      “锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
      “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
      “锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:
 

因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)

在这个应用中,“锁”方面(aspect)应该有以下职责:

提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。

AOP应用范围

很明显,AOP非常适合开发J2EE容器服务器,目前JBoss 4.0正是使用AOP框架进行开发。
具体功能如下:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Resource pooling 资源池
Synchronization 同步

Transactions 事务AOP有必要吗?


当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。

但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。

从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。

AOP具体实现

AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),目前AOP具体实现有以下几个项目:

AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。

动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。

基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?

基于字节码的项目有:
aspectwerkz 
spring ?

定义和概念

AOP像大多数编程范式一样,有她自己的词汇表。下表定义了许多在阅读AOP相关内容或者应用AOP工作时可能会遇到的词汇和短语。这些定义不是Spring特有的。
 
表9.1 AOP定义

Term 术语
Definition 定义
Concern A particular issue
(关注特定问题)
感兴趣应用的特定问题、概念、范围。例如,事务管理、持久化、日志、安全等。
Crosscutting Concern
(横切关注点)
在关注点实现中贯穿了很多类,这在面向对象(OOP)中通常很难实现和维护。
Aspect
(切面)
模块化的横切关注点,通过代码的聚合和隔离实现。
Join Point
(连接点)
在程序或者类执行时的一个点。在Spring的AOP实现中,连接点总是一个方法调用。其他的例子包括访问字段(包括实例中字段的读写),变量和异常处理。
Advice
(通知)
特定连接点所采取的动作。Spring有几种不同类型的通知,包括around、before、throws和after returning。在这几种类型的通知中,around是最强大的,在方法调用的前后都有执行一些操作的机会。之前用到的TraceInterceptor就是around类型的通知,它实现了AOP联盟的MethodInterceptor接口。通过实现下面的Spring接口可以使用其他类型的通知:
Ø         MethodBeforeAdvice
Ø         ThrowsAdvice
Ø         AfterReturningAdvice
Pointcut
(切入点)
连接点的集合,这些连接点确认何时一个通知将会触发。切入点通常使用正则表达式或者是通配符语法。
Introduction
(引入)
添加字段或者方法到一个通知类。Spring允许你在任何通知对象上引入新的接口。例如,你可以使用引入以便让任意对象实现IsModified接口,来简化缓存。
Weaving
(组织)
装配切面来创建一个被通知对象。可以在编译期间完成(像AspectJ做的那样)也可以在运行时完成。本章后面的组织策略部分详细讨论不同的组织策略(也就是实现AOP)。
Interceptor
(拦截器)
一种AOP实现策略,对与特点的连接点,可能存在一个拦截器链。
AOP Proxy
(AOP代理)
AOP框架创建的对象,包括通知。在Spring中,一个AOP代理可能是JDK动态代理或者是CGLIB代理。
Target Object
(目标对象)
包含连接点的对象。在使用拦截的框架中,它是在拦截器链末端的对象实例。也叫做被通知对象或者被代理对象。

 
下个部分是关于切入点的,切入点是应用通知的规则。因为Spring的AOP是基于拦截器的,所以我将会用拦截器来代替通知说明问题。
 

切入点

切 入点是AOP的重要部分。他们能让你确认在何时何地调用拦截器。在某种意义上,他们通常都像是声明式的确认,但是相比确认要验证的字段,你更应该确认要检 查的方法。在上面的表格中,切入点被定义为:确认何时一个通知(拦截器)将触发的一组连接点的集合。由于Spring只支持方法调用连接点,所有在 Spring中切入点也就是应用拦截器的方法的声明。
 
在Spring的AOP中定义切入点的最简单方法是,在context文件中使用正则表达式。下面的例子为数据处理操作定义了一个切入点。
 
<bean id="dataManipulationPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"></bean>
<property name="patterns"></property>
<list></list>
<value></value>.*save.*
<value></value>.*remove.*
 
这个切入点告诉我们,拦截方法的方法名应该以save或者remove开始。
 
 注意
上例的JdkRegexpMethodPointcut类,需要J2SE1.4,它内置了正则表达式支持。也可以改用Perl5RegexpMethodPointcut,它需要Jakarta ORO包(已经在MyUsers里包括了)。
 
大多数情况下,你不必像上面一样定义单独的切入点。Spring提供了一个advisor的类,它在同一个bean中封装了拦截器和切入点。
 
对正则表达式定义的切入点来说,可以使用RegexpMethodPointcutAdvisor这个advisor。下面是一个RegularExpressionPointcutAdvice的例子,它在用户信息被保存的时候触发一个NotificationInterceptor
 
<bean id="notificationAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"></bean>
<property name="advice"></property>
<ref bean="notificationInterceptor"></ref>
<property name="pattern"></property>
<value></value>.*saveUser
 
目前,RegexpMethodPointcutAdvisor只支持Perl5的正则表达式规则,也就是说,如果你要用它,那么在你的classpath下必须要有jakarta-oro.jar 这个包。在org.springframework.aop.support包中有一个详细的切入点列表和他们对应的advisor。
 

组织策略

组织(weaving)是将切面应用到目标对象的过程。下面的列出了实现AOP的基本策略,按从简单到复杂排列。
 
 注意
在这部分大多数信息都以J2EE without EJB中的信息为基础。
Ø         JDK动态代理
Ø         动态字节码生成
Ø         自定义类加载器
Ø         语言扩展
这些策略在各种不同的开源AOP框架中都有实现。
 
 
 注意
这些框架最出色的部分是他们对在API中使用共同的标准很感兴趣。为了支持这一想法,他们创建了AOP联盟计划,定义了大量用来实现的接口。可以阅读AOP联盟的成员列表,看看都哪些框架在这个计划中。
 
下面详细描述各种组织策略。
 
JDK动态代理
 
动 态代理是J2SE1.3以上版本的内置特性。它允许你凭空(on-the-fly)创建一个或更多接口的实现。动态代理内嵌在JDK中,排除了在各种环境 下奇怪行为带来的风险。JDK动态代理有个限制就是它只能代理接口不能代理类。当然如果你用接口很好的设计了你的应用,那这就不是一个问题。
 
使用动态代理的时候还要用一些反射机制,但在J2SE1.4以上的JVM中这点性能消耗可以忽律不计。在代理接口时,Spring默认使用JDK动态代理。dynaop这个项目在代理接口时使用这个策略。
 
更多关于动态代理的信息,可以查看Java 2 SDK文档。
 
字节码动态生成
 
在 代理类时,Spring采用字节码动态生成。CGLIB (Code Generation Library)是做这个的一个流行工具。它通过动态生成子类来拦截方法。这些生成的子类改写父类的方法,用钩子(hook)调用拦截器实现。 Hibernate广泛使用CGLIB,并且已经被证明是可靠的J2EE解决方案。
 
       一个限制是,动态生成的子类不能改写和代理final方法。
 
自定义类加载器
 
使用自定义的类加载器,可以让你通知所创建的实例。这十分强大,因为它提供了修改新操作行为的机会。Jboss AOP和AspectWerkz用的这种方式,根据在XML文件中定义的方式加载和组织类。
这种方式最主要的威胁存在于,J2EE服务器必须仔细地控制类加载层次,在一个服务器上工作很好可以在另一个服务器上就不能正常工作。
 
语言扩展
AspectJ是java AOP框架实现的排头兵。它包含了语言的扩展并且使用自带的编译器,而不是使用简单的策略进行切面的组织。
 
虽 然AspectJ是非常强大和成熟的AOP实现,它的语法还是有点复杂而且也不是很直观。然而,AOP本身就不是很直观,尝试用一种新的语言去实现更显困 难。这种方式的另外一个限制是学习一门新语言的学习曲线。但是,如果你想要完整的AOP功能,包括字段级的拦截,AspectJ可能会成为你最好的伙伴。 在本章的末尾,介绍了集成AspectJ和Spring。
 
在Spring的AOP实现中,采取务实的8-2原则,它解决了最常用的部分,把更专业的部分留给其他AOP框架,而不是试图解决所有的问题。
 

便于应用的代理bean

 
像 前面提到的,为了把通知应用到context文件里定义的bean上,这些bean必须要通过代理。Spring包含很多支撑类(或者说是Proxy Beans)来简化代理。首先是ProxyFactoryBean,它允许你指明要被代理的bean和要应用的拦截器。下面的例子使用了 ProxyFactoryBean来创建一个业务对象的代理。
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"></bean>
<property name="target"></property>
<bean class="org.appfuse.service.BusinessObject"></bean>
<property name="interceptorNames"></property>
<list></list> <ref bean="loggingInterceptor"></ref>
 
上面的例子在target属性上使用了内置bean(inner-bean)。内置bean是在代理中隐藏业务对象的一个简便方法,因此在从ApplicationContext中抽取出业务bean时,它总是值得推荐的方法。
 
TransactionProxyFactoryBean 是最有用和最常用的代理Bean。这个bean允许你使用AOP在目标对象上声明式地定义事务。事务特性在之前只能通过EJB容器管理的事务(CMT)来 获得。TransactionProxyFactoryBean的使用将会在AOP例子练习部分说明。
 
 
自动代理Bean
 
前述的代理类为单个bean提供了简单的操作,但是如果你想代理多个bean或者上下文中所有的bean时怎么办?Spring提高了两个类(在org.springframework.aop.framework.autoproxy包中)简化这种处理。
 
       第一个是BeanNameAutoProxyCreator,它允许你指明一个bean名称列表作为属性。这个属性支持字面(实际的bean名)和像*Manager的通配符。可以用interceptorNames属性设置拦截器。
<bean font="" id="managerProxyCreator"></bean> 
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"></property>
<value></value>*Manager
<property name="interceptorNames"></property>
<list></list>
<value></value>loggingInterceptor
 
       第二个,更通用的多bean代理创建者是DefaultAdvisorAutoProxyCreator。使用这个代理类,简单的在context文件中定义就可以。
<bean font="" id="autoProxyCreator"></bean>
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
 
不像BeanNameAutoProxyCreator,你不能指明想用的拦截器。它将会检查context文件中的每一个advisor,指出他们的切入点是否可以用到其他的bean上。更多关于advisor的信息,请参阅Spring参考文档。
 
 
AOP实际应用的例子
 
这部分包括了几个使用AOP管理应用中横切关注点的例子,这些关注点包括事务、缓存、事件通知。
 

事务

 
指明操作应该在事务中进行,可能导致在DAO中大量的复制。使用事务时,传统的方式需要在数据操作方法中进行大量的tx.begin()tx.commit()调用。Spring和AOP带来的一个好消息是,可以通过在一个位置声明及配置的方法加强事务管理。
 
       可能还不了解,但是你已经在快速开始那一章,MyUsers这个应用中使用过AOP了。使用Spring的AOP和TransactionProxyFactoryBean,可以让你声明式地在userManager这个bean上指明事务属性。下面的代码显示了一个userManager重构过的版本,这里使用了事务模板bean内置bean

xml 代码
 
  1. class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
  2. PROPAGATION_REQUIRED  
  3. PROPAGATION_REQUIRED  
  4. PROPAGATION_REQUIRED,readOnly  

警告
 
       在1.1.2版本之前,可以使用lazy-init=”true”来代替abstract=”true”在1.1.2和之后的版本,Spring会抛出异常:java.lang.IllegalArgumentException: ‘target’ is required。1.1.1版本和之前的版本允许使用lazy-init=”true”来代替abstract=”true”属性。1.1版本添加abstract属性的目的是标记父层bean不会预先初始化。
 
更多关于声明式事务和处理相应异常回滚的信息在第十章会有所涉及。
 
 

中间层缓存

 
另一个关于横切关注点的典型例子是缓存数据。增加缓存的主要目的是改进性能,特别是当获取数据是高代价操作的时候。大多数在最后一章讨论的框架都有他们自己的缓存解决方案,然而缓存是很实际的横切关注点。
 
       由于你引入缓存的动机是提高性能,可以增加一个测试类UserManagerTest测试UserManagerImpl的性能。把下面的代码加到test/org/appfuse/service/UserManagerTest.java中。
 注意
测试中的StopWatch是一个记录任务时间的实用类。
java 代码
 
  1. public void testGetUserPerformance() {  
  2.     user = new User();  
  3.     user.setFirstName("Easter");  
  4.     user.setLastName("Bunny");  
  5.   
  6.     user = mgr.saveUser(user);  
  7.   
  8.     String name = "getUser";  
  9.     StopWatch sw = new StopWatch(name);  
  10.     sw.start(name);  
  11.     log.debug("Begin timing of method '" + name + "'");  
  12.        
  13.     for (int i=0; i < 200; i++) {  
  14.         mgr.getUser(user.getId().toString());  
  15.     }  
  16.   
  17.     sw.stop();  
  18.     log.info(sw.shortSummary());  
  19.     log.debug("End timing of method '" + name + "'");  
  20. }  

执行ant test -Dtestcase=UserManagerTest(applicationContext.xml中没有配置拦截器)会得出类似图9.2的结果。在上面的测试中,获取相同的用户花费大概10秒钟。
 
 
警告
如果使用-Dtestcase=UserManager(没有“Test”后缀),会执行上一章创建的模拟测试。这些测试从Spring分离了UserManager,他们不能证明应用了在applicationContext.xml中定义的拦截器。
 
图9.2 执行ant test -Dtestcase=UserManagerTest的结果
 
       为了提升性能,在UserManagerImpl中添加一个简单的缓存(cache)。下面是缓存的基本需求
1、当需要从数据库取得对象时,把这些对象放入缓存(在本例中用简单的HashMap)。
2、当save()delete()方法调用时,从缓存中剔除对象。
 
       在MyUsers这个应用中,你可以通过添加BaseManager (在org.appfuse.service.impl 包中)实现基本的(非AOP)缓存解决方案,所有的管理方法都从中扩展。
  1. package org.appfuse.service.impl;  
  2.    
  3. // use your IDE to organize imports  
  4.    
  5. public class BaseManager {  
  6.     // Adding this log variable will allow children to re-use it  
  7.     protected final Log log = LogFactory.getLog(getClass());  
  8.     protected Map cache;  
  9.   
  10.     protected void putIntoCache(String key, Object value) {  
  11.         if (cache == null) {  
  12.             cache = new HashMap();  
  13.         }  
  14.         cache.put(key, value);  
  15.     }  
  16.   
  17.     protected void removeFromCache(String key) {  
  18.         if (cache != null) {  
  19.             cache.remove(key);  
  20.         }  
  21.     }  
  22. }  
修改UserManagerImpl使它继承BaseManager,以使用缓存,然后在每个方法中增加对缓存的调用。下面是添加缓存调用之前UserManagerImpl的简单版本:
java 代码
  1. public User getUser(String userId) {  
  2.     return dao.getUser(Long.valueOf(userId));  
  3. }  
  4. public User saveUser(User user) {  
  5.     dao.saveUser(user);  
  6.     return user;  
  7. }  
  8. public void removeUser(String userId) {  
  9.     dao.removeUser(Long.valueOf(userId));  
  10. }  
添加这些方法(注:缓存调用)之后,他们显得更详细:
  1. public User getUser(String userId) {  
  2.     // check cache for user  
  3.     User user = (User) cache.get(userId);  
  4.     if (user == null) {  
  5.         // user not in cache, fetch from database  
  6.         user = dao.getUser(Long.valueOf(userId));  
  7.         super.putIntoCache(userId, user);  
  8.     }  
  9.     return user;  
  10. }  
  11.    
  12. public User saveUser(User user) {  
  13.     dao.saveUser(user);  
  14.     // update cache with saved user  
  15.     super.putIntoCache(String.valueOf(user.getId()), user);  
  16.     return user;  
  17. }  
  18.    
  19. public void removeUser(String userId) {  
  20.     dao.removeUser(Long.valueOf(userId));  
  21.     // remove user from cache  
  22.     super.removeFromCache(userId);  
  23. }  

执行ant test -Dtestcase=UserManagerTest,在图9.3中可以看见,运行时间是9秒(快了1秒)。
图9.3 执行ant test -Dtestcase=UserManagerTest的结果

虽然在像MyUsers这样的应用中添加对来自缓存的add/remove调用很简单,但是在大一些的应用中它马上就变得困难起来。不得不记住在每个管理类中添加这些方法。你不应该过分关注缓存,因为它不是应用的核心关注点。

    使用AOP,可以去掉这些方法调用,检查应该那些需要使用缓存的方法。另外,从代码中提出缓存,可以让你更关注业务逻辑以实现工程。
To add a caching interceptor, perform the following steps:

按下面的步骤增加缓存拦截器:

1、用下面的代码在org.appfuse.aop这个包里创建一个CacheInterceptor类。

  1. package org.appfuse.aop;  
  2.   
  3. // use your IDE to organize imports  
  4.   
  5. public class CacheInterceptor implements MethodInterceptor {  
  6.     private final Log log = LogFactory.getLog(CacheInterceptor.class);  
  7.   
  8.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  9.     String name = invocation.getMethod().getName();  
  10.         Object returnValue;  
  11.   
  12.         // check cache before executing method  
  13.         if (name.indexOf("get") > -1 && !name.endsWith("s")) {  
  14.             String id = (String) invocation.getArguments()[0];  
  15.             returnValue = cache.get(id);  
  16.             if (returnValue == null) {  
  17.                 // user not in cache, proceed  
  18.                 returnValue = invocation.proceed();  
  19.                 putIntoCache(id, returnValue);  
  20.                 return returnValue;  
  21.             } else {  
  22.                 //log.debug("retrieved object id '" + id + "' from cache");  
  23.             }  
  24.         } else {  
  25.             returnValue = invocation.proceed();  
  26.   
  27.             // update cache after executing method  
  28.             if (name.indexOf("save") > -1) {  
  29.                 Method getId = returnValue.getClass().getMethod("getId"new Class[]{});  
  30.                 Long id = (Long) getId.invoke(returnValue, new Object[]{});  
  31.                 putIntoCache(String.valueOf(id), returnValue);  
  32.             } else if (name.indexOf("remove") > -1) {  
  33.                 String id = (String) invocation.getArguments()[0];  
  34.                 removeFromCache(String.valueOf(id));  
  35.             }  
  36.         }  
  37.         return returnValue;  
  38.     }  
  39.   
  40.     protected Map cache;  
  41.   
  42.     protected void putIntoCache(String key, Object value) {  
  43.         if (cache == null) {  
  44.             cache = new HashMap();  
  45.         }  
  46.         cache.put(key, value);  
  47.     }  
  48.   
  49.     protected void removeFromCache(String key) {  
  50.         if (cache != null) {  
  51.             cache.remove(key);  
  52.         }  
  53.     }  
  54. }  

2、添加cacheInterceptor这个bean到applicationContext.xml (在web/WEB-INF目录下)。
 
<bean id="cacheInterceptor" class="org.appfuse.aop.CacheInterceptor"></bean>
 
3、在userManager这个bean的配置中,用cacheInterceptor代替loggingInterceptor
 
<property name="preInterceptors"></property>
<list></list>
<ref bean="cacheInterceptor"></ref>
 
 
执行ant test -Dtestcase=UserManagerTest,会发现在性能提升上颇为夸张的结果。
图9.4 执行ant test -Dtestcase=UserManagerTest的测试结果。
 
使用缓存切面(caching aspect),性能损耗减到0!性能上夸张提升的原因是,CacheInterceptor几乎总在UserManagerImpl的方法调用之前返回数据。
 
       这里的缓存例子很简单,更健壮的缓存实现可以参考Pieter Coucke的Spring AOP Cache或者using EHCache with Spring。
 
注:
Spring AOP Cache:http://www.onthoo.com/blog/programming/2004/09/spring-aop-cache.html
using EHCache with Spring:http://opensource.atlassian.com/confluence/spring/display/DISC/Caching+the+result+of+methods+using+Spring+and+EHCache
 

事件通知

 
如果正开放面向公众的web应用,你可能想了解新用户的注册。这在批准用户之前检验用户信息时特别有用。在下面的例子中,将会创建一个NotificationInterceptor并且把它配置成新用户注册时发送电子邮件。
 
1、修改UserManagerTest,以保证当新用户注册时发送消息。下面只包含了相关的部分。用下划线来区别类中新添加的代码。添加这些代码之后,运行ant test -Dtestcase=UserManagerTest 测试一下。
java 代码
 
  1. protected void setUp() throws Exception {  
  2.     String[] paths = {"/WEB-INF/applicationContext*.xml"};  
  3.     ctx = new ClassPathXmlApplicationContext(paths);  
  4.     mgr = (UserManager) ctx.getBean("userManager");  
  5.   
  6.     // Modify the mailSender bean to use Dumbster's ports  
  7.     JavaMailSenderImpl mailSender = (JavaMailSenderImpl) ctx.getBean("mailSender");  
  8.     mailSender.setPort(2525);  
  9. }  
  10.   
  11. public void testAddAndRemoveUser() throws Exception {  
  12.     user = new User();  
  13.     user.setFirstName("Easter");  
  14.     user.setLastName("Bunny");  
  15.   
  16.     // setup a simple mail server using Dumbster  
  17.     SimpleSmtpServer server = SimpleSmtpServer.start(2525);  
  18.   
  19.     user = mgr.saveUser(user);  
  20.   
  21.     server.stop();  
  22.     assertEquals(1, server.getReceievedEmailSize()); // spelling is correct  
分享到:
评论

相关推荐

    Spring之AOP介绍

    ### Spring之AOP介绍 #### 一、AOP概述 面向方面编程(Aspect-Oriented Programming,简称AOP)作为一种编程范式,旨在通过模块化的方式处理横切关注点(Cross-cutting Concerns)。传统的面向对象编程(OOP)虽然...

    SpringAOP介绍1

    【Spring AOP介绍1】 Spring AOP,全称为Spring面向切面编程,是Spring框架的重要组成部分,它提供了一种在不修改源代码的情况下,对现有代码进行增强的技术,以实现程序功能的统一控制。AOP的主要目的是降低业务...

    面向方面编程的Aop介绍

    本教程介绍 AOP 及其基本概念。AOP 及其相关的工具使您可以将基本横切关注点(如日志记录和安全)的代码,与程序的核心应用逻辑相分离。AOP 通过使代码更具有可读性、更不容易出错以及更容易设计和维护,改善了代码...

    aop介绍

    AOP(Aspect Oriented Programming)介绍 AOP 是 OOP 的延续,是 Aspect Oriented Programming 的缩写,意思是面向方面编程。AOP 实际是 GoF 设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦...

    Spring AOP介绍及源码分析

    **Spring AOP介绍** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个重要组成部分,它提供了一种在程序运行期间通过代理机制动态地将关注点(如日志、事务管理等)与业务逻辑分离的...

    spring-springMVC开发文档和AOP详解

    这本书详细介绍了Spring 3.2版本的各个模块和功能,包括依赖注入、事务管理、数据访问、Web应用以及AOP等。通过阅读,你可以了解到如何配置和使用Spring的核心特性,以及如何将这些特性整合到你的项目中,提升代码的...

    SpringBoot下的SpringAOP-day04-源代码

    2.1 AOP介绍 2.2 AOP中专业术语(难点) 2.3 AOP 入门案例 2.3.1 创建一个SpringBoot的module 2.3.1 导入jar包 2.3.2 项目工程结构 2.3.3 配置类 2.3.4 Service层 2.3.4.1 接口 2.3.4.2 实现类 2.3.5 切入点表达式 ...

    AOP.pdf

    #### 三、Spring AOP介绍 Spring框架提供了强大的AOP支持,它可以通过配置来实现AOP的功能。Spring AOP主要基于代理机制来实现,支持两种类型的代理: 1. **静态代理(Static Proxy)** 2. **动态代理(Dynamic ...

    Spring AOP 介绍

    ### Spring AOP 详细介绍 #### 一、Spring AOP 概述 Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架中的一个重要组成部分,它为开发人员提供了一种更加灵活的方式来处理横切关注点(cross-...

    SpringAOP介绍及源码分析

    那么面向切面编程AOP(Aspect-OrientedProgramming)则可以对系统需求进行很好的模软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程;前者是为了提高开发效率,而后者则使用了归纳法,把具有共性的...

    AWR6843AOP毫米波雷达的工程

    **TI AWR6843AOP介绍** TI的AWR6843AOP毫米波雷达芯片具备以下特点: 1. **频率范围**:工作在76-81GHz频段,符合全球毫米波雷达的法规要求。 2. **多模式操作**:支持点云模式、区域检测模式和快速扫描模式,可...

    Springframework核心技术AOP详细介绍文档.pdf

    面向切面编程(AOP)是一种编程范式,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,实现关注点的模块化。这种技术与面向对象编程(OOP)互为补充,在OOP中,关键模块化单元是类,而在...

    AOP 入门学习介绍文档

    面向方面编程(AOP)是软件开发中的一个重要概念,它旨在解决传统面向对象编程(OOP)中横切关注点的处理问题。AOP的主要目标是将那些分散在整个应用程序中的公共行为,比如日志记录、安全性检查、事务管理和性能...

    spring aop 详细介绍

    【Spring AOP 详细介绍】 Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个重要组成部分,它提供了在不修改源代码的情况下,对应用程序进行功能增强的能力。AOP的核心概念包括Advice...

    简单spring aop 例子

    本示例将简要介绍如何在Spring应用中实现AOP,通过实际的代码示例帮助理解其工作原理。 首先,我们要理解AOP的核心概念。AOP是一种编程范式,它允许开发者定义“切面”(Aspects),这些切面封装了特定的关注点,如...

    aopalliance-white-paper(AOP Alliance白皮书)

    本白皮书详细介绍了 AOP Alliance 的设计理念、技术规范以及如何在实际开发中应用这些规范。 #### 二、AOP Alliance 的背景与意义 - **背景**:随着软件系统变得越来越复杂,传统的面向对象编程方式难以很好地解决...

    Spring AOP面向方面编程原理:AOP概念

    通过上述介绍,我们可以看出Spring AOP不仅提供了丰富的AOP特性,还极大地方便了开发者的使用。它通过非侵入性和轻量级的设计理念,使得开发者能够轻松地将诸如日志记录、事务管理等功能添加到现有的系统中,从而...

    spring aop注解方式、xml方式示例

    下面将详细介绍Spring AOP的注解方式和XML配置方式。 ### 注解方式 #### 1. 定义切面(Aspect) 在Spring AOP中,切面是包含多个通知(advisors)的类。使用`@Aspect`注解标记切面类,例如: ```java @Aspect ...

    西门子操作面板AOP30使用说明.zip

    本说明将详细介绍AOP30的主要功能、配置方法、编程及日常使用注意事项。 一、AOP30简介 西门子AOP30操作面板是一款彩色触摸屏显示器,适用于西门子SIMATIC系列PLC(可编程逻辑控制器)系统。它具备高分辨率和丰富的...

Global site tag (gtag.js) - Google Analytics