(语法基础)浅谈面向切面编程(AOP)

一:前言

面向切面编程是一个很重要的思想概念,想要写出一个便于维护的程序,理解AOP并且能熟练的在实际编程中合理的运用AOP思想是很有必要的

二:AOP的基本概念

基础概念:AOP中文翻译面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

三:通过动态代理实现AOP

读完上面的概念,也许还不知道什么是AOP,但是我们只少能从上面概念知道实现AOP的技术基础是预编译方式和运行期动态代理,那么我们就来通过实际代码用动态代理来实现一个简单的程序吧

我们在日常的面向对象编程中常常会遇到这样的场景:
???A类(下面的ClassA)中的很多个方法同时都要调用B类(日志类Logger)的同一个方法
如下:

public class ClassA{

    public Logger logger;

    public ClassA(){
        logger=new Logger();
    }

    public void AFuncA(){
        //....做一些其他事情
        System.out.println("AFuncA做一些其他事情");
        //记录日志
        logger.WriteLog();
    }

    public void AFuncB(){
         //....做一些其他事情
        System.out.println("AFuncB做一些其他事情");
        //记录日志
        logger.WriteLog();
    }

    public void AFuncC(){
         //....做一些其他事情
        System.out.println("AFuncC做一些其他事情");
        //记录日志
        logger.WriteLog();
    }

}
/*
* 日志类
*/
public class Logger{

    public void WriteLog(){
        System.out.println("我是工具类Logger的WriteLog方法,我记录了日志"));
    }

}
/*
*单元测试Main方法
*/
@Test
public void TestMain(){
        ClassA classa=new ClassA();
        classa.AFuncA();
        classa.AFuncB();
        classa.AFuncC();
}

输出结果

???上面代码这个简单的例子相信只要有一点Java,C#等面向对象语言基础的人都能看明白,我们写了一个类ClassA,封装了一个工具类ClassB,然后ClassA三个方法都调用了Logger的WriteLog()方法。
???这样的设计很明显有缺陷,假如我们项目有几百个地方都是用这个WriteLog()方法来记录日志的,一旦我们要更换这个日志记录类或者全部都取消不再记录日志,那么代码修改起来是很苦恼的,很明显ClassA类对Logger类产生了依赖,代码耦合了,那么我们怎么来优化这个类来解除ClassA对Logger的依赖呢,要解决这样的问题就要牵出我们的面向切面编程(AOP)的思想了。
???优化代码如下:我们可以创建一个动态代理工厂对象DynAgentFactory ,然后用基于子类的动态代理对象Enhancer(需要导入cglib依赖包)去代理ClassA对象,然后在使用ClassA时不直接实例化ClassA对象,而是去调用代理工厂对象DynAgentFactory 去调用ClassA类

具体优化代码如下:

public class ClassA{

    public void AFuncA(){
        //....做一些其他事情
        System.out.println("AFuncA做一些其他事情");
    }

    public void AFuncB(){
        //....做一些其他事情
        System.out.println("AFuncB做一些其他事情");
    }

    public void AFuncC(){
        //....做一些其他事情
        System.out.println("AFuncC做一些其他事情");
    }
    //.....
}
/*
*动态代理工厂
*/
public class DynAgentFactory {

    private ClassA proClassA;
    private Logger logger;

    public DynAgentFactory(){
        AFunAgent();
        GetLogger();
    }

    public ClassA GetClassA(){
        return proClassA;
    }

    public Logger GetLogger(){
        logger=new Logger();
        return logger;
    }
    /*
    *代理ClassA对象
    *此处Enhancer类是基于子类的动态代理对象,需要导入cglib依赖(也可以定义一个接口,然后用Proxy实现基于接口的动态代理)
    */
     public void AFunAgent(){
        ClassA classA=new ClassA();
        proClassA=(ClassA) Enhancer.create(classA.getClass(), new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object obj=method.invoke(classA);
                logger.WriteLog();
                return obj;
            }
        });
    }
}
/*
* 日志类
*/
public class Logger
{
    public void WriteLog(){
        System.out.println("我是工具类Logger的WriteLog方法,我记录了日志");
    }
}
/*
*单元测试Main方法
*/
@Test
public void TestMain(){
    DynAgentFactory dynAgentFactory=new DynAgentFactory();
    dynAgentFactory.GetClassA().AFuncA();
    dynAgentFactory.GetClassA().AFuncB();
    dynAgentFactory.GetClassA().AFuncC();
}

