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方法,可得到相同的结果。