Java核心库实现AOP过程

这篇文章是关于Java的一个疑难杂症,通过利用Java核心库实现简单的AOP方法,并把实例代码做了分析对照,以下是全部内容:

Spring是一个十分火热开源框架,而AOP(面向切面编程)则是Spring最重要的概念之一,为了更好的理解和学习AOP的思想,使用核心库来实现一次不失为一个好方法。

首先介绍一下AOP的概念,AOP(Aspect Oriented Programming),即面向切面编程,所谓的面向切面编程,就是从一个横切面的角度去设计代码的思想,传统的OOP思想是用封装继承和多态构造一种纵向的层次关系,但不适合定义横向的关系,而AOP思想则对此进行了很好的补充。

例如日志管理代码往往横向的散布在很多对象层次中,但跟它对应的对象的核心功能可以说是毫无关系,还有很多类似的代码,如权限验证,调试输出,事务处理等,也都是如此,这样的话就不利于代码的复用和管理了。

这时候AOP技术就应运而生了,它利用“横切”技术,深入封装对象的内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于后续的可操作性和可维护性。

那么AOP又是如何实现的呢?

答案是动态代理(关于代理会有另外篇章做详细介绍,这里就不赘述了)。实现动态代理有两种方式,一种是JDK动态代理,一种是CGLib动态代理。

那么分别使用两种方式来做一个简单的栗子。

先设计一个场景,假设我们有一个计算接口ICalculator和实现了该接口的计算器类CalculatorImpl。

public interface ICalculator {
 //加法运算
 public int add(int a,int b);
 //减法
 public int subtract(int a,int b);
 //乘法
 public int multiply(int a,int b);
 //除法
 public int devide(int a,int b);
}
public class CalculatorImpl implements ICalculator{
 @Override
 public int add(int a, int b) {
  return a + b;
 }
 @Override
 public int subtract(int a, int b) {
  return a - b;
 }
 @Override
 public int multiply(int a, int b) {
  return a * b;
 }
 @Override
 public int devide(int a, int b) {
  return a / b;
 }
} 

如何在不改动原来计算器类内部代码的情况下记录计算器各个方法使用的总次数呢?

有了动态代理后,其实就很简单了,先创建一个类并实现InvocationHandler接口,覆盖invoke方法,

public class TestHandler implements InvocationHandler {
 private Object targetObject;
 private int useTimes;
 //绑定委托对象,并返回代理类

 public Object bind(Object targetObject){
  this.targetObject = targetObject;
  return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
 }
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //do something
  before();
  Object result = method.invoke(targetObject,args);
  after();
  return result;
 }
 private void before(){
  System.out.println("we can do something before calculate.");
 }
 private void after(){
  useTimes++;
  System.out.println("已使用:"+useTimes+"次");
 }
} 

别看代码好像有点多,其实主要的方法就是invoke方法,里面的Object result = method.invoke(targetObject,args);相当于继续用原来的参数执行原来方法。这里的before和after为自定义的函数,可以在目标代码执行前后做一些我们想要做的事情,比如这里的使用次数统计。

在bind方法里,传入目标代理对象,并返回一个代理类实例。接下来我们看看如何使用:

public class TestProxy {
 public static void main(String[] args) {
  TestHandler proxy = new TestHandler();
  ICalculator calculator = (ICalculator)proxy.bind(new CalculatorImpl());
  int result = calculator.add(1,2);
  System.out.println("result is:"+result);
  result = calculator.subtract(3,2);
  System.out.println("result is:"+result);
  result = calculator.multiply(4,6);
  System.out.println("result is:"+result);
  result = calculator.devide(6,2);
  System.out.println("result is:"+result);
 }
} 

我们先定义一个TestHandler,然后通过bind方法来获得一个代理实例,之后我们就可以直接使用这个实例了。运行结果如下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3 

这样我们就实现了不修改CalculatorImpl内部代码的情况下对代码进行扩展。

接下来用CGLib的方式来实现一次。

先创建一个类来实现MethodInterceptor接口,并覆盖intercept方法。其他代码跟使用JDK代理大同小异,仅仅是获取代理对象的过程有所差异。

public class CGLibProxy implements MethodInterceptor {
 private int useTimes;
 private Object target;

 public Object getInstance(Object target){
  this.target=target;
  Enhancer enhancer =new Enhancer();
  enhancer.setSuperclass(this.target.getClass());
  enhancer.setCallback(this);
  return enhancer.create();
 }
 @Override
 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  before();
  Object result = methodProxy.invokeSuper(o,objects);
  after();
  return result;
 }
 private void before(){
  System.out.println("we can do something before calculate.");
 }
 private void after(){
  useTimes++;
  System.out.println("已使用:"+useTimes+"次");
 }
} 

测试一下:

public class TestCGLibProxy {
 public static void main(String[] args) {
  CGLibProxy cgLibProxy = new CGLibProxy();
  ICalculator calculator = (ICalculator) cgLibProxy.getInstance(new CalculatorImpl());
  int result = calculator.add(1,2);
  System.out.println("result is:"+result);
  result = calculator.subtract(3,2);
  System.out.println("result is:"+result);
  result = calculator.multiply(4,6);
  System.out.println("result is:"+result);
  result = calculator.devide(6,2);
  System.out.println("result is:"+result);
 }
} 

