AOP 即 Aspect Oriental Program 面向切面编程 。首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务 所谓的周边功能,比如性能统计,日志,事务管理等等 。周边功能在Spring的变相切面编程AOP思想里,即被定义为切面 ,在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
先看示例,我们有一个类叫AspectBiz类,这个类里的方法是核心的业务功能方法,例如此时有一个核心的业务功能方法biz()。我们希望在每次使用这个核心业务功能方法biz()之前,都能自动的执行一个周边功能方法before(),在使用这个核心业务功能方法biz()之后,都能自动的执行一个周边功能方法after()。
正常的步骤是,我们在AspectBiz类中,我们可以定义一个before()和after()方法,每次调用biz()方法时,都在前启用before()方法,在后启用after()方法。假如只有在这一个能需要使用这些周边的功能,这样的方式还好。但是假如有很多的类需要使用很多的这种周边功能,我们会发现代码耦合度增高,代码的重复度也增加了。而利用AOP思想,可以处理这类问题:
我们再定义一个AspectAOP类,这个类就是专门用来定义这些切面功能方法的,我们利用AOP思想,就可以把核心功能和切面功能分开,只需要在配置文件中加以配置即可实现在AspectBiz类运行某个方法前后,使用某个切面功能。代码如下:
我们先定义一个我们日常使用的核心业务类AspectBiz:
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz!");
}
}
之后,我们定义一个切面业务类PAspect:
public class PAspect {
public void before() {
System.out.println("PAspect before");
}
public void after() {
System.out.println("PAspect after");
}
}
之后我们配置好XML文件:
<bean id="pAspect" class="parafeel.aspect.PAspect"></bean>
<bean id="aspectBiz" class="parafeel.aspect.AspectBiz"></bean>
<aop:config>
<aop:pointcut id="pPointCut" expression="execution(* parafeel.aspect.AspectBiz.*(..))"/>
<aop:aspect id="pAspectAOP" ref="pAspect">
<aop:before method="before" pointcut-ref="pPointCut"/>
<aop:after method="after" pointcut-ref="pPointCut"/>
</aop:aspect>
</aop:config>
配置文件中,我们可以看到,我们是在aop:config中配置切面信息的。aop:pointcut,设置核心功能的切点处,我们的切面功能都是在这个切点周围实现的。上面的:
<aop:pointcut id="pPointCut" expression="execution(* parafeel.aspect.AspectBiz.*(..))"/>
id为切点的名词,expression则指定具体的切点位置,切点位置的描述有很多种,上面指的是parafeel包下的aspect包下的AspectBize类下的所有方法。利用这段文字可以配置各种不同的信息,指定不同的位置、方法、类等等。
<aop:aspect id="pAspectAOP" ref="pAspect">
<aop:before method="before" pointcut-ref="pPointCut"/>
<aop:after method="after" pointcut-ref="pPointCut"/>
</aop:aspect>
而aop:aspect则是用来描述切面的,上面的id是切面名称,ref指切面对应的bean名称。里面配置的aop:before则是配置在切点前执行的方法,其中method指的是方法名,pointcut-ref指的是对应的切点名称。同理,aop:after则是配置在切点后执行的方法,其中method指定方法名,pointcut-ref指的是对用的切点名称。
我们利用Junit测试下,下面的UniteTestBase是为了设置好配置文件,方便之后进行单元测试时使用:
public class UnitTestBase {
private ClassPathXmlApplicationContext context;
private String springXmlpath;
public UnitTestBase() {}
public UnitTestBase(String springXmlpath) {
this.springXmlpath = springXmlpath;
}
@Before
public void before() {
if (springXmlpath.equals("")) {
springXmlpath = "classpath*:applicationContext.xml";
}
try {
context = new ClassPathXmlApplicationContext(springXmlpath.split("[,\\s]+"));
context.start();
} catch (BeansException e) {
e.printStackTrace();
}
}
@After
public void after() {
context.destroy();
}
@SuppressWarnings("unchecked")
protected <T extends Object> T getBean(String beanId) {
try {
return (T)context.getBean(beanId);
} catch (BeansException e) {
e.printStackTrace();
return null;
}
}
protected <T extends Object> T getBean(Class<T> clazz) {
try {
return context.getBean(clazz);
} catch (BeansException e) {
e.printStackTrace();
return null;
}
}
}
之后是单元测试:
public class tSpring extends UnitTestBase{
public tSpring() {
super("classpath:applicationContext.xml");
}
@Test
public void testS() {
AspectBiz biz = super.getBean("aspectBiz");
biz.biz();
}
我们在运行之后可以得到的结果为:很明显的可以看到,这个简单的AOP实现已经成功了。另外,在XML配置文件中,还有很多其他的配置项,例如除了after之外,还有after-returning、after-throwing,分别是切点正常运行后执行的after-returning,在切点运行时抛出异常的after-throwing。其中这些配置项中,after和after-returning是按配置文件中的配置顺序而执行的。
除了这些after和before项,还有around项,用来表示在切点执行前和执行后:
<aop:around method="around" pointcut-ref="pPointCut"/>
在配置around时,需要主要方法的参数:
public Object around(ProceedingJoinPoint pjp) {
Object object = null;
try {
System.out.println("PAspect around1");
object = pjp.proceed();
System.out.println("PAspect around2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return object;
}
运行后会发现,上面的round1和round2,依次输出。
除了上面了,我们还可以在切面方法中,去得到切点方法处的参数,比如我们在切点处有一个init方法:
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz!");
}
public void init(String bizName,int times) {
System.out.println("AspectBiz,bizName :" + bizName + " times: " + times );
}
}
我们在切面中定义一个方法
public class PAspect {
public void before() {
System.out.println("PAspect before");
}
public void after() {
System.out.println("PAspect after");
}
public Object detail(ProceedingJoinPoint pjp,String bizName,int times) throws Throwable {
System.out.println(bizName +" " +times);
StopWatch clock = new StopWatch("Deatail for1 " + bizName + " and " + times + " ");
try {
clock.start();
return pjp.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
我们在xml中配置时,制定好切面中的某个方法,对应切点的位置:
<aop:config>
<aop:pointcut id="pPointCut" expression="execution(* parafeel.aspect.AspectBiz.*(..))"/>
<aop:aspect id="pAspectAOP" ref="pAspect">
<aop:before method="before" pointcut-ref="pPointCut"/>
<aop:around method="detail" pointcut=
"execution(* parafeel.aspect.AspectBiz.init(String,int)) and args(bizName,times))"/>
<aop:after method="after" pointcut-ref="pPointCut"/>
</aop:aspect>
</aop:config>
那么我们在执行切点处的方法时,会自动的调用切面的方法,实现AOP:
@Test
public void testS() {
AspectBiz biz = super.getBean("aspectBiz");
biz.init("para", 3);
}
执行结果为:
上面的示例,我们就可以看到切面方法在切点周围执行的时候,还可以获取切点周围的参数。