聊一聊AOP (面向切面编程)
AOP(Aspect Oriented Programming),采取的是横向抽取的机制,与OOP面向对象不同,取代了继承和委托,而是将分布于应用多处的功能(横切关注点)与业务逻辑相分离。
通俗点说,就是不必要再将一些通用的功能代码如日志,安全,事务管理这些与程序的主要代码放在一起,实现横切关注点与它们所影响的对象之间的解耦。
先介绍一些AOP术语
1、通知(Advice):定义了那些你在程序里作为通用功能代码(就是切面)的主要工作是做什么的,还有什么时候执行这个工作
一般按照执行时机来分:
Before:在业务逻辑方法调用之前调用通知
After:在业务逻辑方法调用之后调用通知(无论方法执行是否成功)
After-returning:方法执行成功后
After-throwing:方法抛出异常后
Around:包裹被通知方法,也就是说这一个通知可以实现上面四个通知的功能。
2、连接点(JoinPoint):应用过程中能够插入切面的一个点
3、切点(Pointcut):在何处调用通知,就是说,在哪个业务逻辑处。
4、切面(Aspect):通知和切点的结合
5、引入(Introduction):在不修改现有类的情况下,允许我们向现有的类添加新方法或属性
6、织入(Weaving):将切面应用到目标对象来创建新的代理对象
分为编译期—–比如AspectJ
类加载期—–用的较少
运行期—–SpringAop
下面将经典AOP与引入简单声明式AOP和基于注解的AOP对比起来说
先看下实例代码
Performer接口
package com.lz.springinaction.springidol;
public interface Performer {
void perform();
}
实现了Performer接口的Singer类
package com.lz.springinaction.springidol;
public class Singer implements Performer {
//业务逻辑核心功能
public void perform() {
System.out.println("singing....");
}
}
实现了Perform接口的Dancer类
package com.lz.springinaction.springidol;
public class Dancer implements Performer {
//业务逻辑核心功能
public void perform() {
System.out.println("dancing。。。");
}
}
Audience类(这就是一个应用程序里的横切关注点,也就是会在多个地方都会用到的通用代码,这里模块化为一个类,即所谓的切面)
package com.lz.springinaction.springidol;
public class Audience {
//每一位表演者表演之前
public void takeSeats(){
System.out.println("观众正在找座位");
}
//每一位表演者表演之前
public void turnOffCellPhones(){
System.out.println("观众将手机关机或调至静音");
}
///每一位表演者表演成功之后
public void applaud(){
System.out.println("观众强烈鼓掌");
}
//每一位表演者表演失败之后
public void demandRefund(){
System.out.println("退钱");
}
}
配置文件的相关内容
<bean id="dancer" class="com.lz.springinaction.springidol.Dancer"></bean>
<bean id="singer" class="com.lz.springinaction.springidol.Singer"></bean>
<bean id="audience" class="com.lz.springinaction.springidol.Audience"></bean>
<aop:config>
<!-- 声明切面,ref引用的作为切面的类 ,这里是Audience-->
<aop:aspect ref="audience">
<!-- 定义切点 ,属性expression即在何处发生-->
<aop:pointcut
expression="execution(* com.lz.springinaction.springidol.Performer.perform(..) )"
id="performance" />
<!-- 多个通知 ,method引用的是切面类里面的方法-->
<aop:before method="takeSeats" pointcut-ref="performance"/>
<aop:before method="turnOffCellPhones" pointcut-ref="performance"/>
<aop:after-returning method="applaud" pointcut-ref="performance"/>
<aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
测试类及结果
package com.lz.springinaction.springidol;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Performer aa=(Performer) context.getBean("dancer");
Performer bb=(Performer) context.getBean("singer");
aa.perform();
bb.perform();
}
}
测试结果,在一个Singer唱歌和一个Dancer跳舞前后都会有相同的行为发生。
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
观众正在找座位
观众将手机关机或调至静音
dancing。。。
观众强烈鼓掌
观众正在找座位
观众将手机关机或调至静音
singing....
观众强烈鼓掌
下面我们来看看经典SpringAop的过程,相同的例子
Singer,Dancer都一样
2.x的AOP的不同通知类型,是通过不同的接口来实现,有MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice,MethodInterceptor
这里的Audience(当然也可以将这里面的每个方法单独拿出来作为一个Advice来实现上述几个接口,比如写个TakeSeats类,TurnOffCellPhone类等,但这样单独写,在配置文件中需要每个分别配置,比较麻烦)
package com.lz.spring;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
public class Audience implements MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice {
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("观众正在找座位");
System.out.println("观众将手机关机或调至静音");
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("观众们在鼓掌");
}
}
配置文件
<!-- 目标对象 -->
<bean id="singer" class="com.lz.spring.Singer"></bean>
<bean id="dancer" class="com.lz.spring.Dancer"></bean>
<!-- 定义通知 -->
<bean id="audience" class="com.lz.spring.Audience"></bean>
<!-- 定义Advisor -->
<bean id="audienceAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 指定切入方法 -->
<property name="mappedName" value="perform"></property>
<!-- 指定Advice -->
<property name="advice" ref="audience"/>
</bean>
<!-- 定义AOP代理 -->
<bean id="SingerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="singer"></property>
<!-- 注入通知 -->
<property name="interceptorNames">
<list>
<value>audienceAdvisor</value>
</list>
</property>
</bean>
<bean id="DancerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="dancer"></property>
<!-- 注入通知 -->
<property name="interceptorNames">
<list>
<value>audienceAdvisor</value>
</list>
</property>
</bean>
</beans>
2.x的AOP,将目标对象和Advice或者Advisor封装到一个AOP代理中,这样客户端使用这个AOP代理对象,就与原来的目标对象没有任何区别
测试类:
package com.lz.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Performer pp1 = (Performer) context.getBean("SingerProxy");
Performer pp2 = (Performer) context.getBean("DancerProxy");
pp1.perform();
pp2.perform();
}
}
Performer pp1 = (Performer) context.getBean(“SingerProxy”);
Performer pp2 = (Performer) context.getBean(“DancerProxy”);
这两个类型是目标对象实现的接口Performer,不能返回类型Singer或者Dancer,而是目标对象实现的接口
相比较,Spring AOP3.x要比2.x简单方便,2.x这里要分别对Singer和Dancer分别进行AOP代理