AOP概述及实现原理

栏目: Java · 发布时间: 6年前

内容简介:Spring 为解耦而生,其中AOP(面向切面编程)是很浓重的一笔。AOP(Aspect-Oriented-Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting)。说明:Advice在国内很多的书面资料上都被翻译为“通知”,但这个翻译无法表达其本质,有少量的读物将这个词翻译为“增强”,这个翻译是比较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对

Spring 为解耦而生,其中AOP(面向切面编程)是很浓重的一笔。AOP(Aspect-Oriented-Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting)。

AOP相关概念

  • 连接点(JoinPoint):程序执行到某个特定位置(如:某个方法调用前,调用后,方法抛出异常),一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。
  • 切点(PointCut):如果连接点相当于数据中的记录,那么切点就相当于查询条件,一个切点可以匹配多个连接点。SpringAop的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。
  • 增强(Advice):增强是织入到目标类连接点上的一段程序代码,Spring提供的增强接口都是带方位的,如BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。,很多资料上将增强译为“通知”,这明显是词不达意的。

说明:Advice在国内很多的书面资料上都被翻译为“通知”,但这个翻译无法表达其本质,有少量的读物将这个词翻译为“增强”,这个翻译是比较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有的业务逻辑进行的一种增强,这个增强可以是前置增强,后置增强,返回后增强,抛出异常时增强,和包围型增强。

  • 引介(Introduction):引介是一种特殊的增强,他为类添加一些属性和方法,这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的为该业务类添加接口的实现逻辑,让业务类称为这个接口的实现类
  • 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP由三种织入方式:1 编译期织入:需要特殊的 Java 编译期(例如Aspect的ajc);2 装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强,3 运行时织入:在运行时为目标类生成代理实现增强。Spring采用动态代理的方式事实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。
  • 切面(Aspect):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。

原理

AOP是运用了动态代理。 代理模式 是GoF提出的23种 设计模式 中最为经典的模式之一,代理是对象的结构模式,他给某一个对象提供一个对象代理,并由代理控制对原对象的引用,简单的说,代理对象可以完成比原对象更多的职责,当需要为原对象添加横切关注功能时,就可以使用原对象的代理对象。我们在打开Office系列的Word文档时,如果文档中有插图,当文档刚加载时,文档中的插图都只是一个虚框的占位符,等真正用户翻到某一页要查看该图片时,才会真正加载这张图片,这其实就是代理模式的使用,代替真正的虚框就是一个虚拟代理,Hibernate的load方法也是返回一个虚拟代理对象,等真正用户需要访问对象的属性时,才向数据库发出 SQL 语句。

按照代理创建初期,代理可以分为两种:

  • 静态代理:由 程序员 创建或特定 工具 自动生成源代码,再对其编译,在程序运行前,代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时,运行反射机制动态创建而成 另外还有 CGLib动态代理 ,它是针对类实现的代理。

实现

JDK代理

静态代理

下面用一个找枪手代考的例子演示代理模式的使用

package com.test.test2.interfaces;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:35
 */
public interface Candidate {
    public void answerTheQuestion();
}

复制代码
package com.test.test2;

import com.test.test2.interfaces.Candidate;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:36
 */
public class LazyStudent implements Candidate {
    private String name;

    public LazyStudent(String name) {
        this.name = name;
    }

    @Override
    public void answerTheQuestion() {
        // 懒学生只能写出自己名字不会答题
        System.out.println("姓名" + name);
    }
}

复制代码
package com.test.test2;

import com.test.test2.interfaces.Candidate;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:40
 */
public class Gunman implements Candidate {
    private Candidate target;  //被代理的对象

    public Gunman(Candidate target) {
        this.target = target;
    }

    @Override
    public void answerTheQuestion() {
        // 抢手要写上代考学生的名字
        target.answerTheQuestion();
        // 抢手帮助懒学生答题并交卷
        System.out.println("奋笔疾书书写正确答案");
        System.out.println("交卷");
    }
}

复制代码
@Test
public void testGunman(){
    Candidate c = new Gunman(new LazyStudent("王小二"));
    c.answerTheQuestion();
}
复制代码

观察代理可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生很多的代理,而且,所有代理除了调用方法不一样之外,其他操作都一样,则此时肯定是重复代码,解决这一个问题最好的做法是通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理了。

动态代理

从JDK 1.3 开始,Java提供了动态技术,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理。

java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理的能力。

JDK动态代理中包含一个接口和一个类:

  • InvocationHandler接口
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
复制代码

参数说明:

参数 说明
Object proxy 指被代理的对象
Method method 要调用的方法
Object[] args 方法调用时的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

  • Proxy类:
  • Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态生成实现类,此类提供了如下的操作方法
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        /**************
        *具体内容略去**
        **************/
    }
