面向方面编程:
在oop中模块化的单元是类,而在aop中,模块化单元是方面(aspect)。可以将一个“方面”想象在独立的实体中实现在软件系统中一个切面的常见的功能。其主要目的是分离这些横切关注点而增加模块化,同时也符合DRY(Don't Repeat Yourself,不要重复你自己)原则,以避免代码重复。
关键术语及简要的定义:
接合点(Join-point):实际代码中的点,在这些点上执行方面,从而面向应用程序中添加额外的逻辑。
通知(Adivce): 在特定接合点由“方面”所执行的行为(或者代码块)。
切入点(Point-cut):用来选择执行一个或者多个连接点的表达式。可以将一个切入点想象成一组接合点。
目标(Target):执行流被“方面”更改为对象。所以意味着该对象是实际的业务逻辑。
编制(Weaving):将方面与目标对象结合在一起的过程,可以在三个不同的阶段完成该过程:编译时,加载时,运行时:
编译时编制是最简单的方法。编译器通过应用程序的源代码,并创建编制类。
加载时编制指当需要加载时由特定的类加载器编制所需加载的类的过程。
相对于编译时和加载时编制过程,运行时编制过程是一种更加动态的方法。springAop通过Proxy模式使用该方法。8.1/
8.1 在spring中开始使用aop
Spring 提供了一个子项目Spring AOP,该项目通过应用Proxy模式,为在目标对象(SpringBean)上定义方法接合点提供一个纯java解决方案。可以将代理对象(Proxy object)想象成实际对象的包装,所以在发起实际的方法调用之前,之后或者过程中引入相关的特性。
如图客户端调用一个代理类。而该类将实际的工作委托给另一个类完成。
在底层,Spring AOP 使用了AspectJ,她是最流行的AOP框架之一,目前已经是业界的标准。AspectJ通过使用AspectJ注解并借助于编制整合机制,提供看一种定义“方面”的简单的方法。尽管存在Spring Aop的动态、运行时编制,AspectJ还是提供了在目标对象上的静态、编译时、加载时编制。但大多数流行的Spring AOP框架都会像Spring AOP那样在运行时创建代理类。
借助于Spring AOP,可以使用JDK动态代理机制或者CGLIB代理机制来创建代理类。如果一个Spring Bean实现了一个接口,那么该接口的所有的实现都将被JDK所代理。而如果Bean没有实现任何接口,CGLIB将应用于具体的类的对象。此外,还可以通过配置而一直使用CGLIB代理机制。
example01
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
//版本号要一致否则可能产生依赖冲突
application.xml
<context:component-scan base-package="com.tzg.aop.springaop03"/>
<context:annotation-config/>
<!-- 实例化对象-->
<bean id="executionTimeLogSpringAop"
class="com.tzg.aop.springaop.ExecutionTimeLoggingSpringAop" />
<aop:config>
表示拦截公共类的方法
切入点
<aop:pointcut id="executionTimeLoggingPointcut"
expression="execution(public * *(..))"
/>
将通知与给定的切入点相匹配
<aop:advisor id="executionTimeLoggingAdvisor"
advice-ref="executionTimeLogSpringAop"
pointcut-ref="executionTimeLoggingPointcut"
/>
</aop:config>
实体类
package com.tzg.aop.springaop;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public void sayHello(){
System.out.println("hello ...!");
}
}
package com.tzg.aop.springaop;
import org.springframework.stereotype.Component;
@Component
public class MyOtherBean {
public void sayHelloDelayed() throws InterruptedException{
Thread.sleep(1000);
System.out.println("hello..!");
}
}
Aop类
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
public class ExecutionTimeLoggingSpringAop implements MethodBeforeAdvice,
AfterReturningAdvice {
long startTime = 0;
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
startTime = System.nanoTime();
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {
long elapsedTime = System.nanoTime()-startTime;
String className = target.getClass().getCanonicalName();
String methodName = method.getName();
System.out.println("Execution of"+className+"#"+methodName+"ended in"+(new BigDecimal(elapsedTime).divide(new BigDecimal(1000000))+"milliseconds"));
}
}
Main
package com.tzg.aop.springaop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) throws InterruptedException {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("/application.xml",Main.class);
MyBean myBean=applicationContext.getBean(MyBean.class);
myBean.sayHello();
MyOtherBean myOtherBean=applicationContext.getBean(MyOtherBean.class);
myOtherBean.sayHelloDelayed();
}
}
8.2 熟悉通知的类型
8.2.1 Before
通知在实际方法调用之前调用,为此方面应该实现 MethodBeforeAdivce 接口
public interface MethodBeforeAdvice extends BeforeAdvice{
void before (Method method ,Object[] args , Object target) throws Thowable;
}
8.2.2 After Returning
通知在实际方法调用之前调用,为此方面应该实现 AfterReturnAdivce 接口
public interface AfterReturningAdvice extends BeforeAdvice{
void afterReturning(Method method ,Object[] args , Object target) throws Thowable;
}
8.2.3 After Throwing
当抛出一个异常且该异常在被调用的方法捕获之前。
8.2.4 After(Finally)
不管结合点是否执行,都会执行通知After(Finally),结合点的执行可能会正常的返回或抛出一个异常,但不管发生什么该通知代码都会执行。
public class ExecutionTimeLoggingWithAfterAdvice{
public void executiontimeLogging(JoinPoint jp)throws Throwable{
String className = jp.getTarget().getClass().getCanonicalName();
String methodName = jp.getSignature().getName();
}
}
注意:在<aop:config>中使用aspect标签,并在其中定义了point-cut 和after标签。注意,<aop:after>的method特性与通知中定义的方法名称相匹配。
pplication.xml
<context:component-scan base-package="com.tzg.aop.springaop03"/>
<context:annotation-config/>
<!-- 实例化对象-->
<bean id="executionTimeLogWithAfterAdivce"
class="com.tzg.aop.springaop.ExecutionTimeLoggingWithAfterAdivce" />
<aop:config>
表示拦截公共类的方法
切入点
<aop:aspect ref="executionTimeLogWithAfterAdivce" >
<aop:pointcut id="executionTimeLoggingPointcut"
expression="execution(public * *(..))" />
<aop:after pointcut-ref="executionTimeLoggingPointcut" method="executiontimeLogging" />
<aop:aspect>
</aop:config>
8.2.5 Around
public class ExecutionTimeLoggingWithAroundAdvice(){
public void executiontiontimeLogging(ProcessdingJoinPoint jp) throws
Throwable{
long startTime = System.nanoTime;
String className = jp.getTarget().getClass().getCanonicalName();
String methodName = jp.getSignature().getName();
//***
jp.proceed();
}
}
ProcessdingJoinPoint扩展了JoinPoint接口,并作为参数传递到通知Around。通过结合点,可以使用getTarget()方法访问目标对象,使用getSignature()方法访问方法签名,以及使用getArgs()方法访问方法的参数。
<context:component-scan base-package="com.tzg.aop.springaop03"/>
<context:annotation-config/>
<!-- 实例化对象-->
<bean id="executionTimeLogWithAroundAdivce"
class="com.tzg.aop.springaop.ExecutionTimeLoggingWithAroundAdivce" />
<aop:config>
表示拦截公共类的方法
切入点
<aop:aspect ref="executionTimeLogWithAroundrAdivce" >
<aop:pointcut id="executionTimeLoggingPointcut"
expression="execution(public * *(..))" />
<aop:around pointcut-ref="executionTimeLoggingPointcut" method="executiontimeLogging" />
<aop:aspect>
</aop:config>
注意:只有Around通知支持ProcessdingJoinPoint ,而针对其他的通知则不能使用,包括After(Finally)通知。
8.3 定义切入点指示符
8.3.1 类型签名表达式,
为了根据类型(比如接口,类名或者包名)过滤方法,springAop提供了within关键字类型签名,模式如下
Within(<type name>)
接下来列举一些类型签名用法的实例。
Within(com.tzg..) :该通知将匹配com.tzg包及子包所有类的所有方法。
Within(com.tzg.spring.MyService)该通知将匹配Myservice类中的所有方法。
Within(MyServiceInterface+):该通知将匹配所有实现MyServiceInterface的类的所有方法。
Within(com.tzg.spring.MyBaseService+):该通知将匹配MyBaseService类以及其所有的子类的方法
8.3.2 方法签名表达式
execution(<scope> <return-type><fully-qualified-class-name>.*(parameters))
此时,对于给定的作用域返回类型,完全限定类名以及参数相匹配的方法,都会应用指定的通知,方法的作用域可以是公共的保护的私有的。例如
execution(*com.tzg.pring.MyBean.*(..));该通知将匹配MyBean中的所有的方法。
execution(public *com.tzg.spring.MyBean.*(..)):该通知将匹配MyBean中的所有的公共的方法。
execution(public String com.tzg.spring.MyBean.*(..)):该通知将匹配MyBean中返回String的所有的公共方法。
execution(public * com.tzg.spring.MyBean.*(long , ..))该通知将匹配Mybean中第一个参数是long的所有的公共方法。
8.3.3 替他替代的切入点指示符
该部分将列举Spring AOP 所支持的指示符。AOP仅支持在其他AOP项目中可用的指示符的一个子集。
bean(*Service):根据名称并使用关键字bean进行过滤。该切入点表达式将与名称中带有后缀Service的Bean相匹配。
@annotation(com.tzg.spring.MarkerMethodAnnotation): 根据所应用的注解对方法进行过滤。该切入点的表达式表明了使用了MarkerMethodAnnotation注解的方法将被通知。
@within(com.tzg.spring.MarkerAnnotation):当带有关键字within的切入点与一个包,类或者接口相匹配是,可以根据类所使用的注解限制对类的过滤。此时使用MarkMethodAnnotation注解的类将被@within关键字通知。
This(com.tzg.spring.MarkerInterface):该切入点表达式将对任何实现了MarkerInterface接口的代理对象的方法进行过滤。