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

Posted ruanraun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(语法基础)浅谈面向切面编程(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具体要怎么玩儿我也没玩儿过,有兴趣的小伙伴可以自行维基

以上是关于(语法基础)浅谈面向切面编程(AOP)的主要内容,如果未能解决你的问题,请参考以下文章

浅谈AOP和代理模式

浅谈AOP和代理模式

浅谈spring中AOP以及spring中AOP的注解方式

Spring框架深入--AOP面向切面

AOP面向切面编程

Spring基础_面向切面(AOP)