AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在spring AOP中业务逻辑仅仅只关注业务本身,将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
而在spring AOP中,有before,after等通知,其中最为强大的通知就是环绕通知(Around Advice),这篇博客就是通过记录几段简单的代码来演示说明spring AOP中的Around Advide的具体使用.
首先给出spring的配置文件:
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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--启用@AspectJ支持-->
<aop:aspectj-autoproxy/>
<context:component-scan base-package="cn.hn3l">
<!--扫描所有的切面-->
<context:include-filter
type="annotation"
expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
</beans>
实体对象Article类代表文章信息,定义代码如下:
package cn.hn3l.domain.entity;
import java.util.Date;
/**
* @Author Wang Weiwei
* @Since 16-10-13
* @Describe
*/
public class Article {
private long id;
protected String title;
private String content;
private Date publishTime;
private Date lastUpdateTime;
private User author;
private ArticleType articleType;
// ... 省略其他属性
// ... omit other attributes
// ... omit getter and setter methods
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getPublishTime() {
return publishTime;
}
public void setPublishTime(Date publishTime) {
this.publishTime = publishTime;
}
public Date getLastUpdateTime() {
return lastUpdateTime;
}
public void setLastUpdateTime(Date lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public ArticleType getArticleType() {
return articleType;
}
public void setArticleType(ArticleType articleType) {
this.articleType = articleType;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
服务层接口ArticleService
package cn.hn3l.services.interfaces;
/**
* @Author Wang Weiwei
* @Since 16-10-13
* @Describe
*/
public interface ArticleService {
boolean deleteArticle(long id);
}
服务层接口的实现类ArticleServiceImpl
package cn.hn3l.services.implement;
import cn.hn3l.services.interfaces.ArticleService;
import org.springframework.stereotype.Service;
/**
* @Author Wang Weiwei
* @Since 16-10-13
* @Describe
*/
@Service
public class ArticleServiceImpl implements ArticleService {
public boolean deleteArticle(long id) {
System.out.println("-------删除文章业务方法-----");
return false;
}
}
主要内容,要想实现AOP我们必须要定义一个切面,下面的代码是使用@AspectJ的方式定义一个切面和环绕通知。
package cn.hn3l.aspects;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.lang.Object;
/**
* @Author Wang Weiwei
* @Since 16-10-13
* @Describe
*/
@Aspect
public class ArticleAspect {
Logger logger = Logger.getLogger(ArticleAspect.class);
@Pointcut("execution(* cn.hn3l.services..*Service.delete*(..))")
public void deleteMethod(){}
@Around("deleteMethod() && args(long)")
public Object aroundDeleteArticle(ProceedingJoinPoint proceedingJoinPoint){
logger.debug("--------方法执行之前---------");
//打印方法所有的参数列表
Object[] args = proceedingJoinPoint.getArgs();
for (Object arg : args){
logger.debug(arg + " , ");
}
Object retValue = null;
try {
retValue = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
logger.debug("------方法执行之后------");
return retValue;
}
}
在这段代码中有以下知识点
1. @Aspect 标注在一个类上 表示一个切面,切面就是若干切入点和通知的集合。注意在applicationContext中已经声明了将此拥有注解的类自动识别为spring中的bean
2. @Pointcut 标注在一个方法上 表示一个切点,切点是连接点的断言。在spring aop中,切点指的就是特定的方法,这个方法由@Pointcut中的表达式来确定,如在本例当中
execution(* cn.hn3l.services..*Service.delete*(..))
指的就是cn.hn3l.services包下的所有以Service结尾的类(包括子包下的类)中以delete开头的任意方法,spring aop就把满足这个条件的所有方法都看作是切入点。切点的名称为方法名。
3. @Around 标注在一个方法上 表示一个环绕通知。注解的参数代表一个切点,其中deleteMethod()表示已经声明的一个切点,而args(long)表示匹配以long作为参数类型的方法的切点,综合两个,与@Around环绕通知相匹配的切点就是cn.hn3l.services包下的所有以Service结尾的类(包括子包下的类)中以delete开头中间只有一个long类型参数的方法。而我们的ArticleService接口中的deleteArticle(long id)刚好能够满足此条件。
4. 被@Around标记的方法的的第一个参数必须为ProceedingJoinPoint类型,而且必须指定。通常情况下我们需要ProceedingJoinPoint的proceed来保证调用连的继续执行,当然我们可以根据情况决定是否调用proceed方法或者多次调用。上面程序的执行结果如下所示
16:52:32,831 DEBUG ArticleAspect:24 - --------方法执行之前---------
16:52:32,831 DEBUG ArticleAspect:28 - 100 ,
-------删除文章业务方法-----
16:52:32,837 DEBUG ArticleAspect:36 - ------方法执行之后------
Process finished with exit code 0
通过环绕通知可以去做很多事,这样才能让我们的应用程序完成良好的解耦。下面再举一个例子,我们对环绕通知的方法体做一下简单的修改,代码如下:
@Around("deleteMethod() && args(long)")
public Object aroundDeleteArticle(ProceedingJoinPoint proceedingJoinPoint){
Object retValue = null;
logger.debug("--------方法执行之前---------");
//打印方法所有的参数列表
printArgsArray(proceedingJoinPoint);
Object[] args = proceedingJoinPoint.getArgs();
if (((Long)args[0] < 0 || (Long)args[0] > 0xFFFFFFFF)){
retValue = false;
}else {
try {
retValue = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
logger.debug("------方法执行之后------");
return retValue;
}
依照这段代码的逻辑,如果外部传来的id参数小于0或者大于int的最大取值范围,那么就判定该id不合法,不再执行删除文章的业务方法。
下面是测试代码:
package cn.hn3l.services.interfaces;
import cn.hn3l.test.SuperTest;
import org.junit.Test;
/**
* @Author Wang Weiwei
* @Since 16-10-13
* @Describe
*/
public class ArticleServiceTest extends SuperTest {
@Test
public void testDeleteArticleService(){
ArticleService articleService =
(ArticleService) applicationContext.getBean("articleService");
// articleService.deleteArticle(100);
articleService.deleteArticle(0xFFFFFFFF + 0x15F);
}
}
运行结果是
18:10:45,078 DEBUG ArticleAspect:26 - --------方法执行之前---------
18:10:45,079 DEBUG ArticleAspect:39 - ------方法执行之后------
Process finished with exit code 0
根据这个运行结果明显可以看出我们的业务逻辑确实没有执行。由此推算,我们就可以利用环绕通知来进行更多的功能,如安全验证,数据存储等。