输出结果和之前一样

???我们可以看到通动态代理工厂对象DynAgentFactory,我们实现了ClassA与Logger类的解耦,如果以后我们要移除或者更换日志记录类Logger,都只需要统一对DynAgentFactory 里的Logger对象操作即可,相当于把ClassA的公共部分抽离的出来这就前面概念所说的:运行期动态代理

上面的代码是否已经完美了呢?其实还是有一定的问题,那就是我建立的动态代理工厂只是单纯的代理了ClassA,那么假如随着项目的逐渐扩展,我们会新加入ClassB,ClassC...也要用这个统一的记录日志类Logger,我们是否又要重复写一个BFunAgent,CFunAgent的代码去代理记录日志呢,那AFunAgent,BFunAgent,CFunAgent里面的代码不就又重复了吗,所以我们还需要继续优化这个工厂类DynAgentFactory,可以用泛型来解决这个问题

/*
*ClassA代码同上,此处省略
*/
//....
/*
*新加入的ClassB
*/
public class ClassB{

    public void BFuncA(){
        //....做一些其他事情
        System.out.println("BFuncA做一些其他事情");
    }

    public void BFuncB(){
        //....做一些其他事情
        System.out.println("BFuncB做一些其他事情");
    }

    public void BFuncC(){
        //....做一些其他事情
        System.out.println("BFuncC做一些其他事情");
    }
    //.....
}
/*
*泛型动态代理工厂
*/
public class DynAgentFactory<T> {

    private Logger logger;

    public DynAgentFactory(T _proClass){
        TFunAgent(_proClass);
        GetLogger();
    }

    private T proClass;

    public T GetProClass(){
        return proClass;
    }

    public Logger GetLogger(){
        logger=new Logger();
        return logger;
    }
    /*
    *代理ClassA对象
    *此处Enhancer类是基于子类的动态代理对象,需要导入cglib依赖(也可以定义一个接口,然后用Proxy实现基于接口的动态代理)
    *T pro:传入依赖对象
    */
    public void TFunAgent(T pro){
        proClass=(T) Enhancer.create(pro.getClass(), new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object obj=method.invoke(pro);
                logger.WriteLog();
                return obj;
            }
        });
    }
}
/*
*日志类Logger代码同上,此处省略
*/
//...
/*
*单元测试Main方法
*/
@Test
public void TestMain(){
    DynAgentFactory<ClassA> dynAgentFactory=new DynAgentFactory(new ClassA());
    dynAgentFactory.GetProClass().AFuncA();
    dynAgentFactory.GetProClass().AFuncB();
    dynAgentFactory.GetProClass().AFuncC();
    System.out.println("-------------------------------分割线------------------------------------------");
    DynAgentFactory<ClassB> dynAgentFactory2=new DynAgentFactory(new ClassB());
    dynAgentFactory2.GetProClass().BFuncA();
    dynAgentFactory2.GetProClass().BFuncB();
    dynAgentFactory2.GetProClass().BFuncC();
}

输出结果


这样进一步优化之后,不管是ClassA,还是ClassB等其他类,如果想要用到Logger类记录日志,都可以用DynAgentFactory来创建对应代理对象就可以了
>这里插入一点题外话:由于Java语言里的泛型完全是由编译器实现的,JVM在这里不提供任何支持,所以不能像C#支持T t=new T()这种写法,所以每次还需要将实例化的对象通过构造函数的方式注入到工厂对象当中去。

动态代理总结

动态代理技术的使用可以更好的降低代码之间的耦合,提升代码功能的专一性,除了上面的全局记录日志之外,我们还可以用它来处理做一些过滤器,通用事务等等功能,知道了动态代理,我们再去回看spring框架中看到的<aop:{ADVICE NAME}>标签,是不是会对这些标签有更深刻的理解,因为spring aop的实现基础就是动态代理,只不过将代理的前后做了细分,如下为spring通过<aop:{ADVICE NAME}>元素在一个中声明五个adivce

