1 spring aop的目标是满足大部分的aop场景,而不是提供一个完整版的aop方案。
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/aop.html
2 aop的关键概念
a joint point 联接点,即一个具体的切面
b pointcut 切入点,对一组joint point的断言,用于描述哪些地方成为切面。
实例,定义一个pointcut,这里的pointcut会被advice引用,相当于指定了advice的作用域。
@Aspect public class SystemArchitecture { @Pointcut("within(com.longji.aop..*)") public void inAop() { } /** * A join point is in the web layer if the method is defined in a type in * the com.xyz.someapp.web package or any sub-package under that. */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() { } /** * A join point is in the service layer if the method is defined in a type * in the com.xyz.someapp.service package or any sub-package under that. */ @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() { } /** * A join point is in the data access layer if the method is defined in a * type in the com.xyz.someapp.dao package or any sub-package under that. */ @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() { } /** * A business service is the execution of any method defined on a service * interface. This definition assumes that interfaces are placed in the * "service" package, and that implementation types are in sub-packages. * * If you group service interfaces by functional area (for example, in * packages com.xyz.someapp.abc.service and com.xyz.def.service) then the * pointcut expression "execution(* com.xyz.someapp..service.*.*(..))" could * be used instead. * * Alternatively, you can write the expression using the 'bean' PCD, like so * "bean(*Service)". (This assumes that you have named your Spring service * beans in a consistent fashion.) */ @Pointcut("execution(* com.xyz.someapp.service.*.*(..))") public void businessService() { } /** * A data access operation is the execution of any method defined on a dao * interface. This definition assumes that interfaces are placed in the * "dao" package, and that implementation types are in sub-packages. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() { } }
c advice 通知,分为before、after、around,分别对应方法执行前、执行后、执行前后(需要前后共享状态的时候)。切面要做的事情就在通知里定义,尽量使用最小范围的通知。通知的概念类同于拦截器,在切面拦截。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
d introduction 引入,运行时刻为某个类动态添加新行为(即新接口)
下例为订书服务引入新的行为IAuditable,记录和获取订书时间。
@Test public void testAop() throws InterruptedException { BeanFactory factory = new XmlBeanFactory(new FileSystemResource("./src/test/resources/aop-context.xml")); BookService bookService = (BookService) factory.getBean("BookService"); System.out.print(bookService.OrderBook("Kerluse Benn", "Professional C#")); printDate((IAuditable) bookService); Thread.sleep(10000); System.out.print(bookService.OrderBook("Kerluse Benn", "Expert j2ee one-on-one")); printDate((IAuditable) bookService); } private void printDate(IAuditable auditable) { auditable.setLastModifiedDate(new Date()); System.out.println(" 订购时间为" + auditable.getLastModifiedDate()); }
public class AuditableMixin implements IAuditable, IntroductionInterceptor { private Date lastModifiedDate; public Object invoke(MethodInvocation m) throws Throwable { // TODO Add your codes here if (implementsInterface(m.getMethod().getDeclaringClass())) { return m.getMethod().invoke(this, m.getArguments()); // invoke introduced mthod,here is IAuditable } else { return m.proceed(); // delegate other method } } public Date getLastModifiedDate() { // TODO Add your codes here return lastModifiedDate; } public void setLastModifiedDate(Date date) { // TODO Add your codes here lastModifiedDate = date; } public boolean implementsInterface(Class cls) { // TODO Add your codes here return cls.isAssignableFrom(IAuditable.class); } }
public interface BookService { public String OrderComputerMagazine(String userName, String bookName); public String OrderBook(String userName, String bookName); }
public class BookServiceImpl implements BookService { public String OrderBook(String name, String bookName) { // TODO Add your codes here String result = null; result = "订购" + bookName + "成功"; return result; } public String OrderComputerMagazine(String userName, String bookName) { // TODO Add your codes here String result = null; result = "订购" + bookName + "成功"; return result; } }
public interface IAuditable { void setLastModifiedDate(Date date); Date getLastModifiedDate(); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans > <bean id="BookServiceTarget" class="com.longji.aop.BookServiceImpl" singleton="false"/> <!-- introduction advice --> <bean id="AuditableMixin" class="com.longji.aop.AuditableMixin" singleton="false"/> <!-- Introduction advisor --> <bean id="AuditableAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor" singleton="false"> <constructor-arg> <ref bean="AuditableMixin"/> </constructor-arg> </bean> <bean id="BookService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref bean="BookServiceTarget"/> </property> <property name="singleton"> <value>false</value> </property> <!-- force to use cglib --> <property name="proxyTargetClass"> <value>true</value> </property> <!-- introduction methods --> <property name="proxyInterfaces"> <value>com.longji.aop.IAuditable</value> </property> <property name="interceptorNames"> <list> <value>AuditableAdvisor</value> </list> </property> </bean> </beans>
参考(http://blogger.org.cn/blog/more.asp?name=luckystar&id=12625)
3 spring aop支持两种声明方式,一个是@AspectJ,另一个是基于xml配置的Schema-based AOP support
@AspectJ的配置如下,其中concurrentOperationExecutor是一个advice,bizService是pointcut的一个联接点。
<?xml version="1.0" encoding="UTF-8"?> <beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.longji.aop.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean> <bean id="bizService" class="com.longji.aop.BizServiceImpl" /> </beans>
@Aspect public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } @Around("com.longji.aop.SystemArchitecture.inAop()") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch (PessimisticLockingFailureException ex) { lockFailureException = ex; } } while (numAttempts <= this.maxRetries); throw lockFailureException; } }
public interface BizService { public String doBiz1(); }
基于schema的配置如下
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- this is the object that will be proxied by Spring's AOP infrastructure --> <bean id="fooService" class="com.longji.aop.DefaultFooService"/> <!-- this is the actual advice itself --> <bean id="profiler" class="com.longji.aop.SimpleProfiler"/> <aop:config> <aop:aspect ref="profiler"> <aop:pointcut id="theExecutionOfSomeFooServiceMethod" expression="execution(* com.longji.aop.FooService.getFoo(String,int)) and args(name, age)"/> <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod" method="profile"/> </aop:aspect> </aop:config> </beans>
其中fooservice是联接点,profiler是advice,pointcut直接用表达式定义了。
而这个则是把advice和pointcut关联,确定了advice的作用域
<aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod" method="profile"/>
4 对于aop技术方案的主流选择有AspectJ(提供更全面强大的aop功能)和spring aop,spring aop又分为@AspectJ 和XML for Spring AOP(schema)
spring官方更加推荐用 @AspectJ ,因为它更加模块化,灵活,功能强大,并且比较容易迁移到 AspectJ 。
当然这两种方式也可以完美共存,他们都是基于同一套代理机制。
5 spring aop可以采用两套代理机制jdk内置代理或者cglib,如非必要,推荐试用jdk内置代理。因为jdk动态代理机制使用前提是委托对象实现某个接口,当委托没有实现接口的话,spring会默认使用cglib创建代理。
cglib的代理方式也由所限制,因为cglib是基于继承实现的,对于final方法无法被覆盖,因此也无法用cglib代理。另外cglib代理机制会调用两次构造方法,一次是代理对象,一次是委托对象。
如果要强制使用cglib代理,可以在schema中配置
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
如果用 @AspectJ,则需配置
<aop:aspectj-autoproxy proxy-target-class="true"/>