说道AOP不得不提到几个概念:
切面:也就是我们自己的一些业务方法。
通知:用于拦截时出发的操作。
切点:具体拦截的某个业务点。
这样说可能还是有点抽象,举个例子,下面是一个纸糊的多面体。
每个面都是一个业务方法,我们通过刺穿每一个面,都可以进入到内部,这个面就是一个切面。
刺穿的时候会发出声响,这就是一种通知。
而具体从哪个面刺入,这就是一个切入点的选择了。
这样说,应该能稍微了解一点。
那么下面看一个简单的例子:
为了便于理清关系,先放上一张相关的类图:
首先定义个接口
1 public interface IService { 2 public void withAop(); 3 public void withoutAop(); 4 }
有了接口,当然需要一个实现类:
1 public class TestAOP implements IService { 2 private String name; 3 public void withAop() { 4 System.out.println("with AOP name:"+name); 5 } 6 public void withoutAop() { 7 System.out.println("without AOP name:"+name); 8 } 9 public String getName() { 10 return name; 11 } 12 public void setName(String name) { 13 this.name = name; 14 } 15 }
这个实现类实现了接口定义的两个方法,下面我们定义几种拦截方式,这些拦截方式通过拦截的位置或者时机不同而不同。
通常有方法前拦截,方法后拦截,以及异常拦截。通过在这些拦截中编写自己的业务处理,可以达到特定的需求。
方法前拦截,需要实现MethodBeforeAdvice接口,并填写before方法。这样,当拦截到某个方法时,就会在方法执行前执行这个before()方法。
1 public class BeforeAOPInterceptor implements MethodBeforeAdvice{ 2 public void before(Method method, Object[] args, Object instance) 3 throws Throwable { 4 System.out.println("before()"+method.getName()); 5 } 6 }
同理,方法后拦截,也是如此。需要实现AfterReturningAdvice接口。
1 public class AfterAOPInterceptor implements AfterReturningAdvice{ 2 public void afterReturning(Object value, Method method, Object[] args, 3 Object instance) throws Throwable { 4 System.out.println("after()"+method.getName()); 5 } 6 }
以及异常拦截。
1 public class ThrowsAOPInterceptor implements ThrowsAdvice{ 2 public void afterThrowing(Method method,Object[] args,Object instance,AccountException ex) throws Throwable{ 3 System.out.println("after()"+method.getName()+"throws exception:"+ex); 4 } 5 public void afterThrowing(NullPointerException ex) throws Throwable{ 6 System.out.println("throws exception:"+ex); 7 } 8 }
接下来就需要配置一下spring的配置文件,把拦截器与切面方法关联起来。
参考上面的图,可以看到配置文件中的层次关系。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://www.springframework.org/schema/beans" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 <!-- 通过名字匹配 --> 7 <!-- 8 <bean id="before" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> 9 <property name="advice"> 10 <bean class="com.test.pointcut.beforeAOP"></bean> 11 </property> 12 <property name="mappedName" value="withoutAop"></property> 13 </bean> 14 --> 15 <!-- 通过正则表达式 匹配 --> 16 <bean id="before" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 17 <property name="advice"> 18 <bean class="com.test.pointcut.BeforeAOPInterceptor"></bean> 19 </property> 20 <property name="patterns"> 21 <list> 22 <value>.*out.*</value> 23 </list> 24 </property> 25 </bean> 26 <bean id="after" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 27 <property name="advice"> 28 <bean class="com.test.pointcut.AfterAOPInterceptor"></bean> 29 </property> 30 <property name="patterns"> 31 <list> 32 <value>.*out.*</value> 33 </list> 34 </property> 35 </bean> 36 <bean id="exception" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 37 <property name="advice"> 38 <bean class="com.test.pointcut.ThrowsAOPInterceptor"></bean> 39 </property> 40 <property name="patterns"> 41 <list> 42 <value>.*out.*</value> 43 </list> 44 </property> 45 </bean> 46 <!-- --> 47 <bean id="aopService" class="org.springframework.aop.framework.ProxyFactoryBean"> 48 <property name="interceptorNames"> 49 <list> 50 <value>before</value> 51 <value>after</value> 52 <value>exception</value> 53 </list> 54 </property> 55 <property name="target"> 56 <bean class="com.test.pointcut.TestAOP"> 57 <property name="name" value="Hello"></property> 58 </bean> 59 </property> 60 </bean> 61 </beans>
ProxyFactoryBean下有两个属性,一个想要拦截的目标类,一个是拦截器。而拦截器又包括两种,主要是因为定位方法的不同而分类。分别是:
RegexpMethodPointcutAdvisor 通过正则表达式来定位业务方法。
NameMatchMethodPointcutAdvisor 通过名字来定位业务方法。
定位到了业务方法,还需要添加响应的拦截器,拦截器就是上面的三种。
最后看一下测试的方法:
public class TestMain {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContextAOP.xml"));
IService hello = (IService)factory.getBean("aopService");
hello.withAop();
hello.withoutAop();
}
}
我们上面通过正则表达式定位到所有包含out的方法,其实就是withoutAOP方法。这样当执行withoutAop方法时,会触发拦截器的操作。
执行结果:
2014-12-4 16:46:58 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContextAOP.xml]
with AOP name:Hello
before()withoutAop
without AOP name:Hello
after()withoutAop
总结:
这是通过定义切入点的方式来实现AOP,通过这种编程方式,可以针对业务方法进行包装或者监控。
举个例子,比如有个业务方法想要进行数据的查询,那么可以再这个查询前面获取JDBC连接池的连接,这样就对用户屏蔽掉了复杂的申请过程。而销毁就可以放在方法后拦截函数里。
再比如,想要监控某个业务方法呗执行了多少次,那么就可以通过这样一种拦截方式,进行信息的统计,计数或者计时!
妙处多多,还待完善!
在Spring中AOP有几种配置方式,根据我对spring源码的浏览,发现几种实现方式原理如下:
1. ProxyFactoryBean
- <bean name="myController" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="interceptorNames">
- <list>
- <value>pointcut.advisor2</value>
- <value>pointcut.advisor1</value>
- <value>myRawController</value>
- </list>
- </property>
- </bean>
这个属于最费力不讨好类型的,配置起来很麻烦。原理是根据spring的获取bean的方式,继承了FactoryBean接口的bean在取bean的时候会调用对应的bean class的getObject方法。下面是ProxyFactoryBean的getObject方法:
- public Object getObject()
- throws BeansException
- {
- initializeAdvisorChain();
- if(isSingleton())
- return getSingletonInstance();
- if(targetName == null)
- logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
- return newPrototypeInstance();
- }
- private synchronized Object newPrototypeInstance()
- {
- if(logger.isTraceEnabled())
- logger.trace((new StringBuilder("Creating copy of prototype ProxyFactoryBean config: ")).append(this).toString());
- ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory());
- TargetSource targetSource = freshTargetSource();
- copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain());
- if(autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass())
- copy.setInterfaces(ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), proxyClassLoader));
- copy.setFrozen(freezeProxy);
- if(logger.isTraceEnabled())
- logger.trace((new StringBuilder("Using ProxyCreatorSupport copy: ")).append(copy).toString());
- return getProxy(copy.createAopProxy());
- }
于是,通过这种方式实现了bean的代理。不过这种方式的缺点也是显而易见的,那就是配置起来相当麻烦。
2. BeanNameAutoProxyCreator
配置方式如下:
- <bean id="userService" class="com.aop.service.UserService"/>
- <bean id="beforeAdvice" class="com.aop.advice.BeforeAdvice"/>
- <bean id="xxxxxx" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property value="beanNames">
- <list>
- <value>*service</value>
- </list>
- </property>
- <property value="interceptorNames">
- <value>beforeAdvice</value>
- </property>
- </bean>
这个类实现了BeanPostProcessor接口的子接口:SmartInstantiationAwareBeanPostProcessor,
每个被这个类care的类在取得bean实例前,会调用以下方法:
- public Object postProcessBeforeInstantiation(Class beanClass, String beanName)
- throws BeansException
- {
- Object cacheKey = getCacheKey(beanClass, beanName);
- if(!targetSourcedBeans.contains(cacheKey))
- {
- if(advisedBeans.contains(cacheKey) || nonAdvisedBeans.contains(cacheKey))
- return null;
- if(isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName))
- {
- nonAdvisedBeans.add(cacheKey);
- return null;
- }
- }
- TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
- if(targetSource != null)
- {
- targetSourcedBeans.add(beanName);
- Object specificInterceptors[] = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
- Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
- proxyTypes.put(cacheKey, proxy.getClass());
- return proxy;
- } else
- {
- return null;
- }
- }
3. <aop:config>标签
通过aop namespace下的一个标签aop:config来实现aop代理,这个也是用起来相当方便的一种配置方式
- <bean id="fooService" class="DefaultFooService"/>
- <!-- this is the actual advice itself -->
- <bean id="profiler" class="SimpleProfiler"/>
- <aop:config>
- <aop:aspect ref="profiler">
- <aop:pointcut id="aopafterMethod"
- expression="execution(* FooService.*(..))"/>
- <aop:after pointcut-ref="aopafterMethod"
- method="afterMethod"/>
- <aop:pointcut id="aopBefore"
- expression="execution(* FooService.getBefore(String)) and args(myName)"/>
- <aop:before pointcut-ref="aopBefore"
- method="beforeMethod"/>
- </aop:aspect>
- </aop:config>
配置很简短,功能很全面。
这种配置方式的原理则是在进行配置文件解析的时候,由AopNameSpaceHandler对此标签进行解析,然后
注册一个“org.springframework.aop.config.internalAutoProxyCreator” bean,这个bean的实现类是:
org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator,此类也实现了
BeanPostProcessor接口。
至此,把大致原理分析了一下。当然,分析的不是很详细,有兴趣的朋友可以跟我联系大家一起交流一下。