一,AOP的基本思想

AOP(Aspect Oriented Programming)翻译成中文的大意是面向切面编程,主要目的解决让不该牵扯在一起的代码分离开来。

(1)认识AOP

应用程序中通常包含两类代码:一种是和业务相关的代码;第二种就是跟业务关系不是非常大的代码。比方日志管理,

异常处理等等。

在非常多情况下。这两种代码通常是写在一起,会出现代码冗余,维护困难等等问题。而AOP就是为了

解决这样的代码的耦合性产生。

我个人理解的生活中的样例来理解AOP,由于假设是初次理解AOPeasy理解得满头雾水。

我先画个出去撸串一哥们对瓶吹的样例图。然后在依据图去理解AOP在思想理论上是怎么回事:

图有点简陋,简单分析下:

一个哥们被鼓舞对瓶吹,兴致上来了就開始喝,不换气,在喝的过程中首先一个女的说爱他。可是这并没有打断他喝酒

这个主流程。在他喝酒这段时间。朋友们能够做不论什么事。拍个视频,吃点菜等等。当这个哥们喝完的时候能够说说话。

抽支烟缓缓。在这个哥们喝酒的这个过程中就是程序执行的主流程。女的说话,朋友拍视频,吃菜等等在这个主流程

的某个时间点发生的事情都是面向切面编程在主流程上切入的执行程序,一旦主流程执行完毕。这些附带在主流程上

的切面程序也随着执行完毕,实现了喝酒与拍视频等等的解耦。也就是说。喝酒是业务代码。拍视频是日志。实现了

业务代码与日志的分离。并不像没有AOP思想的时候。这个哥们一边喝酒还得一边自拍。喝一口拍一下。拍一下。喝一口,

如今有了AOP思想,你喝你的就。我拍我的视频,一句话,分离开来。切面程序能够在主程序的开头,结尾或中间不论什么一点切入切面程序。

就好比能够在喝酒前说点啥。喝酒过程中做点啥,喝完后又说点啥。

(2)AOP和OOP的对照分析

AOP、OOP在字面上尽管很类似。但却是面向不同领域的两种设计思想。

OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装。以获得更加清晰高效的逻辑单元划分。

而AOP则是针对业务处理过程中的切面进行提取。它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

这两种设计思想在目标上有着本质的差异。两者互补,共同使用。

(3)AOP与java的代理机制
AOP是一种思想。与详细的实现技术没有关系,不论什么一种符合AOP思想技术的实现,都能够看做AOP的实现。

JDK1.3以后,JAVA提供了动态代理的机制,通过JAVA的动态代理,非常easy实现AOP的思想技术实现。

二,了解spring的AOP实现技术之前,先了解java的动态代理机制

以做一件事件记录日志的动态代理类为实例,先介绍下思路:

实现动态代理类需实现InvocationHandler接口,写一个做一件事情的日志代理类(LogProxy)。这个代理类实现了InvocationHandler接口,

然后再编写一个做事情(IMakeAnything)的接口。而且实现这个做事情的接口(MakeAnything),在做事情的接口中实现类中列出做一件

事情的具体计划,然后编写一个測试程序,看看測试结果。

日志动态代理类:

package com.lanhuigu.spring.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class LogProxy implements InvocationHandler{
	private Logger logger = Logger.getLogger(this.getClass().getName());
	private Object delegate;
	//绑定代理对象
	public Object bind(Object delegate){
		this.delegate = delegate;
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), 
				delegate.getClass().getInterfaces(), this);
	}
	//针对接口进行编程
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		Object result = null;
		try {
			//在调用方法前。进行日志输出
			logger.log(Level.INFO, args[0]+"吃饭之前===>>做饭");
			//调用方法
			result = method.invoke(delegate, args);
			//在调用方法后,进行日志输出
			logger.log(Level.INFO, args[0]+"吃饭之后===>>洗碗");
			
		} catch (Exception e) {
			// TODO: handle exception
			logger.log(Level.INFO, e.toString());
		}
		return result;
	}

}
做事情的接口:

