一,什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在学习AOP时,先要了解什么是代理模式,可以参考: 代理模式
二,使用Spring实现AOP
横切关注点:跨越应用程序多个模块的方法或功能。(软件系统,可以看做由一组关注点即业务或功能或方法组成。其中,直接的业务关注点是直切关注点,而为直切关注点服务的,就是横切关注点。)即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。
切面(ASPECT) |
横切关注点被模块化的特殊对象。即,它是一个类。 |
通知(Advice) |
切面必须要完成的工作。即,它是类中的一个方法。 |
目标(Target) |
被通知对象。 |
代理(Proxy) |
向目标对象应用通知之后创建的对象。 |
切入点(PointCut) |
切面通知执行的"地点"的定义。 |
连接点(JointPoint) |
与切入点匹配的执行点。 |
下面示意图:
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
定义通知类,如下:
前置通知BeforeAdvice类:
// 前置通知 public class BeforeAdvice implements MethodBeforeAdvice{ /** * * @方法名: before * @描述: 前置通知调用方法 * @param arg0 方法信息 * @param arg1 参数列表 * @param arg2 被代理的目标对象 * @throws Throwable * @创建人:Zender */ @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("-----------------前置通知-----------------"); } }
后置通知AfterAdvice类:
//后置通知 public class AfterAdvice implements AfterReturningAdvice { /** * * @方法名: afterReturning * @描述:后置通知调用的方法 * @param arg0 返回值 * @param arg1 被调用的方法 * @param arg2 方法参数列表 * @param arg3 被代理对象 * @throws Throwable * @创建人:Zender */ @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("-----------------后置通知-----------------"); } }
环绕通知SurroundAdvice类:
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; //环绕通知 public class SurroundAdvice implements MethodInterceptor { /** * * @方法名: invoke * @描述:环绕通知调用的方法 * @param arg0 方法信息对象 * @return * @throws Throwable * @创建人:Zender */ @Override public Object invoke(MethodInvocation arg0) throws Throwable { //前置横切逻辑 System.out.println("方法:" + arg0.getMethod() + " 被调用在对象:" + arg0.getThis() + "上,参数:" + arg0.getArguments()); //方法调用 Object ret = arg0.proceed(); //后置横切逻辑 System.out.println("返回值:"+ ret); return ret; } }
抽象主题角色(subject):
/** * * @类名称:Subject * @类描述:抽象主题角色(subject) * @创建人:zender */ public interface Subject { public String sailBook(); }
具体主题角色(RealSubject):
/** * * @类名称:RealSubject * @类描述:具体主题角色(RealSubject) * @创建人:zender */ public class RealSubject implements Subject { @Override public String sailBook() { System.out.println("买书:Spring!"); return "Spring"; } }
DynamicProxy类:
import org.springframework.aop.framework.ProxyFactory; import com.zender.aop.advice.AfterAdvice; import com.zender.aop.advice.BeforeAdvice; import com.zender.aop.advice.SurroundAdvice; //获得代理对象 public class DynamicProxy { /** * * @方法名: getProxy * @描述: 获得任意的代理对象 * @param object * @return * @创建人 Zender */ public static Object getProxy(Object object){ //实例化Spring代理工厂 ProxyFactory factory=new ProxyFactory(); //设置被代理的对象 factory.setTarget(object); //添加通知,横切逻辑 factory.addAdvice(new BeforeAdvice()); factory.addAdvice(new AfterAdvice()); factory.addAdvice(new SurroundAdvice()); return factory.getProxy(); } }
测试类:
import com.zender.aop.DynamicProxy; import com.zender.aop.RealSubject; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //从代理工厂中获得代理对象 Subject rs = (Subject) DynamicProxy.getProxy(new RealSubject()); rs.sailBook(); } }
结果:
二,使用IOC配置的方式实现AOP
创建IOC的配置文件beans.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 被代理的目标对象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean> <!-- 后置通知 --> <bean id="afterAdvice" class="com.zender.aop.advice.AfterAdvice"></bean> <!-- 前置通知 --> <bean id="beforeAdvice" class="com.zender.aop.advice.BeforeAdvice"></bean> <!-- 代理对象 interceptorNames 通知数组 target 被代理的目标对象 proxyTargetClass 被代理对象是否为一个类,如果是则使用cglib,否则使用jdk动态代理 --> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyTargetClass" value="true" /> <property name="target" ref="realSubject" /> <property name="interceptorNames"> <list> <value>afterAdvice</value> <value>beforeAdvice</value> </list> </property> </bean> </beans>
测试类:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Subject s = (Subject) ctx.getBean("proxy"); s.sailBook(); } }
运行结果:
三,使用XML配置Spring AOP切面
这个时候需要引用一个新的jar包:aspectjweaver.jar,该包是AspectJ的组成部分。pom.xml内容如下:
<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.0</modelVersion> <groupId>Zender</groupId> <artifactId>SpringAOP</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> </dependencies> </project>
定义通知通知类:
//通知类 public class SubjectAdvices { //前置通知 public void before(JoinPoint jp) { System.out.println("--------------------前置通知--------------------"); System.out.println("方法名:" + jp.getSignature().getName() + ",参数:" + jp.getArgs().length + ",被代理对象:" + jp.getTarget().getClass().getName()); } //后置通知 public void after(JoinPoint jp){ System.out.println("--------------------后置通知--------------------"); } //环绕通知 public Object around(ProceedingJoinPoint pjd) throws Throwable{ System.out.println("--------------------环绕通知开始--------------------"); Object object = pjd.proceed(); System.out.println("--------------------环绕通知结束--------------------"); return object; } //异常后通知 public void afterThrowing(JoinPoint jp,Exception exp) { System.out.println("--------------------异常后通知,发生了异常:" + exp.getMessage() + "--------------------"); } //返回结果后通知 public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("结果是:" + result); } }
配置IOC容器依赖的XML文件IOCBeans.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.3.xsd"> <!--被代理的目标对象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean> <!-- 通知类 --> <bean id="advice" class="com.zender.aop.advice.SubjectAdvices"></bean> <!-- AOP配置 --> <!-- proxy-target-class属性表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib --> <aop:config proxy-target-class="true"> <!-- 切面配置 --> <!--ref表示通知对象的引用 --> <aop:aspect ref="advice"> <!-- 配置切入点(横切逻辑将注入的精确位置) --> <aop:pointcut expression="execution(* com.zender.aop.RealSubject.s*(..))" id="pointcut1"/> <!--声明通知,method指定通知类型(即通知类的对应的方法名),pointcut指定切点,就是该通知应该注入那些方法中 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> <aop:around method="around" pointcut="execution(* com.zender.aop.RealSubject.s*(..))"/> <!-- throwing里面的内容与SubjectAdvices类的afterThrowing的参数名相同 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut1" throwing="exp" /> <!-- returning里面的内容与SubjectAdvices类的afterReturning的参数名相同 --> <aop:after-returning method="afterReturning" pointcut-ref="pointcut1" returning="result" /> </aop:aspect> </aop:config> </beans>
aop:after-throwing需要指定通知中参数的名称throwing="exp",则方法中定义应该是这样:afterThrowing(JoinPoint jp,Exception exp);aop:after-returning同样需要设置returning指定方法参数的名称。
测试代码:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zender.aop.Subject; public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans.xml"); Subject s = (Subject) ctx.getBean("realSubject"); s.sailBook(); } }
运行结果如下:
四,使用注解配置Spring AOP切面
1,基于上一个示例,修改被代理的类RealSubject,为了实现IOC扫描在RealSubject类上注解了@Service并命名bean为realSubject。相当于上一个示例中在xml配置文件中增加了一个bean:
<!--被代理的目标对象 --> <bean id="realSubject" class="com.zender.aop.RealSubject"></bean>
修改后的RealSubject类的代码如下:
/** * * @类名称:RealSubject * @类描述:具体主题角色(RealSubject) * @创建人:zender */ @Service("realSubject") public class RealSubject implements Subject { @Override public String sailBook() { System.out.println("买书:Spring!"); return "Spring"; } }
2,修改通知类SubjectAdvices,如下:
//通知类 @Component @Aspect public class SubjectAdvices { //前置通知 @Before("execution(* com.zender.aop.RealSubject.s*(..))") public void before(JoinPoint jp) { System.out.println("--------------------前置通知--------------------"); System.out.println("方法名:" + jp.getSignature().getName() + ",参数:" + jp.getArgs().length + ",被代理对象:" + jp.getTarget().getClass().getName()); } //后置通知 @After("execution(* com.zender.aop.RealSubject.s*(..))") public void after(JoinPoint jp){ System.out.println("--------------------后置通知--------------------"); } //环绕通知 @Around("execution(* com.zender.aop.RealSubject.s*(..))") public Object around(ProceedingJoinPoint pjd) throws Throwable{ System.out.println("--------------------环绕通知开始--------------------"); Object object = pjd.proceed(); System.out.println("--------------------环绕通知结束--------------------"); return object; } //异常后通知 @AfterThrowing(pointcut="execution(* com.zender.aop.RealSubject.s*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp) { System.out.println("--------------------异常后通知,发生了异常:" + exp.getMessage() + "--------------------"); } //返回结果后通知 @AfterReturning(pointcut="execution(* com.zender.aop.RealSubject.s*(..))",returning="result") public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("结果是:" + result); } }
@Component表示该类的实例会被Spring IOC容器管理;@Aspect表示声明一个切面;@Before表示before为前置通知,通过参数execution声明一个切点
3,新增配置文件IOCBeans2.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:p="http://www.springframework.org/schema/p" 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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.zender.aop" /> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
在配置IOC的基础上增加了aop:aspectj-autoproxy节点,Spring框架会自动为与AspectJ切面配置的Bean创建代理,属性proxy-target-class="true"表示被代理的目标对象是一个类,而非实现了接口的类。
4,测试代码如下:
public class SpringAOPTest { public static void main(String[] args) { //容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans2.xml"); RealSubject s = ctx.getBean("realSubject" , RealSubject.class); s.sailBook(); } }
5,运行结果: