一:前言

面向切面编程是一个很重要的编程思想,想要写出一个便于维护的程序,理解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)的更多相关文章

  1. 面向切面编程AOP

    本文的主要内容(AOP): 1.AOP面向切面编程的相关概念(思想.原理.相关术语) 2.AOP编程底层实现机制(动态代理机制:JDK代理.Cglib代理) 3.Spring的传统AOP编程的案例(计 ...

  2. Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)

    在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...

  3. [译]如何在ASP.NET Core中实现面向切面编程(AOP)

    原文地址:ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE 原文作者:ZANID HAYTAM 译文地址:如何在ASP.NET Cor ...

  4. 设计模式之面向切面编程AOP

    动态的将代码切入到指定的方法.指定位置上的编程思想就是面向切面的编程. 代码只有两种,一种是逻辑代码.另一种是非逻辑代码.逻辑代码就是实现功能的核心代码,非逻辑代码就是处理琐碎事务的代码,比如说获取连 ...

  5. Spring学习手札(二)面向切面编程AOP

    AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...

  6. Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)

    一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...

  7. Spring框架学习笔记(2)——面向切面编程AOP

    介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...

  8. Spring之控制反转——IoC、面向切面编程——AOP

      控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...

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

    面向切面编程AOP 描述:将某段代码“动态”的切入到“指定方法”的“指定位置”进行运行的一种编程方式 (其底层就是Java的动态代理)spring对其做了简化书写 场景: 1).AOP加日志保存到数据 ...

随机推荐

  1. Windows上使用Linux命令

    WSL Windows Subsystem for Linux(简称WSL)是一个在Windows 10上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层.它是由微软与Canonical公 ...

  2. ent 基本使用七 Config

    通过config 我们可以自定义表相关的选项 参考配置 package schema ​ import ( "github.com/facebookincubator/ent" & ...

  3. cloudevents 通用event 描述指南

    cloudevents 是由cncf 组织管理的一个通用event描述指南 特性: 一致性 可理解性 可移植性 说明 cloudevents 不仅提供了核心描述,同时也包含了不同协议的指南说明(htt ...

  4. 洛谷P1084 疫情控制

    题目 细节比较多的二分+跟LCA倍增差不多的思想 首先有这样一个贪心思路,深度越低的检查点越好,而最长时间和深度具有单调性,即给定时间越长,每个军队能向更浅的地方放置检查点.因此可以考虑二分时间,然后 ...

  5. K8s的存储卷使用总结

    K8s的存储卷: 它有四种存储卷: 1. emptyDir: 空目录,这种存储卷会随着Pod的删除而被清空,它一般作为缓存目录使用,或临时目录, 当做缓存目录时,通常会将一块内存空间映射到该目录上,让 ...

  6. About me recently

    About me recently Recently I fell that memory has always been problematic.Maybe I hava bee too tired ...

  7. mac 搭建Java Spring boot 环境(eclipse)

    安装 下载安装Springboot 安装完成后,创建项目 1. 2. 3. 完成创建!

  8. 第08组 Alpha事后诸葛亮

    组长博客 点这里! 总结思考 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 弥补Powerpoint中模板转换存在的缺陷,完善PPT模板一键转换的功能 ...

  9. Lombok的使用详解与插件安装

    JAVA面向对象编程中的封闭性和安全性.封闭性即对类中的域变量进行封闭操作,即用private来修饰他们,如此一来其他类则不能对该变量访问.这样我们就将这些变量封闭在了类内部,这样就提高了数据的安全性 ...

  10. Java中的long类型和Long类型比较大小

    Java中我们经常要做一些判断,而对于判断的话,用的最多的便是“>”.“==”.“<”的比较,这里我们进行一个Long类型数据和long类型数据的比较大小的讲解. Java中Long和lo ...