第二十七部分_AOP详解

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

posted @ 2015-11-19 22:51  Code_Rush  阅读(414)  评论(0编辑  收藏  举报