看过之前的蜕变系列文章,相信你对mybatis有了应用方面的认识。但是这些要完成你的蜕变还不够,考虑到大家的基础知识,我们继续回到spring的话题上来,我们一起聊一聊AOP。
AOP是Aspect OrientedProgramming的缩写,中文译名,面向切面编程。我们曾经为了解决字符编码问题,编写了Filter对所有请求的字符编码进行了统一的字符处理。我们配置了Filter之后,请求要先经过Filter之后,才会交给servlet进行处理。Filter中实现的代码,直接强行插入到了Servlet的代码之前。相对于Servlet来说,Filter像一把砍刀一样,把原本正常执行的请求流程横切了,只不过,这个切入点,是固定在了在执行Servlet代码之前。这种切面是静态的。AOP其实是一种编程思想:在不修改源代码的情况下,将实现了某方面功能的代码切入到原有程序的指定位置的一种思想。
当然,我这样讲可能太抽象了,一般情况下,很多其它的教材一般是AOP的从目的出发的:在实际开发中,我们需要添加一些和业务无关的代码,比如打日志,提交数据库事务等等。这些操作和业务无关,代码写法又比较固定,我们可以抽取出来,放到一个统一的地方去处理…诚然,是可以将和业务无关的代码抽取出来,实现非业务功能和业务逻辑的分离,让大家更加专注于业务逻辑,但是不要忘了,AOP可不仅仅能干这个噢。至于能干点儿啥新鲜的咱们后面再说。
下面这张图很形象的说明了AOP
提到AOP,那么就不得不搞懂几个概念。
1) Aspect :切面,既然是面向切面编程,没有切面还面向啥呢?什么是切面,切面是一个方面,代表需要解决的一个方面的问题,比如日志打印、比如事务管理、比如异常处理等等。
2)Join point :连接点,也就是横向切入的位置,程序执行的某个位置;
3) Pointcut :切点,符合切点规则的连接点集合(一个切点包含一个或多个连接点),也就代码真正需要被切入执行的地方;
4) Advice :增强,也就是一段代码,面向切面不是要解决某个方面的问题吗?解决问题是需要编写代码的,Advice就是代码的地方,也就是某个连接点,需要执行的具体操作(在Spring中分为: Before advice , After returning advice , After throwing advice ,After (finally) advice , Around advice );
5)Introduction:引介,一种特殊的增强方式,给一个类增加属性和方法,让原有的类具备增加新的功能和特性。
6)Weaving:织入,织入是指将增强设置到连接点的具体过程。
7)aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。
上面说了一大堆,我们看点简单的原理性质的东西,——如何才能在不修改源代码的基础上实现新的功能呢?我们带着例子一步一步来看,我们有一个HelloService还有一个实现类HelloServiceImpl:
代码语言:javascript复制package com.pz.study.frame.aop.service;
public interface HelloService {
public void sayHello();
}
代码语言:javascript复制package com.pz.study.frame.aop.service.impl;
import com.pz.study.frame.aop.service.HelloService;
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("sayHello");
}
}
还有一个测试用例
代码语言:javascript复制package com.pz.study.frame.aop.test;
import org.junit.Test;
import com.pz.study.frame.aop.service.HelloService;
import com.pz.study.frame.aop.service.impl.HelloServiceImpl;
public class TestDemo {
@Test
public voidtestSayHello(){
HelloService helloService = new HelloServiceImpl();
helloService.sayHello();
}
}
我们知道,HelloServiceImpl的sayHello方法会输出”sayHello”。我们有没有什么办法在不修改HelloServiceImpl代码的情况下,让sayHello的方法做一点别的事情呢?别想了,直接看例子:
编写代码:
代码语言:javascript复制package com.pz.study.frame.aop.invocation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class HelloInvocationHandler implements InvocationHandler {
private Object target;
public HelloInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method,Object[] args)
throws Throwable {
method.invoke(target, args);
System.out.println("hello world");
return null;
}
}
编写测试用例
代码语言:javascript复制@Test
public void testSayHello2(){
HelloInvocationHandler handler = new HelloInvocationHandler( new HelloServiceImpl() );
HelloService helloService =(HelloService)Proxy.newProxyInstance(HelloService.class.getClassLoader(), newClass<?>[]{HelloService.class},handler);
helloService.sayHello();
}
运行测试用例控制台输出:
sayHello
hello world
这是什么鬼?没有修改HelloServiceImpl的情况下,调用了helloService.sayHello()方法,结果多输出了一行hello world!
这就是JDK的动态代理,动态代理?那什么是动态代理?动态代理是一种机制:在代码的运行时期,使用Proxy给某个接口创建一个代理对象。接口将方法委托给代理对象执行,JDK提供的动态代理是针对接口的,只能做接口增强的事情,而不能给某个类做增强的事情。要实现动态代理,需要以下步骤:
1.定义一个类实现java.lang.relect.invocation接口重写invoke方法,实现调用接口的具体逻辑。
这里要特别说一下,method.invoke方法会调用目标对象的方法(接口实现类),这个方法要不要调用根据具体的需求来看,不是一定要调用的。把一件自己要做的事情交给别人来完成,那个别人就是你的代理人,怎么做是代理人的事 情,结果怎么样也是他的事情。
2.使用Proxy.newProxyInstance来创建代理对象需要3个参数,ClassLoader,接口数组,需要处理接口方法调用的InvocationHandler对象。
3.将Proxy.newProxyInstance返回的参数强制转换为接口类型。
已经忘记猿思考系列5——一文明白java和微商那点儿事儿,的同学,好好看看,懂了这些再看下一章节spring 的AOP的用法,喝水一样简单。