Java 面向切面编程 AOP

本文内容

  • 实例

    • 引入
    • 原始方法
    • 装饰者模式
  • JDK 动态代理和 cglib 代理
  • 直接使用 AOP 框架

下载 demo

实例


引入

package com.cap.aop;
 
public interface ICalculator {
    public double add(double num1, double num2) throws Exception;
 
    public double sub(double num1, double num2) throws Exception;
 
    public double div(double num1, double num2) throws Exception;
 
    public double mul(double num1, double num2) throws Exception;
}
package com.cap.aop;

 
/**
 * 加减乘除
 * */
public class Calculator implements ICalculator {
    @Override
    public double add(double num1, double num2) {
        double result = num1 + num2;
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) {
        double result = num1 - num2;
        return result;
    }
 
    @Override
    public double div(double num1, double num2) {
        double result = num1 / num2;
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) {
        double result = num1 * num2;
        return result;
    }
}

定义 ICalculator 接口和类 Calculator,并且 Calculator 继承 ICalculator

若要为这个类添加“日志”功能该怎么办?日志在实际项目中很有必要,比如数据库日志,业务日志等等,通过日志就能知道数据库和业务存在的问题,这要比调试程序容易多了,此外还有性能统计,安全控制,事务处理,异常处理等等都是类似的问题。

我们最可能想到的是,在类的每个方法内都写日志相关的代码,或是在该类的基类中写,在其子类中继承。

原始方法

package com.cap.aop;
 
/**
 * 加减乘除,原始方式
 * */
public class CalculatorOriginalWay implements ICalculator {
    @Override
    public double add(double num1, double num2) {
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 + num2;
 
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) {
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 - num2;
 
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) {
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 / num2;
 
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) {
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 * num2;
 
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
}

这样做得缺点显而易见,重复代码太多,耦合也不好。要是该类只针对正数运算呢,代码如下所示:

package com.cap.aop;
 
/**
 * 加减乘除,只对正数
 * */
public class CalculatorForPositiveNumber implements ICalculator {
    @Override
    public double add(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 + num2;
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 - num2;
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 / num2;
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 * num2;
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    private void argsValidatior(double arg) throws Exception {
        if (arg < 0)
            throw new Exception("参数不能为负数");
    }
}

这也仅仅是一个类而已,实际项目中那么多类,要是都这么干,显然不现实,那么如何改进?

设计模式“装饰者模式”,在不必改变原类文件和继承的情况下,动态地扩展一个对象的功能。

装饰者方法

package com.cap.aop;
 
/**
 * 加减乘除,装饰者模式
 */
public class CalculatorDecorator implements ICalculator {
    private ICalculator cal;
 
    public CalculatorDecorator(ICalculator iCalculator) {
        cal = iCalculator;
    }
 
    @Override
    public double add(double num1, double num2) throws Exception {
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.add(num1, num2);
 
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) throws Exception {
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.sub(num1, num2);
 
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) throws Exception {
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.div(num1, num2);
 
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) throws Exception {
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.mul(num1, num2);
 
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
}

这个方法比上面的强点,但也有弱点,需要为每个类都应用“装饰者模式”,工作量也不小。如何解决?——代理。

 

JDK 动态代理和 cglib 代理



JDK 从 1.3 版本开始,引入了动态代理。JDK 动态代理非常简单,但动态代理的对象必须是一个或多个接口。

若想代理类,就需要使用 cglib 包。cglib 是一个强大的、高性能的代码生成包,cglib 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类,cglib 被许多 AOP 框架使用,例如 Spring AOP;Hibernate 使用 cglib 来代理单端 single-ended(多对一和一对一)关联;EasyMock 通过使用模仿(moke)对象来测试 java 包……它们都通过 cglib 来为那些没有接口的类创建模仿(moke)对象。

JDK 动态代理

package com.cap.aop;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
 
/**
 * 加减乘除,JDK 代理<br/>
 * 只能代理接口,不能代理类
 * 
 * */
public class CalculatorInvocationHandler implements InvocationHandler {
    // 动态代理只有在运行时才知道代理谁,所以定义为Object类型
    private Object target = null;
 
    /**
     * 通过构造函数传入原对象
     * 
     * @param target
     *            要代理的对象
     */
    public CalculatorInvocationHandler(Object target) {
        this.target = target;
    }
 
