学习心路:
是什么??能干什么??怎么做???最高境界:为什么做??
AOP的介绍
AOP(Aspect-Oriented Programming,面向切面编程[面向方面编程]),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。
也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。
例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。
这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP就是来解决这个问题的。
他能够把那些重复的代码给抽取出来,并且把它们应用到任何需要它的地方。
比如我这里有一个统计代码运行效率的需求:
代码很好实现:
public class Dosomething{
public void showDo(){
System.out.println("记时开始!!!");
do();
System.out.println("记时结束!!!");
}
}
如果一个方法要统计还好,如果有很多方法需要统计呢??
我们以前的办法就是把统计的代码copy放到其它要统计的里面去。
现在有了AOP思想(注意只是思想),我们只需要把它抽离出来。这个抽离出来的代码在AOP里面叫做 Advice(增强) ,而需要我们这些代码统计运行效率的方法叫做 Pointcut(切入点)
我们通过AOP思想的实现方式 把代码写入到 需要统计效率的方法(Pointcut),写入的过程就叫做Aspect(切面)
增强点:
- 前置通知:在方法之前执行
- 后置通知:在方法之后执行
- 异常通知:方法出现异常
- 最终通知:在后置之后执行
- 环绕通知:在方法之前和之后执行
AOP思想比较官方的说法:
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。
横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。
Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
AOP思想的实现技术:
实现AOP的技术,主要分为两大类:
1.采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
2.采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的
主要方式用三种:
1.静态代理
下面是动态代理的两种方式:
2.JDK 动态代理
3.CGLib 动态代理
OOP回顾
OOP(Object Oriented Programming)。OOP主要是为了实现编程的重用性、灵活性和扩展性。它的几个特征分别是继承、封装、多态和抽象。OOP重点体现在编程架构,强调的是类之间的层次关系。
OOP缺陷
为了更好的说明OOP的概念,我们接下来讲一个OOP的实例,重点分析OOP存在哪些缺陷,以便更好的理解AOP的相关内容。
上面这张图有三个类:Dog,Cat和Duck,他们都有一个方法run。按照OOP的设计理念,我们很容易就会想到抽象出一个Animal父类,同时让这三个子类继承Animal父类。这样的设计可以用如下的图示表示:
需求:动物园动物的表演(这里的Animal是动物园的动物),当驯兽师说跑后,狗才跑,跑完后驯兽师给狗食物。
用OOP思想很好写:
public interface Animal {
void run(String animalType);
}
public class DogImpl implements Animal {
@Override
public void run(String animalType) {
System.out.println("驯兽师命令狗跑!!");
System.out.println("Dog run!!!");
System.out.println("驯兽师给奖励狗吃东西!!");
}
}
如果现在有另外一个物种,比如猫,要表演,驯兽师让猫跑,跑后驯兽师奖励猫吃东西。是不是还要这样写呢??很明显这些代码是存在冗余的。从OOP角度是不能够完美解决这个问题的。
于是这里就得靠AOP了。
静态代理:
这是最简单方便的一个AOP实现。我们为动物园Animal写一个代理类。我们把Animal的实现类里面不必要的逻辑抽取出来,放到代理类里面。
动物就只专心跑:
public class DogImpl implements Animal {
@Override
public void run(String animalType) {
System.out.println("Dog run!!!");
}
}
代理类:
public class AnimalProxy implements Animal {
private Animal animal;
public AnimalProxy(Animal animal) {
this.animal=animal;
}
@Override
public void run(String animalType) {
System.out.println("驯兽师命令狗跑!!");
animal.run(animalType);
System.out.println("驯兽师给奖励狗吃东西!!");
}
}
测试代码:
public class Client {
public static void main(String[] args) {
Animal animalProxy=new AnimalProxy(new DogImpl());
animalProxy.run("狗狗");
}
}
代码间很明显体现着,抽取不必要的通用逻辑(增强Advice),把通用逻辑放到了指定的切入点(Pointcut),形成了切面(Aspect)。代码完美耦合!!不冗余!!
这样就能够让猫,熊等动物到动物园里面好好玩耍吃东西了。代理类一般在我们写小东西的时候又不想麻烦可以用一用。
但是我们现在给动物吃的食物吃完了:
1.驯兽师发现给动物奖励的食物快吃完了
2.工作人员补充食物
3.驯兽师很高兴食物又有了,能够好好让动物表演了。
上面这个代码逻辑又该怎么实现呢??
这里引入了一个工作人员,我们用静态代理很容易实现,定义一个动物园Human接口,给动物园Human创建一个代理类。等等。但是如果又出现了其它的问题,我们又会定义无穷无尽的代理类。
那么有什么办法只定义一个代理类就能够解决上述的问题呢??
那就是用动态代理了:
JDK 动态代理
这里就简单介绍些JDK动态代理加上动态执行方法,这里的动态执行方法就写AOP里面的前置方法和后置方法。
动态代理只能代理有接口的子类。
定义一个接口:
public interface Animal {
void run(String animalType);
}
抽象出我们动物园里面的一个狗狗:
public class DogImpl implements Animal {
@Override
public void run(String animalType) {
System.out.println("Dog run!!!");
}
}
定义一个动态任务的接口:
public interface Mession {
public Object before(Object ...args);
public Object after(Object ...args);
}
准备工作已经做完了,下面就通过动态代理的方式来实现AOP核心思想:
我们可以知道上面定义的接口就是我们通过AOP思想抽离出来的逻辑。叫做Advice(增强)
而我们要增强的方法叫做Pointcut(切入点)
我们要做的就是把增强放入切入点。这就是动态代理要做的!!
定义一个类实现动态代理的接口(InvocationHandler):
public class MyInvocation implements InvocationHandler {
private Object target;//目标增强类
private Mession mession;//增强类
public MyInvocation(Object target,Mession mession) {
//初始化
this.target=target;
this.mession=mession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
mession.before();//执行增强方法
Object result=method.invoke(target, args);//执行目标类的核心逻辑
mession.after();//执行增强方法
return result;
}
}
测试代码:
public class Client {
public static void main(String[] args) {
Class[] cs={Animal.class};
Object objectProxy=Proxy.newProxyInstance(Animal.class.getClassLoader(), cs, new MyInvocation(new DogImpl(),new Mession() {
public Object before(Object ...arg){
System.out.println("前置方法"+arg.length);
return null;
}
public Object after(Object ...arg){
System.out.println("后置方法"+arg.length);
return null;
}
}));
((Animal)objectProxy).run("狗狗");
}
}
不管你是否理解,按照模板这样来就能够创建一个代理类,实现一个代理类代理所有通用逻辑和商务逻辑的分离。
现在我们就不用给每一种类型的对象写一个代理类了,实现了静态代理的简化。
下面介绍一下升级版:
都是笔者个人的想法:
把增强的接口多定义几个方法:
public interface Mession {
public Object before(Object ...args);
public Object after(Object ...args);
public Object afterReturning(Object ...args);
public Object catchError(Object ...args);
}
动态代理类这样写:
public class MyInvocation implements InvocationHandler {
private Object target;
private Mession mession;
public MyInvocation(Object target,Mession mession) {
this.target=target;
this.mession=mession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
try {
mession.before();
result=method.invoke(target, args);
mession.after();
} catch (Exception e) {
mession.catchError();
}finally {
mession.afterReturning();
}
return result;
}
}
测试类:
public class Client {
public static void main(String[] args) {
Class[] cs={Animal.class};
Object objectProxy=Proxy.newProxyInstance(Animal.class.getClassLoader(), cs, new MyInvocation(new DogImpl(),new Mession() {
public Object before(Object ...arg){
System.out.println("前置方法"+arg.length);
return null;
}
public Object after(Object ...arg){
System.out.println("后置方法"+arg.length);
return null;
}
@Override
public Object afterReturning(Object... args) {
System.out.println("最终方法"+args.length);
return null;
}
@Override
public Object catchError(Object... args) {
System.out.println("异常捕获方法"+args.length);
return null;
}
}));
((Animal)objectProxy).run("狗狗");
}
}
/*
输出:
前置方法0
Dog run!!!
后置方法0
最终方法0
*/
这样就感觉非常Nice!!但是还是没有Spring的实现的完美,但是如果这样实现,在我们做些小东西的情况下也是非常轻量级和绿色的。
缺点:JDK动态代理只能增强那些拥有接口的实现类。
这里必须要介绍的是在动态代理类实现过程中涉及的参数
Proxy类的newInstance()方法有三个参数:
1.ClassLoader loader:它是类加载器类型,
2.Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs={Animal.class};
3.InvocationHandler:它是最重要的一个参数!它是一个接口!它的名字叫调用处理器!你想一想,上面例子中objectProxy对象是Animal接口的实现类对象,那么它一定是可以调用run方法了。难道你不想调用一下run方法么,它会执行些什么东东呢?其实无论你调用代理对象的什么方法,它都是在调用InvocationHandler的invoke()方法!,除了调用getClass()方法不会调用invoke()方法。
InvocationHandler的invoke()方法的参数有三个:
1.Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;
2.Method method:表示当前被调用方法的反射对象,例如((Animal)target).run(),那么method就是run()方法的反射对象;
3.Object[] args:表示当前被调用方法的参数,当然((Animal)target).run()这个调用是没有参数的,所以args是一个零长数组。
我们常常会出现的异常
一般我们要注意注意注意一点:
proxy这个代理对象参数我们不能这样用:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
try {
((Animal)proxy).run("Proxy..........");
mession.before();
result=method.invoke(target, args);
mession.after();
} catch (Exception e) {
mession.catchError();
}finally {
mession.afterReturning();
}
return result;
}
这样用会报内存溢出。因为这是一个无限的递归过程,因为我们前面说过,当我们通过代理对象调用一次Animal接口中的方法的时候就会来调用这里的invoke(…)方法,调用了又调用,这就形成了无限的递归过程。
所以我们一般不会用proxy这个代理对象!!!
CGlib动态代理
实现CGlib动态代理的第一步是导包:
CGlib能够用在Java普通项目中等其它地方。
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
在上面我们对动物园的动物进行了JDK的动态代理,下面我们用CGlib进行替代:
比如这里有一条小狗。
public class DogImpl implements Animal {
@Override
public void run(String animalType) {
System.out.println("Dog run!!!");
}
}
该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。
intercept()方法拦截所有目标类方法的调用,target表示目标类的实例(不是单纯的父类实例),method为目标类方法的反射对象,args为方法的动态入参,methodProxy为代理类实例。
proxy.invokeSuper(target, args)通过代理类调用父类中的方法。
代理类对象这样写:
public class MyCglibProxy implements MethodInterceptor{
private Enhancer enhancer=new Enhancer();
private Mession mession;
public Object getProxy(Class clazz,Mession mession){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
this.mession=mession;
return enhancer.create();
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
try {
mession.before();
methodProxy.invokeSuper(target, args);
mession.after();
} catch (Exception e) {
mession.catchError();
}finally {
mession.afterReturning();
}
return null;
}
}
测试代码:
public class Client {
public static void main(String[] args) {
MyCglibProxy myCglibProxy=new MyCglibProxy();
DogImpl dogImplProxy=(DogImpl)myCglibProxy.getProxy(DogImpl.class,new Mession() {
public Object before(Object ...arg){
System.out.println("前置方法"+arg.length);
return null;
}
public Object after(Object ...arg){
System.out.println("后置方法"+arg.length);
return null;
}
@Override
public Object afterReturning(Object... args) {
System.out.println("最终方法"+args.length);
return null;
}
@Override
public Object catchError(Object... args) {
System.out.println("异常捕获方法"+args.length);
return null;
}
});
dogImplProxy.run("小狗");
}
}
/*
输出:
前置方法0
Dog run!!!
后置方法0
最终方法0
*/
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。