运行结果如下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3 

现在我们得到了同样的结果。(需要导入两个包,cglib-2.2.2.jar asm-3.3.jar)

两种方法各有所长,JDK代理需要先设置一个接口,然后才能实现代理,这是它的缺点,也是它的优点,缺点是这样会麻烦一点,而且无法对那些已经封装好的,没有实现接口的类进行代理,而CGLib代理的方式不需要使用接口。但也正是因为如此,JDK代理的方式仅仅拦截类中覆盖接口的方法,而CGLib则会拦截类的所有方法调用。两者各有利弊,所以需要具体情况具体分析。在Spring中也是混杂使用了两种代理模式。

原文地址:https://blog.51cto.com/14311648/2405597

时间: 2024-11-13 08:45:34

Java核心库实现AOP过程的相关文章

Java核心类

Java官方文档:https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/String.html String 字符串在String内部是通过一个char[]数组表示的 String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'}); 各类函数的使用可参考官方文档 String和char[]类型可以互相转换,方法是: char[] cs = "

java 标签库(核心,xml,sql ,国际化,函数)

java标签库分分为上述几种,一般经常使用的是核心和函数,接下来会分别讲解这几种,和常见的用法. 一般标签库会和el表达式一起使用,所以在学习标签库前最后也学习下el表达式的使用. 导入后展开 可以从jar包查看相对应的标签库得tld文档,里面会描述每个标签的说明和用法 先从核心标签库开始 tld文档有几个重点,第一个就是uri,这是等下在jsp页面引入标签库时是的uri 基本一个tld文档的重点内容就这么多了,分开看其实也不是很难 1 <%@taglib uri="http://java

Java核心编程快速学习

Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示. 反射reflect是理解Java语言工作原理的基础,Java编译器首先需要将我们编写的.java源文件编译为.class字节码,然后再JVM虚拟机上运行,接下来通过一个表格,来了解反射的基本操作. 功能 示例 泛化的Class引用 Class<?> intClass = int.class Class<? extends Number> bounded = int.cl

Java核心 --- 枚举

Java核心 --- 枚举 枚举把显示的变量与逻辑的数字绑定在一起在编译的时候,就会发现数据不合法也起到了使程序更加易读,规范代码的作用 一.用普通类的方式实现枚举 新建一个终态类Season,把构造方法设为私有,因为枚举值不能随意增加因为不能new出这个类的对象,所以需要定义成员变量,把new写在类的内部这样,就可以在类的外部通过访问类的静态成员变量的方式访问到枚举值通过这样的方式,只能在类的外部使用在枚举类的内部定义的枚举值 类Season里面是可以有方法的,我们设置地球又公转了四分之一方法

Java核心:类加载和JVM内存的分配

类的加载: 指的是将class文件的二进制数据读入到运行时数据区(JVM在内存中划分的) 中,并在方法区内创建一个class对象. 类加载器: 负责加载编译后的class文件(字节码文件)到JVM(Java虚拟机)当中. 而类加载器主要分为以下几种: 1.Bootstrap class loader (引导类加载器) 负责加载Java核心类库.在jre\lib目录下,包括rt.jar(Java基础类库),这些 都是Java的核心类库.而且这个加载器是由C语言编写的,所以在Java程序中是获取 不

最受欢迎的Java第三方库

翻译自programcreek: 典型的Java项目通常会依赖一些第三方库,本文总结了一些最受欢迎的Java库,这些类库在各种应用程序中被广泛使用: 当然,Java SDK是最广泛使用的Java库,这个不用多说,本文的关注点是第三方库: 这份清单可能不是很完美,如果你觉得有些类库也应该被列举出来,那么请留下你的评论. Core核心库 Apache Commons Lang - 这是Apache的一个库,提供了许多辅助工具,如字符串处理,对象创建等,是对java.lang API的补充: Goog

【DRP】-JSTL核心库 c:out标签

1.引入jar包 index页面:作用点击连接调用页面 <a href="servlet/JstlCoreServlet">测试JSTL核心库</a><br> JstlCoreServlet类,作用:存储数据,转发页面! package com.bjpowernode.jstl; import java.io.IOException; import javax.servlet.ServletException; import javax.servle

从字节码和JVM的角度解析Java核心类String的不可变特性

1. 前言 最近看到几个有趣的关于Java核心类String的问题. String类是如何实现其不可变的特性的,设计成不可变的好处在哪里. 为什么不推荐使用+号的方式去形成新的字符串,推荐使用StringBuilder或者StringBuffer呢. 翻阅了网上的一些博客和stackoverflow,结合自己的理解做一个汇总. 2. String类是如何实现不可变的 String类的一大特点,就是使用Final类修饰符. A class can be declared final if its

Java基础学习笔记二十三 Java核心语法之反射

类加载器 类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,链接,初始化三步来实现对这个类进行初始化. 加载就是指将class文件读入内存,并为之创建一个Class对象.任何类被使用时系统都会建立一个Class对象. 链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程.在链接之前,这个类必须被成功加载.类的链接包括验证.准备和解析等几个步骤. 验证:是否有正确的内部结构,并和其他类协调一致. 准备:负责为类的静态成员分配内存,并设置默认初始化值 解析: