在软件系统中,存在一些功能需要用到应用程序的多个地方,但是我们又不想在每个点都去调用它,比如日志,事务管理,安全等。如果我们让应用对象只关注于自己所针对的业务领域问题,而上述这些功能由其他应用对象自行处理。为解决上述问题,Spring提出了面向切面的编程思想。在给出AOP的具体实现之前,先给出AOP相关的一些具体概念。
1、基本概念
(1)横切关注点
在软件开发过程中,散布于应用中多处的功能。面向切面编程的目的就是解决把横切关注点和业务逻辑相分离。
切面:把横切关注点模块化成为一些特殊的类。切面告诉我们是什么,在何时何处完成 其功能。
(2)通知
在AOP中,切面的工作称之为通知。通知主要定义了切面是什么,以及何时使用。
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用,此时不会关心方法的输出是什么;
- 返回通知(After-returning):在目标方法执行成功后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
(3)切点
切点定义了通知在何处执行。切点和通知结合构成了一个完整的切面。
2、AOP具体实现
除了Spring的基础jar外,还需要额外导入aspectjweaver.jar,aopalliance.jar,aspectjrt.jar
(1)在XML中声明切面
定义一个接口IHelloWorldService :
package com.wygu.service;
public interface IHelloWorldService {
public void sayBefore();
public void sayAfter();
public boolean sayAfterReturning();
public void sayAfterThrow();
public void sayAround();
}
编写接口的实现HelloWorldService :
package com.wygu.impl;
import com.wygu.service.IHelloWorldService;
public class HelloWorldService implements IHelloWorldService{
@Override
public void sayBefore() {
System.out.println("Before Hello World!");
}
@Override
public void sayAfter() {
System.out.println("After Hello World!");
}
@Override
public boolean sayAfterReturning() {
System.out.println("After Returning Hello World!");
return true;
}
@Override
public void sayAfterThrow() {
System.out.println("After Throw Hello World!");
throw new RuntimeException("后置抛出异常");
}
@Override
public void sayAround() {
System.out.println("Around Hello World!");
}
}
定义切面HelloWorldAspect:
package com.wygu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class HelloWorldAspect {
public void beforeAdvice(){
System.out.println("*******************前置通知");
}
public void afterAdvice(){
System.out.println("*******************后置通知");
}
public void afterReturnAdvice(Object retVal){
System.out.println("*******************返回通知"+":"+retVal);
}
public void afterThrowingAdvice(Exception exception){
System.out.println("*******************异常通知");
exception.printStackTrace();
}
public void aroundAdvice(ProceedingJoinPoint pjp){
try {
System.out.println("******************环绕之前通知");
//执行环绕方法
pjp.proceed();
System.out.println("******************环绕之后通知");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("******************执行环绕方法失败");
}
}
}
定义配置文件spring-aop.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 定义Bean实例 -->
<bean id="helloWorldService" class="com.wygu.impl.HelloWorldService"/>
<!-- -定义切面Bean实例-->
<bean id="helloAspect" class="com.wygu.aop.HelloWorldAspect"/>
<aop:config>
<!-- -声明切面 -->
<aop:aspect id="ascept" ref="helloAspect">
<!-- -声明切点 -->
<aop:pointcut id="pointcut1" expression="execution(* com.wygu.impl.*.sayBefore(..))" />
<aop:pointcut id="pointcut2" expression="execution(* com.wygu.impl.*.sayAfter(..))" />
<aop:pointcut id="pointcut3" expression="execution(* com.wygu.impl.*.sayAfterReturning(..))" />
<aop:pointcut id="pointcut4" expression="execution(* com.wygu.impl.*.sayAfterThrow(..))" />
<aop:pointcut id="pointcut5" expression="execution(* com.wygu.impl.*.sayAround(..))" />
<!-- -前置通知 -->
<aop:before method="beforeAdvice" pointcut-ref="pointcut1"/>
<!-- -后置通知 -->
<aop:after method="afterAdvice" pointcut-ref="pointcut2" />
<!-- -后置返回通知 -->
<aop:after-returning method="afterReturnAdvice" pointcut-ref="pointcut3"
arg-names="retVal" returning="retVal" />
<!-- -后置异常通知 -->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut4"
arg-names="exception" throwing="exception" />
<!-- -环绕通知 -->
<aop:around method="aroundAdvice" pointcut-ref="pointcut5"/>
</aop:aspect>
</aop:config>
</beans>
程序测试:
package com.wygu.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.wygu.service.IHelloWorldService;
public class TestAOP {
public static void main(String[] args) throws InterruptedException {
@SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
IHelloWorldService helloWorldService = (IHelloWorldService) context.getBean("helloWorldService");
helloWorldService.sayBefore();
helloWorldService.sayAfter();
helloWorldService.sayAfterReturning();
try {
helloWorldService.sayAfterThrow();
} catch (Exception e) {
Thread.sleep(1000);
}
helloWorldService.sayAround();
}
}
最终执行结果为:
八月 28, 2017 8:23:51 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@55801443: startup date [Mon Aug 28 20:23:51 GMT+08:00 2017]; root of context hierarchy
八月 28, 2017 8:23:51 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-aop.xml]
*******************前置通知
Before Hello World!
After Hello World!
*******************后置通知
After Returning Hello World!
*******************返回通知:true
After Throw Hello World!
*******************异常通知
java.lang.RuntimeException: 后置抛出异常
at com.wygu.impl.HelloWorldService.sayAfterThrow(HelloWorldService.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy2.sayAfterThrow(Unknown Source)
at com.wygu.main.TestAOP.main(TestAOP.java:18)
******************环绕之前通知
Around Hello World!
******************环绕之后通知
(2)使用注解创建切面
我们利用XML方式实现了AOP的五种通知类型,然而在实际开发中,使用比较多的还是利用注解的方式实现注入,下面利用注解实现切面的五种通知类型。
接口IHelloWorldService和接口的实现类HelloWorldService同上一个实例。
修改切面HelloWorldAspect
package com.wygu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class HelloWorldAspect {
@Before("execution(* com.wygu.impl.*.sayBefore(..))")
public void beforeAdvice(){
System.out.println("*******************前置通知");
}
@After("execution(* com.wygu.impl.*.sayAfter(..))")
public void afterAdvice(){
System.out.println("*******************后置通知");
}
@AfterReturning(value="execution(* com.wygu.impl.*.sayAfterReturning(..))", returning="retVal")
public void afterReturnAdvice(Object retVal){
System.out.println("*******************返回通知"+":"+retVal);
}
@AfterThrowing(value="execution(* com.wygu.impl.*.sayAfterThrow(..))",throwing="exception" )
public void afterThrowingAdvice(Exception exception){
System.out.println("*******************异常通知");
exception.printStackTrace();
}
@Around("execution(* com.wygu.impl.*.sayAround(..))")
public void aroundAdvice(ProceedingJoinPoint pjp){
try {
System.out.println("******************环绕之前通知");
//执行环绕方法
pjp.proceed();
System.out.println("******************环绕之后通知");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("******************执行环绕方法失败");
}
}
}
定义spring-aop.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 启用AspectJ自动代理 -->
<aop:aspectj-autoproxy />
<!-- 定义Bean实例 -->
<bean id="helloWorldService" class="com.wygu.impl.HelloWorldService"/>
<!-- -定义切面Bean实例-->
<bean id="helloAspect" class="com.wygu.aop.HelloWorldAspect"/>
</beans>
在xml中声明aspect的代理对象,使得在初始化spring 容器的时候,spring会自动对切点生成代理对象。如下:
<!-- 启用AspectJ自动代理 -->
<aop:aspectj-autoproxy />
运行结果为:
*******************前置通知
Before Hello World!
After Hello World!
*******************后置通知
After Returning Hello World!
*******************返回通知:true
After Throw Hello World!
*******************异常通知
java.lang.RuntimeException: 后置抛出异常
at com.wygu.impl.HelloWorldService.sayAfterThrow(HelloWorldService.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy10.sayAfterThrow(Unknown Source)
at com.wygu.main.TestAOP.main(TestAOP.java:19)
******************环绕之前通知
Around Hello World!
******************环绕之后通知