spring AOP(1)底层实现的一些细节知识

spring AOP(1)底层实现的一些细节知识

2456发表于2016-11-15

spring里面有个概念叫aop(面向切面编程),很好很强大又很让人费解,很多开发人员会用并且天天挂在嘴边但是不理解其核心原理,今天周末有空,我想用一个小系列的文章给大家把aop分析清楚。要理解aop,首先要掌握Java中的代理模式。

 

在日常生活中,会遇到各种各样的中介机构,比如猎头公司,律师事务所,婚姻介绍所,房产公司等。在这些单位工作的人员均可称为代理人。代理人的共同特征是可以代替委托人去和第三方通信。譬如:律师代替委托人打官司,猎头代替委托人物色人才,红娘代替委托人寻找对象,房产代理人代替委托人出租房屋。

 

代理人可以在第三方和委托人之间转发或过滤消息,但是不能取代委托人的任务。譬如你要找女朋友,委托你一要好的哥们去帮你物色,结果被他给物色了。这就不叫代理。

 

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

 

代理模式一般涉及到的角色有:

 

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

我们来看个例子:

 

package com.wepull.aop.staticProxy;
/**
 * @author leno
 *抽象角色
 */
public interface IHello {
  String greeting(String who);
}
 
package com.wepull.aop.staticProxy;
/**
 * @author leno
 *真实角色
 */
public class HelloImpl implements IHello {
 public String greeting(String who) {
              System.out.println("greeting method is invoked.....");
              return "hello,"+who;
       }
 
}



试想一下,如果这时候我们要对问候的业务逻辑方法进行日志记录。我们当然可以这样做:


package com.wepull.aop.staticProxy;
/**
 * @author leno
 *真实角色
 */
public class HelloImpl implements IHello {
private Logger logger = Logger.getLogger(this.getClass().getName());
 
 public void log(String message)
 {
  logger.log(Level.INFO, message);
 }
 
 public String greeting(String who) {
      log( "starting...");
       System.out.println("greeting method is invoked.....");
      log("stopping...");
       return "hello,"+who;
 
 
 }

一、静态代理


可问题来了,项目经理发话,不容许修改现存的类的实现。怎么办?这时候我们就要就想到代理模式了。我们再加一个代理类:

静态代理类:


package com.wepull.aop.staticProxy;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HelloLoggerProxy implements IHello {
 private Logger logger = Logger.getLogger(this.getClass().getName());
 private IHello helloImpl;
 
 public HelloLoggerProxy(IHello helloImpl) {
  super();
  this.helloImpl = helloImpl;
 }
 
 public String greeting(String who) {
  log( "starting...");
  String hello = helloImpl.greeting(who);
  log("stopping...");
  return hello;
 }
 
 public void log(String message)
 {
  logger.log(Level.INFO, message);
 }
}


客户端测试代码:


package com.wepull.aop.staticProxy;
 
public class TestStaticProxy {
 
       public static void main(String[] args) {
              IHello hello = new HelloLoggerProxy(new HelloImpl());
              hello.greeting("leno");
       }
}


 

 由以上代码可以看出,客户实际需要调用的是HelloImpl类的greeting()方法,现在用HelloLoggerProxy来代理HelloImpl类,同样达到目的,同时还在方法调用的前后都做了日志记录。这就是静态代理。

      

但是,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实色必须对应角一个代理角色,如果大量使用会导致类的急剧膨胀;而且,如果项目中要做日志的类有100个,每个类里面有100个方法。重复代码就太可怕了。有人说,不怕,我就一愚公。好,有天项目经理突然要求修改做日志的方式,你再看看这些方法,估计撞墙的心都有了。那怎么办?我们就改用动态代理。

二、动态代理

JDK1.3以后提供了动态代理的支持,程序员通过实现java.lang.reflect.InvocationHandler接口提供一个执行处理器,然后通过java.lang.reflect.Proxy得到一个代理对象,通过这个代理对象来执行商业方法,在商业方法被调用的同时,执行处理器会被自动调用。 

动态代理类:


package com.wepull.aop.dynamicProxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class DynamicLoggerProxy implements InvocationHandler {
       private Logger logger = Logger.getLogger(this.getClass().getName());
       private Object delegate;
 
       @SuppressWarnings("unchecked")
       public  Object bind(Object delegate)
       {
              this.delegate = delegate;
              Class cls = delegate.getClass();
              return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), this);
       }
 
 
       public Object invoke(Object o, Method method, Object[] args)
                     throws Throwable {
              log( "starting...");
              Object obj = method.invoke(delegate, args);
              log("stopping...");
              return null;
       }
 
       public void log(String message)
       {
              logger.log(Level.INFO, message);
       }
}


客户端测试代码:


package com.wepull.aop.dynamicProxy;
 
public class TestDynamicProxy {
 
       public static void main(String[] args) {
              IHello hello = (IHello) new DynamicLoggerProxy().bind(new HelloImpl());
              hello.greeting("leno");
       }
}


大家看到,一个动态代理类就代替了无数个静态代理类。一劳永逸。这里涉及到一些java反射的知识,我会在以后单独讲述。

java的动态代理非常实用和强大,可它有一个前提,需要我们的处理业务逻辑的类必须至少实现一个接口,虽然面向接口编程是

需要提倡的,但如果我们有些现存的类就是没有实现接口,那如何代理呢?

这就需要CGLIB的帮助了。继续改进我们的代码:

没有实现任何接口的类:


package com.wepull.aop.cglibProxy;
 
public class Hello{
      
       public void greeting(String who) {
              System.out.println("hello,"+who);
       }
 
}


CGLIB代理类:


package com.wepull.aop.cglibProxy;
 
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
public class CGLIBLoggerProxy implements MethodInterceptor {
 
       private final Logger logger = Logger.getLogger(this.getClass().getName());
      
       public Object bind(Object delegate)
       {
              Enhancer enhancer = new Enhancer();
              enhancer.setCallback(this);
              enhancer.setSuperclass(delegate.getClass());
              return enhancer.create();
       }
       public Object intercept(Object o, Method method, Object[] args,
                     MethodProxy proxy) throws Throwable {
              log( "starting...");
              proxy.invokeSuper(o, args);
              log("stopping...");
              return null;
       }
      
       public void log(String message)
       {
              logger.log(Level.INFO,message);
       }
 
}


客户端测试代码:


package com.wepull.aop.cglibProxy;
 
public class TestCGLIBProxy {
 
       public static void main(String[] args) {
              Hello hello = (Hello) new CGLIBLoggerProxy().bind(new Hello());
              hello.greeting("leno");
       }
}


好了,有了动态代理和CGLIB,那我们就可以给任何实际的类提供代理了。

从而可以在我们的代理类中加入一些与业务逻辑无关的系统级的功能服务,如:日志记录,事务处理,安全管理等。而且不会影响到我们现有的系统。

看起来似乎很完美了。但是且慢,如果我们的应用中有些需要这些系统级的功能服务,而有些又不需要呢?

如果把代码写死了显然达不到我们的要求。怎么办?

这时候就要用到一个非常重要的思想:用配置代替编码。

小编蓝狐