package com.lanhuigu.spring.impl;

public interface IMakeAnything {
	public void doThing(String name);
}

做事情的实现类:

package com.lanhuigu.spring.impl;

public class MakeAnything implements IMakeAnything{

	@Override
	public void doThing(String name) {
		// TODO Auto-generated method stub
		//做某件事情的具体计划
		System.out.println("===========吃饭过程==========");
	}

}
測试程序:

package com.lanhuigu.spring.test;

import org.junit.Test;

import com.lanhuigu.spring.impl.IMakeAnything;
import com.lanhuigu.spring.impl.LogProxy;
import com.lanhuigu.spring.impl.MakeAnything;

public class TestProxy {
	@Test
	public void testProxy(){
		//实现日志的重用类
		LogProxy logProxy = new LogProxy();
		IMakeAnything makeAnything = (IMakeAnything) logProxy.bind(new MakeAnything());
		makeAnything.doThing("吃饭");
	}
}
执行结果:

 INFO 04-11 22:50:42(LogProxy.java-invoke:27): 吃饭吃饭之前===>>做饭
===========吃饭过程==========
 INFO 04-11 22:50:42(LogProxy.java-invoke:31): 吃饭吃饭之后===>>洗碗

对于结果分析:

Proxy.newProxyInstance创建动态代理对象。利用反射机制调用动态代理对象方法method.invoke(delegate, args),

在运行动态代理对象方法前后记录日志,比方測试程序中就说吃饭这件事。调用动态方法method.invoke(delegate, args)

之前得做饭吧,吃完后得洗完吧,通过动态代理实现代码的反复利用。同一时候不影响主流程。


三,AOP的三个关键概念

关于AOP的理解。不同的人可能有着不同的理解。有时候自己能体会别人体会不了或者你能体会而别人体会不了。

可是关于spring的AOP,须要理解三个重要概念:Pointcut(切入点),Advice(通知),Advisor(配置器).

(1)切入点(Pointcut)

在说切入点之前。须要先了解下Join Point(连接点)的概念,Join Point是指程序执行过程中的某个阶段。

比方在程序运行到某个地方时,进行方法调用。或抛个异常等等。比如前面到的doThing()做某件事情就是

一个切入点,表示程序要在吃饭这个程序之前或之后增加Advice.

Pointcut是Join Point的集合,是程序中须要注入Advice位置的集合。指明Advice须要在什么条件下才干被触发。


(2)通知(Advice)

Advice就是某个连接点的处理逻辑。也就是像某个连接点注入的代码。如在吃饭这个连接点之前或之后注入的日志代码。

(3)Advisor

Advisor就是Pointcut和Advice的配置器,包含Pointcut和Advice,是将Advice注入程序中Pointcut的位置代码。

简单说就是靠Advisor来安排Advice放在Pointcut中的什么位置,处于调节的地位,所以被称为配置器。

以上三点主要是小试牛刀,先热热生,接下来对Pointcut,Advice和Advisor进行具体分析。


四,spring的三个切入点的实现(Pointcut具体解释)

spring主要提供3种切入点的实现:静态切入点,动态切入点和自己定义切入点。

(1)静态切入点

静态切入点仅仅限于给定的方法和目标类,而不考虑方法的參数。

spring在调用静态切入点时,仅仅在第一次的时候计算

静态切入点的位置。然后把它缓存起来,以后就不须要再进行计算。

(2)动态切入点

动态切入点与静态切入点的差别是,它不仅限于给点的方法和类。动态切入点还能够指定方法的參数。由于參数的

动态性。所以动态切入点不能缓存,须要每次调用的时候都进行计算,因此使用动态切入点对性能损耗非常大。

当切入点须要在运行时依据參数来调用通知时。就须要使用动态切入点。spring提供了一个内建的切入点:控制流切入点,

此切入点的匹配基于当前线程的调用堆栈。