复制代码

参数说明:

源码中的参数说明

* @param   loader the class loader to define the proxy class
* @param   interfaces the list of interfaces for the proxy class to implement
* @param   h the invocation handler to dispatch method invocations to
复制代码
参数 说明
ClassLoader 类加载器
Class<?>[] interfaces 得到全部的接口
InvocationHandler h 得到InvocationHandler接口的子类实例

Ps:在Proxy中类的 newProxyInstance() 方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器。在Java中主要有三种类加载器:

  • Bootstrap ClassLoader:此口加载器采用C++编写,一般开发中是看不到的。
  • Extendsion ClassLoader:用来进行扩展类的加载器,一般对应的是jre\lib\ext目录中的类;
  • APPClassLoader:(默认)加载classpath制定的类,是最常使用的一种加载器。

BookFacade.java

public interface BookFacade {   
    public void addBook();  
}  
复制代码

BookFacadeImpl.java

import net.battier.dao.BookFacade;  
  
public class BookFacadeImpl implements BookFacade {  
  
    @Override  
    public void addBook() {  
        System.out.println("增加图书方法。。。");  
    }  
}
复制代码

BookFacadeProxy.java

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
/** 
 * JDK动态代理代理类 
 */  
public class BookFacadeProxy implements InvocationHandler {  
    private Object target;  
    /** 
     * 绑定委托对象并返回一个代理类 
     * @param target 
     * @return 
     */  
    public Object bind(Object target) {  
        this.target = target;  
        //取得代理对象  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this);   //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)  
    }  
  
    @Override  
    /** 
     * 调用方法 
     */  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        Object result=null;  
        System.out.println("事物开始");  
        //执行方法  
        result=method.invoke(target, args);  
        System.out.println("事物结束");  
        return result;  
    }  
}
复制代码

TestProxy.java

import net.battier.dao.BookFacade;  
import net.battier.dao.impl.BookFacadeImpl;  
import net.battier.proxy.BookFacadeProxy;  
  
public class TestProxy {  
    public static void main(String[] args) {  
        BookFacadeProxy proxy = new BookFacadeProxy();  
        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());  
        bookProxy.addBook();  
    }    
}
复制代码

说明:使用Java的动态代理有一个局限性就是被代理的类必须实现接口(也就是说只能对该类所实现接口中定义的方法进行代理),虽然面向接口编程是每个优秀的Java程序员都知道的规则,但现实往往不尽人意,对于没有实现接口的代理类如何为其生成代理类呢? 继承 继承是最经典的扩展已有代码能力的手段。CGLib代理就是这样实现代理。

CGLib动态代理

CGLib采用非常底层的字节码生成技术,通过为一个类创建子类来生成代理类,它弥补了Java动态代理的不足,因此,Spring中的动态代理和CGLib都是创建代理的重要手段,对于实现了接口的类就用动态代理为其生成代理类,而没有实现接口的类就用CGLib通过继承方式为其创建代理类。但因为采用的是继承,所以不能对final修饰的类进行代理。

intercept参数列表:

参数 说明
Object obj 代理的对象
Metho method 表示拦截的方法
Object[] args 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
MethodProxy methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
package com.test.test2.proxy;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/5/3 21:32
 */
public class BookFacadeImpl1 {
    public void addBook(){
        System.out.println("增加图书的普通方法");
    }
}
复制代码
package com.test.test2.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/5/3 21:34
 */

/**
 * CGLib创建动态代理
 */
public class BookFacadeProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }

    @Override
    // 回调方法  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始事务");
        methodProxy.invokeSuper(obj, args);
        System.out.println("事务结束");
        return null;
    }
}

复制代码
public class TestCglib {  
      
    public static void main(String[] args) {  
        BookFacadeCglib cglib=new BookFacadeCglib();  
        BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());  
        bookCglib.addBook();  
    }  
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective JavaScript

Effective JavaScript

赫尔曼 (David Herman) / 黄博文、喻杨 / 机械工业出版社 / 2014-1-1 / CNY 49.00

Effective 系列丛书经典著作,亚马逊五星级畅销书,Ecma 的JavaScript 标准化委员会著名专家撰写,JavaScript 语言之父、Mozilla CTO —— Brendan Eich 作序鼎力推荐!作者凭借多年标准化委员会工作和实践经验,深刻辨析JavaScript 的内部运作机制、特性、陷阱和编程最佳实践,将它们高度浓缩为极具实践指导意义的 68 条精华建议。 本书共......一起来看看 《Effective JavaScript》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器