标签:详解 imp ase 复杂 pen 学习 业务逻辑 throw div
软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题。这年头互联网也天天强调要专注嘛!
这其实也是一种“分治”或者“分类”的思想,人解决复杂问题的能力是有限的,所以为了控制复杂性,我们解决问题时通常都要对问题进行拆解,拆解的同时建立各部分之间的关系,各个击破之后整个问题也迎刃而解了。人类的思考,复杂系统的设计,计算机的算法,都能印证这一思想。额,扯远了,这跟AOP有神马关系?
面向切面编程(Aspect Oriented Programming,AOP)其实就是一种关注点分离的技术,在软件工程领域一度是非常火的研究领域。我们软件开发时经常提一个词叫做“业务逻辑”或者“业务功能”,我们的代码主要就是实现某种特定的业务逻辑。但是我们往往不能专注于业务逻辑,比如我们写业务逻辑代码的同时,还要写事务管理、缓存、日志等等通用化的功能,而且每个业务功能都要和这些业务功能混在一起,痛苦!所以,为了将业务功能的关注点和通用化功能的关注点分离开来,就出现了AOP技术。这些通用化功能的代码实现,对应的就是我们说的切面(Aspect)。
业务功能代码和切面代码分开之后,责任明确,开发者就能各自专注解决问题了,代码可以优雅的组织了,设计更加高内聚低耦合了(终极目标啊!)。但是请注意,代码分开的同时,我们如何保证功能的完整性呢? 你的业务功能依然需要有事务和日志等特性,即切面最终需要合并(专业术语叫做织入, Weave)到业务功能中。怎么做到呢? 这里就涉及AOP的底层技术啦,有三种方式:
接下来上例子!David对土豪老板定机票的例子比较满意,所以决定继续沿用这个例子。
Boss在订机票时,我们希望能够记录订机票这个操作所消耗的时间,同时记录日志(这里我们简单的在控制台打印预定成功的信息)。
我们来看普通青年的做法吧:
package com.tianmaying.aopdemo;
public class Boss {
private BookingService bookingService;
public Boss() {
this.bookingService = new QunarBookingService();
}
//...
public void goSomewhere() {
long start = System.currentTimeMillis();
//订机票
boolean status = bookingService.bookFlight();
//查看耗时
long duration = System.http://tianmaying.com/tutorial/spring-ioc#/1() - start;
System.out.println(String.format("time for booking flight is %d seconds", duration));
//记录日志
if (status) {
System.out.println("booking flight succeeded!");
} else {
System.out.println("booking flight failed!");
}
}
}
我们看到,在订机票的同时,还要处理查看耗时和记录日志,关注的事情太多了,头大啊。而且项目大了之后,除了订机票之外,很多业务功能都要写类似的代码。让AOP来拯救我们吧!
相比在IoC例子中的代码,我们让BookingService的bookFlight()方法返回一个boolean值,表示是否预定成功。这样我们可以演示如何获取被切方法的返回值。
通过AOP我们怎么做呢,David今天送出独门秘籍,告诉你通过3W方法(What-Where-When)来理解AOP。
了解了3W之后,来看看代码吧,先上LogAspect:
插入一段,POM文件不要忘记了引入Spring AOP相关的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.5</version>
</dependency>
LogAspect
package com.tianmaying.aopdemo.aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect //1
@Component
public class LogAspect {
@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2
private void logPointCut() {
}
@AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3
public void logBookingStatus(boolean retVal) { //4
if (retVal) {
System.out.println("booking flight succeeded!");
} else {
System.out.println("booking flight failed!");
}
}
}
我们看这段代码:
1通过一个@Apsect标注,表示LogAspect是一个切面,解决了What问题。2通过定义一个标注了@Pointcut的方法,定义了Where的问题,"execution(* com.tianmaying.aopdemo..*.bookFlight(..))"表示在com.tianmaying.aopdemo包或者子包种调用名称为bookFlight的地方就是切入点!定义Pioncut的语法这里不详解了,David这里要告诉你的时它的作用:解决Where的问题!3通过一个@AfterReturning标注表示在bookFlight()调用之后将切面织入,这是一个AfterReturning类型的Advice,注意这里可以通过returning属性获取bookFlight()的返回值。4这里定义了实现切面功能的代码,经过这么一番闪转腾挪,最后写日志的代码跑到这里来了!
再来看TimeRecordingAspect:
TimeRecordingAspect:
package com.tianmaying.aopdemo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimeRecordingAspect {
@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))")
private void timeRecordingPointCut() {
}
@Around("timeRecordingPointCut()") //1
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //2
long start = System.currentTimeMillis();
Object retVal = pjp.proceed(); // 3
long duration = System.currentTimeMillis() - start;
System.out.println(String.format(
"time for booking flight is %d seconds", duration));
return retVal;
}
}
回头再看SmartBoss的代码,比Boss简单多了,goSomewhere()方法中只剩下一条语句,酷的掉渣啊,只关注订机票,其他的事情都F**k Off吧!
package com.tianmaying.aopdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SmartBoss {
private BookingService bookingService;
//...
public void goSomewhere() {
bookingService.bookFlight();
}
}
当然,要让代码Run起来,还需要在App类中加上@EnableAspectJAutoProxy标注,这样Spring启动时就去去扫描AOP相关的标注,在创建对象时帮我们去执行织入过程!
例子讲完!现在我们再来逐一看看AOP中的那些名词定义,这个时候理解这些概念,你应该不会觉得冷冰冰了,应该要有一种"Ya!"的感觉啦!
切面(Aspect):指的就是通用功能的代码实现,比如我们上面演示的时间记录切面,日志切面,它们都是普通的Java类:TimeRecordingAspect和LogAspect。
目标对象(Target):要被织入切面的对象,例子中的CtripBookingService,有了AOP,它们可以专注于核心业务逻辑代码了!
切入点(Pointcut):定义通知应该切入到什么地方,Spring支持的切入点就是方法调用,切入点的定义可以使用正则表达式,用以描述什么类型的方法调用。@Pointcut就是用来定义切入点的。
通知(Advice):切面是一个类,而通知就是类里的方法以及这个方法如何织入到目标方法的方式(用@AfterReturning和@Around标注的方法)。我们的例子中只展示了两类通知,根据织入到目标方法方式的不同,一共可以分为5种:
现在你应该理解Spring中AOP的关键知识和核心原理了,剩下的就是在David给你的3W框架下,去学习每一部分的知识了,比如不同类型通知的写法,PointCut的各种类型的定义方法,切面和目标对象之间参数传递,等等。当然,最关键的还是赶紧实践中用起来!
标签:详解 imp ase 复杂 pen 学习 业务逻辑 throw div
原文地址:http://www.cnblogs.com/Feiyang-Lafe/p/6009875.html