一:前言

面向切面编程是一个很重要的编程思想,想要写出一个便于维护的程序,理解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. vim命令(转)

    1.Linux下创建文件 vi test.txt 或者 vim test.txt 或者 touch test.txt 2.vi/vim 使用 基本上 vi/vim 共分为三种模式,分别是命令模式(Co ...

  2. 05_配置交换机SSH服务(数通华为)

    1. 网络拓扑: 2. SW1配置:2.1 配置为Access口,vlan 10:[SW1]vlan 10[SW1-GigabitEthernet0/0/1]port link-type access ...

  3. 6-网页,网站,微信公众号基础入门(PHP学习_1)

    https://www.cnblogs.com/yangfengwu/p/11037675.html 安装PhpStrom http://www.jetbrains.com/phpstorm/ 然后百 ...

  4. A1102 | 反转二叉树

    #include <stdio.h> #include <memory.h> #include <math.h> #include <string> # ...

  5. NOI2019 Day1游记

    Day1挂了,没什么好说的... 开场T1想到70分暴力就走人了,后来发现可以写到85...(听说有人写dfs过了95?233333) T2刚了2个多小时,得到每次只在中间填最大值的结论后不会区间DP ...

  6. 【luoguP2994】[USACO10OCT]吃晚饭的时候Dinner Time

    题目链接 按顺序对于每个座位找一个最近的同时编号最小的牛就行了 #include<iostream> #include<cstring> #include<cstdio& ...

  7. shell 文件的包含

    使用.或者source api.sh function intadd() { let data=$+$ echo $data } test.sh #!/bin/bash . api.sh read d ...

  8. exit命令

    exit命令用于退出当前shell,在shell脚本中可以终止当前脚本执行. 常用参数格式:exit n退出.设置退出码为n.(Cause the shell to exit with a statu ...

  9. C++ 中的 inline 用法

    1.引入 inline 关键字的原因 在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数. 栈空间就是指放置程序的局部数 ...

  10. Object changed by Unknown

    https://documentation.red-gate.com/soc7/troubleshooting/object-changed-by-unknown https://documentat ...