首先。AOP 面向切面编程。
就是说通过配置将业务逻辑和系统的服务分离。目的是让业务逻辑之关系业务的处理而不再去处理其他事情。其中切面一般都是哪些可以为多个类提供服务的模块,将其封装起来称为切面。减少系统的重复代码和低模块之间的耦合度。一般用于权限验证、日志、事务等。
拦截器也是应用了AOP的思想,将拦截(请求)Action以进行一些预处理或者结果处理。Spring的AOP可以拦截Spring管理的bean。
AOP主要是通过动态代理和反射机制实现的。
Spring代理实际上是对JDK代理和CGLIB代理做了一层封装,并且引入了AOP概念:Aspect、advice、joinpoint等等,同时引入了AspectJ中的一些注解@pointCut,@after,@before等等.Spring Aop严格的来说都是动态代理,所以实际上Spring代理和Aspectj的关系并不大.
基础概念
- 通知(Advice)
定义了切入点代码执行时间点附近需要做的工作。
切入点:
@Before org.apringframework.aop.MethodBeforeAdvice
@After-returning(返回后) org.springframework.aop.AfterReturningAdvice
@After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
@Arround周围 org.aopaliance.intercept.MethodInterceptor
@Introduction引入 org.springframework.aop.IntroductionInterceptor
2. 连接点(Joinpoint)
程序能够应用通知的一个时机。这些时机就是连接点。比如方法调用时、异常抛出时、方法返回后等。
3. 切入点(Pointcut)
通知定义了切面要发生的故事,连接点定义了故事要发生的时机,而切入点定义了故事发生的地点,例如某个类or方法的名称。Spring中允许我们用正则表达式来指定。
- 切面(Aspect)
Advice Joinpoint Pointcut共同组成了切面:要发生的故事、时间、地点 - 引入(Introduction)
引入允许我们向现有的类添加新的方法和属性。
Spring中的方法注入的功能。 - 目标(Target)
就是被通知的对象。
如果没有AOP,通知的逻辑就要写在目标对象中。有了AOP之后他可以只关注自己要做的事情,解耦。 - 代理(Proxy)
应用通知的对象。 - 织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程。
织入一般发生在以下几个时机:
一 编译时:AspectJ的织入编译器
二 类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码。
三 运行时 :切面在运行的某一个时刻被织入,SpringAOP就是运行时织入切面的。(JDK的动态代理技术)
使用AOP的几种方式:
- 经典的基于代理的AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面
举一个例子:
Me类是实现了Sleepable的接口,重写了其中的sleep方法。
基于代理的AOP
- 由于Me完成了sleep的逻辑,但是其他睡觉需要的功能 ,例如起床穿衣服,睡前脱衣服都可以由AOP代替“Me”执行。实现解耦。
那么我们就可以通过SleepHelper类
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("睡觉前要脱衣服!");
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
- Spring配置文件 application.xml配置AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<span style="white-space:pre"> </span>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<span style="white-space:pre"> </span>xmlns:aop="http://www.springframework.org/schema/aop"
<span style="white-space:pre"> </span>xsi:schemaLocation="http://www.springframework.org/schema/beans
<span style="white-space:pre"> </span>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
<span style="white-space:pre"> </span>http://www.springframework.org/schema/aop
<span style="white-space:pre"> </span>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义切入点位置 -->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*sleep"></property>
</bean>
<!-- 使切入点与通知相关联,完成切面配置 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper"></property>
<property name="pointcut" ref="sleepPointcut"></property>
</bean>
<!-- 设置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的对象,有睡觉能力 -->
<property name="target" ref="me"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<!-- 代理接口,睡觉接口 -->
<property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
</bean>
</beans>
其中配置标签中的几个重要属性
xmlns:
是默认的xml文档解析格式,即spring的beans。地址是http://www.springframework.org/schema/beans;通过设置这个属性,所有在beans里面声明的属性,可以直接通过<>来使用,比如<bean>等等。一个XML文件,只能声明一个默认的语义解析的规范。例如上面的xml中就只有beans一个是默认的,其他的都需要通过特定的标签来使用,比如aop,它自己有很多的属性,如果要使用,前面就必须加上aop:xxx才可以。类似的,如果默认的xmlns配置的是aop相关的语义解析规范,那么在xml中就可以直接写config这种标签了。
xmlns:xsi:
是xml需要遵守的规范,通过URL可以看到,是w3的统一规范,后面通过xsi:schemaLocation来定位所有的解析文件。
xmlns:aop:
这个是重点,是我们这里需要使用到的一些语义规范,与面向切面AOP相关。
xmlns:tx:
Spring中与事务相关的配置内容。
- 测试:通过AOP代理的方式执行Me的sleep()方法,会把执行前、执行后的操作执行,实现了AOP的效果!
public class Test {
public static void main(String[] args){
@SuppressWarnings("resource")
//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
//ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
Sleepable me = (Sleepable)appCtx.getBean("proxy");
me.sleep();
}
}
然后我们其实可以通org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator简化配置。
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
在Test中就可以直接获取me对象,执行sleep方法。自动匹配,切面会自动匹配符合切入点的bean,会被自动代理,实现功能。
AspectJ提供的注解实现AOP
AspactJ框架常用注解:
@PonitCut // 声明切入点的注解
@Before // 声明前置通知注解
@After // 声明后置通知注解(原始方法执行正常或者非正常执行都会进入)
@AfterReturing //声明后置通知注解(原始方法必须正常执行)
@AfterThrowing // 声明异常通知
@Around // 环绕通知注解
- 修改SleepHelper类
@Aspect
public class SleepHelper{
public SleepHelper(){
}
@Pointcut("execution(* *.sleep())")
public void sleeppoint(){}
@Before("sleeppoint()")
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
@AfterReturning("sleeppoint()")
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
- 在方法中,可以加上JoinPoint参数以进行相关操作,如:
1. //当抛出异常时被调用
public void doThrowing(JoinPoint point, Throwable ex)
{
System.out.println(“doThrowing::method "
+ point.getTarget().getClass().getName() + “.”
+ point.getSignature().getName() + " throw exception”);
System.out.println(ex.getMessage());
}
- 然后修改配置为:
<aop:aspectj-autoproxy />
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
- 测试
4. public class Test {
public static void main(String[] args){
@SuppressWarnings(“resource”)
//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
//ApplicationContext appCtx = new ClassPathXmlApplicationContext(“application.xml”);
ApplicationContext appCtx = new FileSystemXmlApplicationContext(“application.xml”);
Sleepable me = (Sleepable)appCtx.getBean(“me”);
me.sleep();
}
}
Pojo切面
使用Spring来定义纯粹的POJO切面
(名字很绕口,其实就是纯粹通过aop:fonfig标签配置,也是一种比较简单的方式)。
- 修改SleepHelper类,所以这种方式的优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置。
public class SleepHelper{
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
- 配置文件
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
</aop:aspect>
</aop:config>
</beans>
- 另一种配置写法
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
<aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
<aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
</aop:aspect>
</aop:config>