安卓开发Java

【JAVA】浅谈AOP

2018-05-08  本文已影响136人  Y了个J

AOP (面向切面编程)Aspect Oriented Programming的缩写

优点:

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP相关概念

1.方面(Aspect)
2.连接点(Joinpoint)
3.通知(Advice): 各种类型的通知包括“around”、“before”和“throws”通知。
许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
4.切入点(Pointcut)
5.引入(Introduction): 添加方法或字段到被通知的类。
6.目标对象(Target Object): 被通知或被代理对象。
7.AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。
8.织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

Spring AOP代理对象的生成

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

静态代理很简单,咱们自己在写代码的时候都会写到这种类似静态代理的代码。简单来说,就是把被代理类作为参数传给代理类的构造方法,让代理类替被代理类实现更强大的功能。

public class StaticProxyTest { 
      public static void main(String[] args) {
            UserService userService = new UserService();
            LogProxy logProxy = new LogProxy(userService);
            logProxy.addUser();
            logProxy.deleteUser();
      }
}

interface IUserService{
      void addUser();
      void deleteUser();
}

class UserService implements IUserService{
      @Override
      public void addUser() {
            System.out.println("添加用户");
        }
        @Override    
        public void deleteUser() {
            System.out.println("删除用户");
        }
}

//日志代理
class LogProxy implements IUserService{    
    //目标类    
    private UserService target;

    public LogProxy(UserService target){
          this.target = target;
      }
  
      @Override    
      public void addUser() {
          System.out.println("记录日志开始");
          target.addUser();
          System.out.println("记录日志结束");
      }
  
      @Override
      public void deleteUser() {
          System.out.println("记录日志开始");
          target.deleteUser();
          System.out.println("记录日志结束");
      }
}

动态代理在Spring源码中,用到的动态代理主要有两种,JDK动态代理以及CGLib动态代理。

两者主要区别是:
JDK动态代理一般针对实现了接口的类生成代理。
目标对象没有实现接口,则默认会采用CGLIB代理。如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库)其实,上面的区别阐述虽然不够完全,但足以区分二者。

相同点:两种动态代理本质上都是字节码组装

JDK动态代理的代理类一般需要实现接口

/** 
 * Description: jdk动态代理 
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        IPersonService personService = JdkDynamicProxy.getProxy();
        personService.addPerson();
        personService.deletePerson();
    }
}

interface IPersonService{
    void addPerson();
    void deletePerson();
}

class PersonService implements IPersonService{
    @Override   
    public void addPerson() {
        System.out.println("添加人物");
    }
    @Override
    public void deletePerson() {
        System.out.println("删除人物");
    }
}

/**
 * newProxyInstance方法参数说明:
 * ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
 * Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
*  InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
 */
class JdkDynamicProxy{
    public static IPersonService getProxy(){

        IPersonService personService = new PersonService();

        IPersonService proxy = (IPersonService) Proxy.newProxyInstance(IPersonService.class.getClassLoader(), new Class<?>[]{IPersonService.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               System.out.println("记录日志开始");
               Object obj = method.invoke(personService, args);
               System.out.println("记录日志结束");
              return obj;
          }
      });
       return proxy;
    }
}

CGLib动态代理

public class CglibProxyTest {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        Train t = (Train)proxy.getProxy(Train.class);
        t.move();
    }
}

class Train {
    public void move(){
        System.out.println("火车行驶中...");
    }
}

class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz){
        //设置创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * 拦截所有目标类方法的调用
     * obj  目标类的实例
     * m   目标方法的反射对象
     * args  方法的参数
     * proxy代理类的实例
     */
    @Override
    public Object intercept(Object obj, Method m, Object[] args,  MethodProxy proxy) throws Throwable {
        System.out.println("日志开始...");
        //代理类调用父类的方法
        proxy.invokeSuper(obj, args);
        System.out.println("日志结束...");
        return null;
    }
}

原理分析

下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,假设我们要对下面这个用户管理进行代理:

//用户管理接口  
package com.tgb.proxy;  

public interface UserMgr {  
    void addUser();  
    void delUser();  
}  

//用户管理的实现  
package com.tgb.proxy;  

public class UserMgrImpl implements UserMgr {  

    @Override  
    public void addUser() {  
        System.out.println("添加用户.....");  
    }  

    @Override  
    public void delUser() {  
        System.out.println("删除用户.....");  
    }  
}  

按照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和delUser方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。 我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等。

JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:
InvocationHandler接口:

package com.tgb.proxy;  

import java.lang.reflect.Method;  

public interface InvocationHandler {  
    public void invoke(Object o, Method m);  
} 