大多数情况下使用静态切入点。使用动态切入点的机会相对较小。


(3)自己定义切入点

spring中切入点是在java类上,能够自己定义切入点的位置。

五,spring的通知(Advice具体解释)

spring提供5种类型的通知:Interception Around,Before,After Returning,Throw 和 Introduction

使用情况:JointPoint前后,JointPoint之前。JointPoint之后,JointPoint抛出异常时,JointPoint调用完成后。


(1)Interception Around

该通知会在JointPoint前后运行。如前面的LogProxy,在运行吃放之前之后都会运行一段日志。在spring中最

主要的通知类型为Interception Around,该通知类型实现了MethodInterceptor的接口。


(2)Before

Before通知仅仅在JointPoint前运行,该通知类须要实现MethodBeforeAdvice的接口。


(3)After Returning

After Returning通知仅仅在JointPoint后运行,该通知类须要实现AfterReturningAdvice的接口。

(4)Throw

Throw通知在抛出异常时运行。该通知类须要实现ThrowsAdvice接口。

(5)Introduction

Introduction通知在JointPoint调用运行完成后运行,该通知类需实现IntroductionInterceptor接口和IntroductionAdvisor接口。

六。spring的Advisor具体解释

在前面关于AOP三个重要概念中提到过Advisor是Pointcut和Advice的配置器,它将Advice的代码注入到PointCut中,

spring中关于AOP的使用能够用注解或XML方式配置Pointcut和Advice,一般使用XML形式配置AOP。

七。使用BeanProxyFactory创建AOP代理(大概在spring1.2的时候)

使用spring提供的org.springframework.aop.framework.ProxyFactoryBean类是创建AOP的基本方式。

八,spring的AOP实例,用实例去理解知识

把上面的吃饭日志输出改成AOP的实现方式。

(1)spring1.2老版本号通过ProxyFactoryBean创建动态代理对象实现AOP(特别注意,新版本号不这样玩,可是了解老版本号 对分析新版本号也许有帮助)

採用Interceptor Around的通知形式事项

Interceptor Around通知会在JointPoint前后运行。实现Interceptor Around通知的类须要实现MthodInterceptor接口。

实现思路例如以下:

首先实现MethodInterceptor接口,在invoke()方法里编写负责日志信息输出的代码,详细业务实现使用上面的动态代理

代码实现类,然后在配置文件里配置pointcut,最后编写測试程序。

日志代理类:

package com.lanhuigu.spring.impl;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class LogAround implements MethodInterceptor{
	private Logger logger = Logger.getLogger(this.getClass().getName());
	//***************负责日志输出的代码*****************
	/**
	 * 关于invoke()方法相关解释:
	 * 參数MethodInvocation,能够通过它活得方法的名称、程序传入的參数Object[]等。
	 * proceed()方法负责运行被调用的方法。
	 * return result为被调用方法的返回值;
	 */
	@Override
	public Object invoke(MethodInvocation arg0) throws Throwable {
		logger.log(Level.INFO, arg0.getArguments()[0]+"做饭......");
		// TODO Auto-generated method stub
		try {
			Object result = arg0.proceed();
			//返回被调用方法的返回值
			return result;
		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			logger.log(Level.INFO, arg0.getArguments()[0]+"洗碗......");
		}
		return null;
	}

}

接口类:

package com.lanhuigu.spring.impl;

public interface IMakeAnything {
	public void doThing(String name);
}

实现类:

package com.lanhuigu.spring.impl;

public class MakeAnything implements IMakeAnything{

	@Override
	public void doThing(String name) {
		// TODO Auto-generated method stub
		//做某件事情的具体计划
		System.out.println("===========吃饭过程==========");
	}

}

spring通过ProxyFactoryBean实现AOP的配置:

<?

