AOP(Aspect Oriented Programming)面向方面编程,关注软件系统中的横切关注点,通过分离这些横切关注点而增加模块化。
相关术语:
Spring AOP仅使用了运行时编织,是一种很动态的方法,通过运行时创建代理类实现。尽管它底层使用的AspectJ,但并没有保留后者的编译时编织和加载时编织。
本章项目需要依赖如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.springframework.samplesgroupId>
<artifactId>executionTimeLoggingAspectJartifactId>
<version>0.0.1-SNAPSHOTversion>
<properties>
<java.version>1.6java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<spring-framework.version>4.3.10.RELEASEspring-framework.version>
<hibernate.version>5.2.10.Finalhibernate.version>
<logback.version>1.2.3logback.version>
<slf4j.version>1.7.25slf4j.version>
<junit.version>4.12junit.version>
<aspectj.version>1.8.10aspectj.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
<scope>compilescope>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>${logback.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring-framework.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
dependency>
dependencies>
project>
public interface MyBean {
void sayHello();
}
@Component
public class MyBeanImpl implements MyBean {
@Override
public void sayHello() {
System.out.println("Hello..!");
}
}
public interface MyOtherBean {
void sayHelloDelayed() throws InterruptedException;
}
@Component
public class MyOtherBeanImpl implements MyOtherBean {
@Override
public void sayHelloDelayed() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Hello..!");
}
}
如图创建了两个类,一个类可以打印一行消息,另一个类则延时打印消息,接下来创建executionTimeLoggingSpringAOP通知Bean来计算每个方法执行用时。
public class ExecutionTimeLoggingSpringAOP implements MethodBeforeAdvice, AfterReturningAdvice {
long startTime = 0;
@Override
public void before(Method method, Object[] args, Object target) 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");
}
}
其中before和afterReturning两个方法分别在接合点方法执行前和返回值后立即执行。
然后创建上下文配置文件定义通知和接合点:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.wiley.beginningspring.ch8" />
<context:annotation-config />
<bean id="executionTimeLoggingSpringAop" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingSpringAOP" />
<aop:config>
<aop:pointcut id="executionTimeLoggingPointcut"
expression="execution(public * *(..))" />
<aop:advisor id="executionTimeLoggingAdvisor"
advice-ref="executionTimeLoggingSpringAop"
pointcut-ref="executionTimeLoggingPointcut" />
aop:config>
beans>
其中
最后就可以在Main方法中测试函数了。
public class Main {
public static void main(String... args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.sayHello();
MyOtherBean myOtherBean = context.getBean(MyOtherBean.class);
myOtherBean.sayHelloDelayed();
}
}
运行得到结果:
可以看到两个函数的运行时间被分别打印了出来。
在实际方法调用前被调用。该方面应该实现MethodBeforeAdvice接口。
在实际方法执行并返回值后调用。应该实现AfterReturningAdvice接口。
在方法抛出异常并被调用方法捕获之前执行,实现ThrowsAdvice接口。
不管接合点返回值或者抛出异常都会执行的通知。
在接合点之前和之后执行。可以替换Before+AfterReturning:
public class ExecutionTimeLoggingWithAroundAdvice {
public void executiontimeLogging(ProceedingJoinPoint jp) throws Throwable {
long startTime = System.nanoTime();
String className = jp.getTarget().getClass().getCanonicalName();
String methodName = jp.getSignature().getName();
jp.proceed();
long elapsedTime = System.nanoTime() - startTime;
System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds");
}
}
在xml中配置如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.wiley.beginningspring.ch8" />
<context:annotation-config />
<bean id="executionTimeLoggingWithAroundAdvice" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingWithAroundAdvice" />
<aop:config>
<aop:aspect ref="executionTimeLoggingWithAroundAdvice">
<aop:pointcut id="logPointCut" expression="execution(public * *(..))" />
<aop:around pointcut-ref="logPointCut" method="executiontimeLogging" />
aop:aspect>
aop:config>
beans>
Within(
匹配某个类型/包/类中的所有方法。
execution(
产生实际方法调用之前的通知方法。可以访问之际方法的传入参数。
@Component
@Aspect
@Order(100)
public class ExecutionOrderBefore {
@Before(value = "execution(public * *(..))")
public void before(JoinPoint joinPoint) {
System.out.println("===1. Before Advice.");
}
}
value属性定义了切点方法的信息,而@Order注解表明了执行优先级。越小越先执行。
除了在value属性中定义以外,还可以额外定义切点:
@Component
@Aspect
@Order(110)
public class ExecutionOrderBeforeWithPointCut {
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {
}
@Before("anyPublicMethod()")
public void beforeWithPointCut(JoinPoint joinPoint) {
System.out.println("===1.1. Before Advice with @PointCut.");
}
}
此时@PointCut注解的方法名可直接使用为切点名,其代指的方法在属性中写出。该注解的属性中采用其他切点指示符以及使用Boolean表达式创建更大切点:
@Component
@Aspect
@Order(110)
public class ExecutionOrderAfterWithMultiplePointCut {
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {
}
@Pointcut("@annotation(com.wiley.beginningspring.ch8.MarkerAnnotation)")
public void annotatedWithMarkerAnnotation() {
}
@After(value = "anyPublicMethod() & annotatedWithMarkerAnnotation()")
public void afterWithMultiplePointcut(JoinPoint joinPoint) {
System.out.println("===5.1. After Advice with Multiple Pointcut applied on method.");
}
}
其中MarkerAnnotation定义如下:
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MarkerAnnotation {
}
产生实际方法调用之后的通知方法。
@Component
@Aspect
@Order(150)
public class ExecutionOrderAfter {
@After(value = "execution(public * *(..))")
public void after(JoinPoint joinPoint) {
System.out.println("===5. After Advice.");
}
}
定义接合点执行完毕且成功返回值后执行的通知。可以访问返回值。
@Component
@Aspect
@Order(100)
public class ExecutionOrderAfterReturning {
@AfterReturning(value = "execution(public * *(..))")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("===6. After Returning Advice.");
}
}
定义在异常抛出后调用方法捕捉之前执行的通知方法。
在Bean的类级别上使用该注解将其声明为一个方面。此外还应该随之使用@Component及其派生注解类。
定义在目标方法执行之前和之后启动的通知方法。
@Component
@Aspect
@Order(200)
public class ExecutionOrderAround {
@Around("execution(public * *(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("===2. Before proceeding part of the Around advice.");
jp.proceed();
System.out.println("===4. After proceeding part of the Around advice.");
}
}
用于动态继承接口。
最后查看注解配置类和Main类:
@Configuration
@ComponentScan(basePackages = {"com.wiley.beginningspring.ch8"})
@EnableAspectJAutoProxy
public class ApplicationConfig {
}
public class Main {
public static void main(String... args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.sayHello();
}
}
几个Bean和他们的接口定义不变。采用AspectJ通知Bean:
@Component
@Aspect
public class ExecutionTimeLoggingAspectJ {
@Around("execution(public * *(..))")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.nanoTime();
String className = pjp.getTarget().getClass().getCanonicalName();
String methodName = pjp.getSignature().getName();
Object output = pjp.proceed();
long elapsedTime = System.nanoTime() - startTime;
System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds");
return output;
}
}
上下文配置文件如下,采用方面的自动代理:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.wiley.beginningspring.ch8" />
<context:annotation-config/>
<aop:aspectj-autoproxy/>
beans>
创建Main类来测试程序:
public class Main {
public static void main(String... args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.sayHello();
MyOtherBean myOtherBean = context.getBean(MyOtherBean.class);
myOtherBean.sayHelloDelayed();
}
}
结果还是运行时间。