    /**
     * InvocationHandler 接口的 invoke 方法,调用代理的方法
     * 
     * @param proxy在其上调用方法的代理实例
     * @param method拦截的方法
     * @param args拦截的参数
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("JDK proxy...");
        // 日志开始
        System.out.println("the method [" + method.getName() + "]"
                + "begin with args (" + Arrays.toString(args) + ")");
 
        Object result = method.invoke(this.target, args);
 
        // 日志结束
        System.out.println("the method [" + method.getName() + "]"
                + "end with result (" + result + ")");
 
        return result;
    }
 
    /**
     * 获取代理类
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new CalculatorInvocationHandler(target));
    }
}

cglib 代理

package com.cap.aop;

 
import java.lang.reflect.Method;
import java.util.Arrays;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
/**
 * 加减乘除,cglib 代理<br/>
 * 能代理接口和类,不能代理final类
 */
public class CalculatorInterceptor implements MethodInterceptor {
    private Object target;
 
    public CalculatorInterceptor(Object target) {
        this.target = target;
    }
 
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy invocation) throws Throwable {
        System.out.println("cglib proxy...");
        // 日志开始
        System.out.println("the method [" + method.getName() + "]"
                + "begin with args (" + Arrays.toString(args) + ")");
 
        Object result = invocation.invoke(target, args);
        // 日志结束
        System.out.println("the method [" + method.getName() + "]"
                + "end with result (" + result + ")");
 
        return result;
    }
 
    public Object proxy() {
        return Enhancer.create(target.getClass(), new CalculatorInterceptor(
                target));
    }
}

主程序如下所示:

package com.cap.aop;

 
public class Client {
    public static void main(String[] args) throws Exception {
        ICalculator calculatorProxy = (ICalculator) new CalculatorInvocationHandler(
                new Calculator()).getProxy();
        calculatorProxy.add(10, 10);
 
        Calculator calculator = (Calculator) new CalculatorInterceptor(
                new Calculator()).proxy();
        calculator.add(20, 20);
    }
}

运行结果:

JDK proxy...
the method [add]begin with args ([10.0, 10.0])
the method [add]end with result (20.0)
cglib proxy...
the method [add]begin with args ([20.0, 20.0])
the method [add]end with result (40.0)

 

直接使用 AOP 框架——AspectWerkz



利用 AOP 框架,你只需要利用注释和 Aspect 就可以完成上面工作。

AOP,Aspect Oriented Programming,称为“面向切面编程”,是一种通过预编译和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。在 Spring 中,通过分离应用的业务逻辑与系统级服务,应用对象只完成业务逻辑而已,并不负责(甚至是意识)其它的系统级的关注点,例如日志、事务、审计等等。AOP 是 GoF 的延续,GoF 孜孜不倦的追求是,调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP 的目标也是一样。

AOP 主要应用在日志记录,性能统计,安全控制,事务处理,异常处理等等,将它们从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP 与 OOP/OOD

AOP(面向切面编程)与 OOP(面向对象编程)字面上虽然类似,但却是面向不同领域的两种设计思想。

OOP 针对业务中的实体及其属性和行为进行抽象封装,以便划分逻辑单元。而 AOP 则是针对业务中的“切面”进行提取,它面对的是处理过程中的某个步骤或阶段,以获得各部分之间低耦合性的隔离效果。因此,这两种设计思想有着本质的差异。

简单来说,对“雇员”这个业务实体进行封装,是 OOP 的任务,我们可以建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用 AOP 对“雇员”进行封装将无从谈起;权限检查也是如此,它是 AOP 的领域。

换而言之,OOD/OOP 面向名词领域,AOP 面向动词领域。

很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 接口也能,我想这个观点是值得商榷的。AOP 和定义良好的 OOP 可以说都是用来解决并且实现需求中的横切问题。但对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法回避的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会,你只需要修改相应的 Aspect,再重新 weave(编织)即可。 AOP 也绝对不会取代 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,扬长避短。

AOP 涉及的概念

这些概念,在 AspectWerkz 的注释中都有所体现。

  • Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

Java 的 AOP 框架

  • AspectWerkz 是简单、动态、轻量级、强大的 AOP 框架,更容易的集成 AOP 的项目中。
  • JBoss AOP 是 JBoss 4.0 带了一个 AOP 框架,但也能够在你的应用中单独的运行它。
  • Nanning。Nanning Aspects is a simple yet scaleable aspect-oriented framework for Java. Nanning is also nice "little" town in Guanxi province in southern China. It‘s about 1-2 million inhabitants which barely qualifies it as a town by chinese standards. It is not to be confused with the much larger Nanking/Nanjing.
  • JAC,Java Aspect Components 是一个应用服务器。它为Java2平台、用于Java开发的企业开发环境(J2EE)、和基于Web的分布式应用,提供开放式资源 的又一个选择(在GNU次常规公共许可证下发布)。JAC包括统一模型语言(UML)IDE,该UML IDE模块化应用商业逻辑并且自动生成和编译纯商业逻辑Java类。这些类,在JAC容器内执行,可从一组技术和/或商业的横切关系 (crosscutting concerns)[1] 如数据持久性、认证、配置文件管理、访问权限检测、演示、和负载平衡中无缝地受益。基于面向方面编程技术(AOP)的JAC将这些关系( concerns)[2]从应用程序的核心商业逻辑中分离出来。
  • DynamicAspects。This project is no longer maintained! DynamicAspects enables you to do aspect-oriented programming in pure Java. Using the "instrumentation" and "agent" features introduced with Sun JDK 1.5, aspects can be installed and deinstalled during runtime!
  • CAESAR。CaesarJ is a new Java based programming language, which facilitates better modularity and development of reusable components. The components are collaborations of classes, but they can modularize crosscutting features or non-functional concerns. Caesar language features help to implement, abstract and integrate such components. Caesar can be used in combination with plain Java. Tool support is available in the form of an Eclipse plugin.
  • PROSE 是一个动态编排(weaving)工具(允许在运行期插入或抽取aspects)。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。
  • FastAOP。FastAOP is an very high performant AOP (Aspect Oriented Programming) framework for java. The framework was initially

    developped to support performance profiling and monitoring for large J2EE applications with nearly no runntime overhad.

 

.Net 的 AOP 框架