xml version="1.0" encoding="UTF-8"?> <!-- - Application context definition for JPetStore's business layer. - Contains bean references to the transaction manager and to the DAOs in - dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation"). --> <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" 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.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- spring AOP日志输出配置start --> <!-- log负责输出日志信息 --> <bean id="log" class="com.lanhuigu.spring.impl.LogAround"/> <!-- makeAnything负责 业务逻辑的实现--> <bean id="makeAnything" class="com.lanhuigu.spring.impl.MakeAnything"/> <!-- 使用spring提供的ProxyFactoryBean来实现代理 --> <bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理接口 --> <property name="proxyInterfaces"> <value>com.lanhuigu.spring.impl.IMakeAnything</value> </property> <!-- 代理目标类 --> <property name="target"> <ref bean="makeAnything"/> </property> <!-- 处理日志的类 --> <property name="interceptorNames"> <list> <value>log</value> </list> </property> </bean> <!-- spring AOP日志输出配置end --> <!-- 负责国际化支持 --> <!-- <bean id="messageSource" class="com.lanhuigu.spring.util.ResourceBundleMessageSourceExtend"> property有两个属性名,basename,basenames 顾名思义。第一个放一个value,第二个放一个或多个value <property name="basename"> 国际化支持的定义在文件名称为message的文件里, 也就是这个地方设置什么,src下相应的配置文件为 messages.properties或 messages.class, 名字是别的也一个含义 <value>messages_en_CN</value> </property> <property name="basenames"> <list> <value>messgaes</value> <value>error</value> </list> </property> </bean> --> <!-- 定义一个id为sayHello的bean, 通过spring配置文件变换实现类,实现不同的功能。无需改动别的程序 --> <!-- <bean id="sayHello" class="com.lanhuigu.spring.action.HelloWorld" > 将变量msg值依赖注入 <property name="msg"> <value>測试</value> </property> refTest为HelloWorld的一个属性,通过ref指定依赖关系。 也就是说你依赖于哪个类,或者接口,直接把这个类通过set方式注入 , 看看HelloWorld的属性定义就明确了 <property name="refTest"> <ref bean="refTest"/> </property> </bean> --> <!-- RefTest类 --> <!-- <bean id="refTest" class="com.lanhuigu.spring.action.RefTest"> myRef为RefTest类的一个属性 <property name="myRef"> <value>依赖关系測试</value> </property> </bean> --> </beans>

測试程序:

package com.lanhuigu.spring.test;

import java.util.Calendar;
import java.util.Locale;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.lanhuigu.spring.action.HelloWorld;
import com.lanhuigu.spring.impl.IMakeAnything;

public class TestHelloWorld {
	@Test
	public void testMyHelloWorld() throws Exception{
		//1 读取spring初始化的配置文件
		ApplicationContext acxt = 
				new ClassPathXmlApplicationContext("/applicationContext.xml");
		IMakeAnything makeAnything = (IMakeAnything) acxt.getBean("logProxy");
		makeAnything.doThing("吃饭");
		/*//2 依据bean获取ISayHello实现类对象
		HelloWorld helloAC = (HelloWorld) acxt.getBean("sayHello");
		//3 调用接口方法
		System.out.println(helloAC.getMsg());
		//4 先获取依赖的类RefTest,在从依赖类中获取依赖类的属性
		//System.out.println(helloAC.getRefTest().getMyRef());
		//5.国际化測试
		//A.相应messages.properties中的两个參数{0},{1}
		Object[] objs = new Object[]{"HelloWorld",Calendar.getInstance().getTime()};
		//B.依据messages.properties中的HelloWorld获取配置。再传入objs数据參数,最后加上国家获取当前时间
		String mytest = acxt.getMessage("HelloWorld", objs, Locale.CHINA);
		System.out.println(mytest);*/
	}
}

(2)新版本号实现AOP的方式

如今对于AOP的实现技术进行类改进,通过封装对应的jar包实现AOP,更灵活。更强大,主要有两大形式:

A,XML形式实现AOP:http://blog.csdn.net/yhl_jxy/article/details/47443843

B,注解形式实现AOP:http://blog.csdn.net/yhl_jxy/article/details/47443099