官方的说法是面向切面编程是面向对象编程的有益补充,实际上可以理解为面向切面可以做到面向对象做不到但是我们又很需要的事情。
面向切面即aop,aop的优点在于可以让我们在无侵入的情况下在原本的代码和功能上实现新的东西,如果我们把原本代码中我们想要修改/新添的原本代码称作切面的话,那么aop还可以做到多个切面、一次编程。这样可以将新加入的通用模块、功能抽离出来,即保持了原本代码的纯洁性,代码之间的低耦合行,又可以减少开发量。唯一的缺点就是需要付出额外的开销。
当我们进行aop编程的时候,我们往往需要对作为切面的类或方法的参数进行读取和修改,要完成这样的行为,一般有两种方法。
一、继承法。
优点:方便,清晰。
缺点:对原本bean的配置有侵入。
现在我们对TestInterceptor类的doSomething方法做拦截,在触发此方法时修改方法的参数和类的域的值。
原本doSomething方法的输出应为hello,world
拦截后的实际输出为eat,shit
xml配置如下:可以看到原本类和代理的关系是明摆着的,并且侵入修改了原本TestInterceptor的bean的配置。
<bean id="before" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<bean class="com.yeepay.work.utils.interceptor.BeforeMethod"></bean>
</property>
<property name="patterns">
<list>
<value>.*ome.*</value>
</list>
</property>
</bean>
<bean id="aopService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<list>
<value>before</value>
</list>
</property>
<property name="target">
<bean class="com.yeepay.work.utils.interceptor.TestInterceptor">
</bean>
</property>
</bean>
TestInterceptor类如下:
public class TestInterceptor {
public String str = "hello,";
public void doSomething(String args){
System.out.println(str+args);
}
}
BeforeMethod类如下:
注意BeforeMethod需要继承MethodBeforeAdvice基类
public class BeforeMethod implements MethodBeforeAdvice{
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("interceptor the method:" + method.getName());
System.out.println("classname:"+o.getClass().getName());
objects[0] = "shit";
TestInterceptor testInterceptor = (TestInterceptor)o;
testInterceptor.str = "eat,";
}
}
主函数如下:
ApplicationContext ac = new FileSystemXmlApplicationContext("src/main/resources/test.xml");
TestInterceptor testInterceptor = (TestInterceptor)ac.getBean("aopService");
testInterceptor.doSomething("world");
可以看到,继承法编程的思路是显而易见的,以spring管理的代理来代替原本的类,明摆着告诉你你从spring容器中获取到的是个假的代理类。
在before方法中,我们通过其参数可以方便的更改切面的相关属性,做我们想做的事。
二、注释法。
优点:无侵入,完全符合aop的预期。
缺点:比较麻烦。
xml配置如下:可以看到我们保持了原本aopPointCut的bean的纯洁性,对原本配置是无侵入的。通过正则表达式确定切面。
<bean id="beforeMethod" class="com.yeepay.work.utils.interceptor.withoutBean.BeforeMethod"></bean>
<bean id="aopPointCut" class="com.yeepay.work.utils.interceptor.withoutBean.AopPointCutImpl"></bean>
<aop:config>
<aop:aspect id="something" ref="beforeMethod">
<aop:pointcut id="pringSomethingBean" expression="execution(* com.yeepay.work.utils.interceptor.withoutBean.AopPointCut.*(..))" />
<aop:before method="before" pointcut-ref="pringSomethingBean" />
</aop:aspect>
</aop:config>
BeforeMethod类如下:可以看到我们使用了注释,注释的内容应该和配置文件中的保持一致(?),所有我们需要的信息都可以从jp中获取。
public class BeforeMethod {
@Before(value="execution(* com.yeepay.work.utils.interceptor.withoutBean.AopPointCut.*(..))")
public void before(JoinPoint jp) throws Throwable {
System.out.println("doit:"+jp.getArgs()[0]);
AopPointCutImpl impl = (AopPointCutImpl)jp.getTarget();
impl.i = 1;
}
}
AopPointCutImpl类如下:
public class AopPointCutImpl implements AopPointCut {
public int i = 0;
public int printSomething(int args) {
String a = "";
String b = "a";
for(int j = 0;j<100;j++){
a = a+b;
}
return i+args;
}
}
主函数如下:
public static void main(String args[]){
ApplicationContext ac = new FileSystemXmlApplicationContext("src/main/resources/test.xml");
int count = 0;
AopPointCut cut = (AopPointCut)ac.getBean("aopPointCut");
Long a = System.currentTimeMillis();
for(int i = 0;i<5000000;i++){
count = cut.printSomething(count);
}
Long b = System.currentTimeMillis();
System.out.println("-----"+(b-a));
cut = new AopPointCutImpl();
a = System.currentTimeMillis();
for(int i = 0;i<5000000;i++){
count = cut.printSomething(count);
}
b = System.currentTimeMillis();
System.out.println("-----"+(b-a));
}
注释法相对于继承法是不那么显而易见的,而其优点也正在于此,不改动原有逻辑的基础上做同样的事。
aop对性能存在一定消耗,但是每次调用的消耗大概相当于一个二十次的简单循环,而且其消耗是一定的,不会随着切面函数的消耗增大而增大,所以问题不大。