AOP技术本质:
AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,他利用一种称为"横切"的技术,剖解开封装的对象内部,并将哪些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即方面。所谓"方面",简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说"对象"是一个空心的圆柱体,其中封装的是对象的属性和行为,那么面向方面编程的方法就仿佛一把利刃,把这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的"方面"了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,它们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP的核心思想就是"将应用程序中的商业逻辑同其提供支持的通用服务进行分离"。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建"方面",从而使得编译器可以在编译期间织入有关"方面"的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:
- join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
- point cut(切入点):本质上是一个捕获连接点的结构,在AOP中,可以定义一个point cut,来捕获相关方法的调用。
- advice(通知):是point cut的执行代码,是执行"方面"的具体逻辑。
- aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
- introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。
上图:
"横切"是AOP的专有名词。它是一种蕴含强大力量的相对简单的设计和编程技术,尤其是用于建立松散耦合的、可扩展的企业系统时。横切技术可以使得AOP在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。
如果不使用横切技术,软件开发是怎样的情形呢?在传统的程序中,由于横切行为的实现是分散的,开发人员很难对这些行为进行逻辑上的实现或更改。例如,用于日志记录的代码和主要用于其他职责的代码缠绕在一起。根据所解决的问题的复杂程度和作用域的不同,所引起的混乱可大可小。更改一个应用程序的日志记录策略可能涉及数百次编辑——即使可行,这也是个令人头疼的任务。
在AOP中,我们将这些具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为称为"横切关注点(Crosscutting Concern)",因为它跨越了给定编程模型中的典型职责界限。
下面我们通过一个小的Demo,演示一下AOP在Spring中的具体应用:
首先新建一个Java Project:aopproxy,增加对Spring的支持。选择Spring2.5,库选择:Spring 2.5 AOP Libraries、Spring 2.5 Core Libraries。点击Finish。
新建包lee,在下面新建六个类:
package lee; public interface Person { void info(); void run(); } --------------------------------------------------------------------------------------------- package lee; public class PersonImpl implements Person { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public void info() { System.out.println("我的名字是: " + name + ", 今年年龄为: " + age); } @Override public void run() { if(age < 45) System.out.println("我还年轻,奔跑迅速..."); else System.out.println("我年老体弱,只能慢跑..."); } } ---------------------------------------------------------------------------------------------------------- package lee; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class MyBeforeAdvisor implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("方法调用之前..."); System.out.println("下面是方法调用的信息:"); System.out.println("所执行的方法是:" + method); System.out.println("调用方法的参数是: " + args); System.out.println("目标对象是: " + target); } } ------------------------------------------------------------------------------------------------------- package lee; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class MyAfterAdvisor implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("方法调用结束..."); System.out.println("目标方法的返回值是:"); System.out.println("目标方法是:" + method); System.out.println("目标方法的参数是: " + args); System.out.println("目标对象是: " + target); } } ----------------------------------------------------------------------------------------------------- package lee; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyAroundInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("调用方法之前:invocation对象:[" + invocation + "]"); Object rval = invocation.proceed(); System.out.println("调用结束..."); return rval; } } ---------------------------------------------------------------------------------------------------- package lee; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class BeanTest { public static void main(String[] args) throws Exception { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); Person p = (Person) factory.getBean("person"); // p.info(); p.run(); } }
下面是src下的配置文件applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="personTarget" class="lee.PersonImpl"> <property name="name"> <value>Wawa</value> </property> <property name="age"> <value>51</value> </property> </bean> <bean id="myAdvisor" class="lee.MyBeforeAdvisor" /> <bean id="myAroundInterceptor" class="lee.MyAroundInterceptor" /> <bean id="runAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- advice属性确定处理bean --> <property name="advice"> <!-- 此处的处理bean定义采用嵌套bean, 也可引用容器的另一个bean --> <bean class="lee.MyAfterAdvisor" /> </property> <!-- patterns确定正则表达式模式 --> <property name="patterns"> <list> <!-- 确定正则表达式列表 --> <value>.*run.*</value> </list> </property> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>lee.Person</value> </property> <property name="target"> <ref local="personTarget"/> </property> <property name="interceptorNames"> <list> <value>runAdvisor</value> <value>myAdvisor</value> <value>myAroundInterceptor</value> </list> </property> </bean> </beans>
运行BeanTest类的run方法的结果:
log4j:WARN No appenders could be found for logger (org.springframework.beans.factory.xml.XmlBeanDefinitionReader). log4j:WARN Please initialize the log4j system properly. 方法调用之前... 下面是方法调用的信息: 所执行的方法是:public abstract void lee.Person.run() 调用方法的参数是: [Ljava.lang.Object;@48ff2413 目标对象是: lee.PersonImpl@7b36a43c 调用方法之前:invocation对象:[ReflectiveMethodInvocation: public abstract void lee.Person.run(); target is of class [lee.PersonImpl]] 我年老体弱,只能慢跑... 调用结束... 方法调用结束... 目标方法的返回值是: 目标方法是:public abstract void lee.Person.run() 目标方法的参数是: [Ljava.lang.Object;@669980d5 目标对象是: lee.PersonImpl@7b36a43c
运行info方法的结果:
log4j:WARN No appenders could be found for logger (org.springframework.beans.factory.xml.XmlBeanDefinitionReader). log4j:WARN Please initialize the log4j system properly. 方法调用之前... 下面是方法调用的信息: 所执行的方法是:public abstract void lee.Person.info() 调用方法的参数是: [Ljava.lang.Object;@bd10a5c 目标对象是: lee.PersonImpl@736921fd 调用方法之前:invocation对象:[ReflectiveMethodInvocation: public abstract void lee.Person.info(); target is of class [lee.PersonImpl]] 我的名字是: Wawa, 今年年龄为: 51 调用结束...
接下来,再看一个实例:使用Spring进行权限验证。
首先新建一个Java Project:authority,增加对Spring的支持。选择Spring2.5,库选择:Spring 2.5 AOP Libraries、Spring 2.5 Core Libraries。点击Finish。
新建包lee,在下面新建六个类:
package lee; public interface TestService { void view(); void modify(); } ---------------------------------------------------------------------------------------------------------- package lee; public class TestServiceImpl implements TestService { @Override public void view() { System.out.println("用户查看数据"); } @Override public void modify() { System.out.println("用户修改数据"); } } --------------------------------------------------------------------------------------------------------- package lee; public interface TestAction { public void modify2(); public void view2(); } ------------------------------------------------------------------------------------------------------ package lee; public class TestActionImpl implements TestAction { private TestService ts; public void setTs(TestService ts) { this.ts = ts; } @Override public void modify2() { ts.modify(); } @Override public void view2() { ts.view(); } } ------------------------------------------------------------------------------------------------------ package lee; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AuthorityInterceptor implements MethodInterceptor { private String user; public void setUser(String user) { this.user = user; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("================="); String methodName = invocation.getMethod().getName(); if(!user.equals("admin") && !user.equals("registerUser")) { System.out.println("您无权执行该方法"); return null; // 直接返回,不再执行后续方法。 } else if(user.equals("registerUser") && methodName.equals("modify")) { System.out.println(methodName); System.out.println("您不是管理员,无法修改数据"); return null; } else { return invocation.proceed(); // 代理过后,真实对象的方法得到执行。 } } } ---------------------------------------------------------------------------------------------------------- package lee; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class BeanTest { public static void main(String[] args) throws Exception { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); TestAction ta = (TestAction) factory.getBean("testAction"); ta.view2(); // 不同于TestService接口的方法名,以示区别。 ta.modify2(); } }
下面是ApplicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="serviceTarget" class="lee.TestServiceImpl" /> <bean id="authorityInterceptor" class="lee.AuthorityInterceptor"> <property name="user"> <value>registerUser</value> <!-- 测试:admin,aa,registerUser --> </property> </bean> <bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 也可以使用不借助接口的实现方式,直接作用于类,后面会给出相应的xml文件 --> <property name="proxyInterfaces"> <value>lee.TestService</value> </property> <property name="target"> <ref local="serviceTarget"/> </property> <property name="interceptorNames"> <list> <value>authorityInterceptor</value> </list> </property> </bean> <bean id="testAction" class="lee.TestActionImpl"> <property name="ts"> <ref local="service"/> </property> </bean> </beans>
通过修改ApplicationContext.xml中的bean-authorityInterceptor的user属性,可以看到权限验证起作用了。
此外,我们也可以直接代理实现类类,而不是接口,它本身不是Java提供的支持,而是Spring的AOP三方库提供的支持,通过直接修改Class文件的方式来达到目的。xml中相关部分如下:
<bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 也可以使用不借助接口的实现方式,直接作用于类,后面会给出相应的xml文件 --> <property name="proxyTargetClass"> <value>true</value> </property> <property name="target"> <ref local="serviceTarget"/> </property> <property name="interceptorNames"> <list> <value>authorityInterceptor</value> </list> </property> </bean>
删除TestAction接口,对TestActionImpl修改如下:
package lee; public class TestActionImpl implements TestAction { private TestServiceImpl ts; public void setTs(TestServiceImpl ts) { this.ts = ts; } @Override public void modify2() { ts.modify(); } @Override public void view2() { ts.view(); } }
运行main方法,可得到相同的结果。