<aop:config>
   <aop:aspect id="myAspect" ref="aBean">
      <aop:pointcut id="businessService"
         expression="execution(* com.xyz.myapp.service.*.*(..))"/>
      <!-- a before advice definition -->
      <aop:before pointcut-ref="businessService"
         method="doRequiredTask"/>
      <!-- an after advice definition -->
      <aop:after pointcut-ref="businessService"
         method="doRequiredTask"/>
      <!-- an after-returning advice definition -->
      <!--The doRequiredTask method must have parameter named retVal -->
      <aop:after-returning pointcut-ref="businessService"
         returning="retVal"
         method="doRequiredTask"/>
      <!-- an after-throwing advice definition -->
      <!--The doRequiredTask method must have parameter named ex -->
      <aop:after-throwing pointcut-ref="businessService"
         throwing="ex"
         method="doRequiredTask"/>
      <!-- an around advice definition -->
      <aop:around pointcut-ref="businessService"
         method="doRequiredTask"/>
   ...
   </aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

三:扩展和总结

通过上面简单的例子我们了解了通过动态代理实现AOP,但这里我们需要知道的是AOP是一种编程思想,所以通过动态代理实现AOP只是其中一种实现方式,我们还可以通过预编译实现AOP,这里就不得不说一下AspectJ面向切面框架了,AspectJ能够在编译期间直接修改源代码生成class。

什么是AspectJ?此AspectJ非彼@AspectJ

在网上一搜一大片所谓AspectJ的用法,其实都是AspectJ的“切面语法”,只是AspectJ框架的冰山一角,AspectJ是完全独立于Spring存在的一个Eclipse发起的项目,官方关于AspectJ的描述是:
Eclipse AspectJ is a seamless aspect-oriented extension to the Java? programming language. It is Java platform compatible easy to learn and use.

是的,AspectJ甚至可以说是一门独立的语言,我们常看到的在spring中用的@Aspect注解只不过是Spring2.0以后使用了AspectJ的风格而已本质上还是Spring的原生实现,关于这点Spring的手册中有提及:
@AspectJ使用了Java 5的注解,可以将切面声明为普通的Java类。@AspectJ样式在AspectJ 5发布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。
因此我们常用的org.aspectj.lang.annotation包下的AspectJ相关注解只是使用了AspectJ的样式,至于全套的AspectJ以及织入器,那完全是另一套独立的东西。至于AspectJ具体要怎么玩儿我也没玩儿过,有兴趣的小伙伴可以自行维基

原文地址:https://www.cnblogs.com/ruanraun/p/javaaop.html

时间: 2024-08-01 19:06:11

(语法基础)浅谈面向切面编程(AOP)的相关文章

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

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

面向切面编程——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

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

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

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;   p

Spring(二)面向切面编程AOP

这篇博客写的比较累赘,好多相同的程序写了好几遍,主要是为了是自己养成这样的一个编程思路,其中应该不乏错误之处,以后好好学,慢慢改吧.------jgp 1 AOP介绍 1.1什么是AOP 面向切面编程(Aspect Oriented Programing):通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码,主要体现在事务处理.日志管理.权限控制.异常处理等方面,使开发人员在编写业务逻辑时可以专心于核心业务,提高了代码的可

面向切面编程AOP——加锁、cache、logging、trace、同步等这些较通用的操作,如果都写一个类,则每个用到这些功能的类使用多继承非常难看,AOP就是解决这个问题的

面向切面编程(AOP)是一种编程思想,与OOP并不矛盾,只是它们的关注点相同.面向对象的目的在于抽象和管理,而面向切面的目的在于解耦和复用. 举两个大家都接触过的AOP的例子: 1)java中mybatis的@Transactional注解,大家知道被这个注解注释的函数立即就能获得DB的事务能力. 2)python中的with threading.Lock(),大家知道,被这个with代码块包裹的部分立即获得同步的锁机制. 这样我们把事务和加锁这两种与业务无关的逻辑抽象出来,在逻辑上解耦,并且可