  • Encase 是C#编写开发的为.NET平台提供的AOP框架。Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式。这种部署方面(aspects)的方法帮助缺少经验的开发人员提高开发效率。
  • NKalore 是一款编程语言,它扩展了C#允许在.net平台使用AOP。NKalore的语法简单、直观,它的编译器是基于Mono C#编译器(MCS)。NKalore目前只能在命令行或#Develop内部使用。NKalore兼容公共语言规范CLS(Common Language Specification),它可以在任何.NET开发环境中使用,包括微软的Visual Studio .NET。
  • PostSharp 读取 .NET 字节模块,转换成对象模型。让插件分析和转换这个模型并写回到MSIL。PostSharp使开发程序分析应用程序容易得像分析代码规则和设计模式,它使程序开发的思想变革为面向方面软件开发(AOSD/AOD)思想。
  • AspectDNG 的目标是为.NET开发人员提供简单而功能强大的AOP-GAOP实现。它效仿java下的开源工具AspectJ 和 Spoon,成熟程度也很接近它们。
  • RAIL(Runtime Assembly Instrumentation Library) 开源项目可以在C#程序集加载和运行前进行处理控制调整和重新构建。C#在CLR中,我们已经能够动态加载程序集并且获得程序集中的类和方法,RAIL(Runtime Assembly Instrumentation Library)的出现填补了CLR处理过程中的一些空白。
  • SetPoint是一款.NET框架下的全功能(full-featured)AOP引擎.它着重为称为语义切点(semantic pointcuts)的定义依赖RDF/OWL的使用.它的功能为一个IL-level,highly dynamic weaver&LENDL,一个引人注目的定义语言 DotNetAOP 为 CLR language提供AOP 框架基础属性。
  • NAop 是一个 DotNet 下的 AOP 框架。
  • AspectSharp 是 DotNet 下的免费 AOP框架,它以 Dynamic Proxies 和 XML 作为配置文件。

 

下载 demo

时间: 2024-10-10 23:59:48

Java 面向切面编程 AOP的相关文章

Java——面向切面编程,Spring中的AOP编程

面向切面编程 AOP思想:将横向重复代码,纵向抽取出来 AOP体现--Filter AOP体现--拦截器 AOP体现--动态代理 Spring中实现AOP思想 原理:Spring可以为容器中管理的对象生成代理对象 代理分为动态代理和cglib代理: 动态代理(优先) 被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术,换句话说,就是代理对象和被代理要实现同一接口 cglib代理 第三方代理技术,cglib代理,可以对任何类生成代理,代理的原理是对目标对象进行继承代理,

Web项目中静态代理和动态代理为基础的面向切面编程AOP

本来每天更新的,我一般喜欢夜里过了十二点的时候发文章,结果难道是愚人节吗?学校的网也很有意思,断了,把我给耍了...好吧-开始今天的话题AOP.AOP太重要了,所以放到第二篇文章来谈这个话题,AOP是Spring中的重要概念.如果这个不理解Web开发中的三大框架的原理,那就呵呵了.时常听到同学和网友议论Web程序员大部分时间都是在考皮XML配置,我当时听到也是醉了,所以我要用心学习Web,其实这里面蕴含的设计模式.算法.架构思想在源码中体现的淋漓尽致啊,一个大宝库竟然视而不见可惜了.下面就一起品

java面向切面编程

面向切面在英文中的单词是Aspect Oriented Programming(AOP),在spring框架中叫aop,它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.它是一种新的方法论,它是对传统OOP编程的一种补充. AOP技术是建立在Java语言的反射机制与动态代理机制之上的.请看下图 以上图是利用jdk动态代理为真实对象上的每个方法上加上日志功能.从这张图中可以看出,我们是面向真实对象上的方法编程,把方法切开,在执行方法之前做了日志功能.

【串线篇】面向切面编程AOP

面向切面编程AOP 描述:将某段代码“动态”的切入到“指定方法”的“指定位置”进行运行的一种编程方式 (其底层就是Java的动态代理)spring对其做了简化书写 场景: 1).AOP加日志保存到数据库 2).AOP做权限验证,filter能做的它都能 3).AOP做安全检查 4).AOP做事务控制 AOP专业术语: 原文地址:https://www.cnblogs.com/yanl55555/p/11744089.html

面向切面编程——Aop

一.概念 AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.它是一种新的方法论,它是对传统OOP编程的一种补充. 二.Aop原理 1.面向对象编程模型 OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分.面向对象编程是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等

Spring(四):面向切面编程AOP

横切关注点:分布于应用中多处的功能 面向切面编程AOP:将横切关注点与业务逻辑相分离 在使用面向切面编程时,仍在一个地方定义通用功能,但是可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类. 横切关注点可以被模块化为特殊的类,这些类被称为切面. 好处: 每个关注点集中于一处,而不是分散到多处代码中 服务模块更加简洁,因为它们只包含主要关注点的代码,次要关注点被转移到切面中了 1.定义AOP术语 1.1.通知(Advice) 切面的工作被称为通知. 通知定义了切面是什么以及何

面向切面编程aop

面向切面编程 (AOP) Aspect Oriented Programming 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现. 主要功能 日志记录,性能统计,安全控制,事务处理,异常处理等等 主要意图 将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的

spring中面向切面编程(AOP)的个人理解

面向切面编程AOP,是spring的一大特点 Aspect切面:封装共性功能的(增强功能的)类 Advice通过:切面类中封装的增强功能的方法. PointCut:切入点,是一个集合的概念,该集合的表达使用一个正则表达式表达 所有核心业务对象的所有方法的前后(事务处理AOP典型的应用) JoinPoint:连接点,程序中需要加入advice的地方,而且正在执行的ponitCut 织入(Weaving):将aspect和核心业务对象,进行整合的过程. 通过特定接口实现AOp Aop通知的类型: B

Spring面向切面编程(AOP)

1 spring容器中bean特性 Spring容器的javabean对象默认是单例的. 通过在xml文件中,配置可以使用某些对象为多列. Spring容器中的javabean对象默认是立即加载(立即实例化:spring加载完成,立即创建对象) scope:属性 singleton:默认值为单例,默认也是立即加载,在加载完成spring容器的时候,bean对象已经创建完成 prototype:多例的,默认懒加载,spring容器加载完成的时候,不会创建bean的对象,只有从容器获得bean对象的