实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:

package com.tgb.proxy;  

import java.io.File;  
import java.io.FileWriter;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Method;  
import java.net.URL;  
import java.net.URLClassLoader;  
import javax.tools.JavaCompiler;  
import javax.tools.StandardJavaFileManager;  
import javax.tools.ToolProvider;  
import javax.tools.JavaCompiler.CompilationTask;  

public class Proxy {  
    /** 
     *  
     * @param infce 被代理类的接口 
     * @param h 代理类 
     * @return 
     * @throws Exception 
     */  
    public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {   
        String methodStr = "";  
        String rt = "\r\n";  

        //利用反射得到infce的所有方法,并重新组装  
        Method[] methods = infce.getMethods();    
        for(Method m : methods) {  
            methodStr += "    @Override" + rt +   
                         "    public  "+m.getReturnType()+" " + m.getName() + "() {" + rt +  
                         "        try {" + rt +  
                         "        Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +  
                         "        h.invoke(this, md);" + rt +  
                         "        }catch(Exception e) {e.printStackTrace();}" + rt +                          
                         "    }" + rt ;  
        }  

        //生成Java源文件  
        String srcCode =   
            "package com.tgb.proxy;" +  rt +  
            "import java.lang.reflect.Method;" + rt +  
            "public class $Proxy1 implements " + infce.getName() + "{" + rt +  
            "    public $Proxy1(InvocationHandler h) {" + rt +  
            "        this.h = h;" + rt +  
            "    }" + rt +            
            "    com.tgb.proxy.InvocationHandler h;" + rt +                           
                methodStr + rt +  
            "}";  

        String fileName =  "d:/src/com/tgb/proxy/$Proxy1.java";  
        File f = new File(fileName);  
        FileWriter fw = new FileWriter(f);  
        fw.write(srcCode);  
        fw.flush();  
        fw.close();  

        //将Java文件编译成class文件  
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);  
        Iterable units = fileMgr.getJavaFileObjects(fileName);  
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);  
        t.call();  
        fileMgr.close();  

        //加载到内存,并实例化  
        URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};  
        URLClassLoader ul = new URLClassLoader(urls);  
        Class c = ul.loadClass("com.tgb.proxy.$Proxy1");  

        Constructor ctr = c.getConstructor(InvocationHandler.class);  
        Object m = ctr.newInstance(h);  

        return m;  
    }  
} 

这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成Proxy1.java文件,然后将其编译成Proxy1.class。这样我们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。

然后,在客户端我们就可以随意的进行代理了。

package com.tgb.proxy;  

public class Client {  
    public static void main(String[] args) throws Exception {  
        UserMgr mgr = new UserMgrImpl();  

        //为用户管理添加事务处理  
        InvocationHandler h = new TransactionHandler(mgr);  
        UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);  

        //为用户管理添加显示方法执行时间的功能  
        TimeHandler h2 = new TimeHandler(u);  
        u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);  

        u.addUser();  
        System.out.println("\r\n==========华丽的分割线==========\r\n");  
        u.delUser();  
    }  
}  

事务处理:

package com.tgb.proxy;  

import java.lang.reflect.Method;  

public class TransactionHandler implements InvocationHandler {  

    private Object target;  

    public TransactionHandler(Object target) {  
        super();  
        this.target = target;  
    }  

    @Override  
    public void invoke(Object o, Method m) {  
        System.out.println("开启事务.....");  
        try {  
            m.invoke(target);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("提交事务.....");  
    }  
}  

运行结果:

开始时间:2014年-07月-15日 15时:48分:54秒  
开启事务.....  
添加用户.....  
提交事务.....  
结束时间:2014年-07月-15日 15时:48分:57秒  
耗时:3秒  

==========华丽的分割线==========  

开始时间:2014年-07月-15日 15时:48分:57秒  
开启事务.....  
删除用户.....  
提交事务.....  
结束时间:2014年-07月-15日 15时:49分:00秒  
耗时:3秒  

项目中使用AOP遇到的坑

1.在Spring配置文件中配置了事务管理器,如下:

<!--事务管理器配置,单数据源事务-->
<tx:annotation-driven transaction-manager="transactionManager" />  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
</bean>

2.配置了事务管理器后,加入了@Transactional注解

@Service
@Transactional
public class AccountService{
    //to do something
}

上面配置默认启用JDK动态代理,JDK只能代理接口不能代理类。而我的项目中用的是这个配置,却因为没有定义Service接口导致项目启动报错。

如果需要使用CGLIB动态代理:配置如下:

<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> 
上一篇下一篇

猜你喜欢

热点阅读