核心解读

  • AOP:Aspect Oriented Programming,面向切面编程
  • 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去
  • 核心2:面向接口编程,即设置接口类型的变量,传入接口类型的参数,返回接口类型的返回值,调用接口类型的变量自身的实现方法
  • 图示

图示分析:例如,在开发中普遍需要使用到日志输出功能,会将日志输出功能大量耦合到项目的不同位置,如上图左侧所示。

而日志输出功能与其实与项目本身的核心业务逻辑无关,我们只是为了不时的查看项目的运行状态。

则可以将日志功能单独提出去开发,在需要的地方将日志输出功能(所谓:日志功能切面)反织回去即可,如上图右侧所示。

手写AOP框架

  • 下面将手写5个版本的AOP框架,在版本的不断优化中,逐步理解AOP面向切面编程的核心,最后一个版本最接近Spring中AOP的原生实现
  • 业务功能和切面功能用简单的输出语句来模拟,主要是为了简洁直观的演示AOP核心思想
  • 手写的AOP框架的业务背景:图书购买业务

项目结构

  • 5个AOP版本分别放在proxy01 ~ proxy05这5个包下,下图左侧为项目结构,右侧为各版本用到的接口和实现类

AOP版本1

实体类

  • 整个版本1,只是一个BookService实体类,业务功能和切面功能严重耦合
package com.example.proxy01;

/**
* 图书购买功能和事务切面功能耦合在一个类中
*/
public class BookService {
public void buy(){
try{
System.out.println("开启事务....");
System.out.println("图书购买业务....");
System.out.println("提交事务....");
}catch (Exception e){
System.out.println("回滚事务....");
}
}
}

测试

  • 调用BookService实体对象中的buy()方法即可

AOP版本2

优化原理

  • 通过子类代理来实现将业务功能和切面功能初步拆分解耦

实体类

  • 实体类BookService及其子类SubBookService

  • BookService实体类

package com.example.proxy02;

/**
* 图书购买功能
*/
public class BookService {
public void buy(){
System.out.println("图书购买功能....");
}
}
  • SubBookService子类
package com.example.proxy02;

/**
* 子类代理:将图书购买功能和事务切面划分到不同类中
*/
public class SubBookService extends BookService{
@Override
public void buy() {
try{
System.out.println("开启事务事务....");
super.buy();
System.out.println("提交事务....");
}catch (Exception e){
System.out.println("回滚事务....");
}
}
}

测试

  • 调用SubBookService实体类对象中的buy()方法即可

AOP版本3

优化原理

  • 通过静态代理,可以进行受代理对象的灵活切换

接口

  • Service接口
package com.example.proxy03;

/**
* 静态代理接口
*/
public interface Service {
//定义业务功能
void buy();
}

实现类

  • BookServiceImpl实现类
package com.example.proxy03;

/**
* 目标对象
*/
public class BookServiceImpl implements Service{
@Override
public void buy() {
System.out.println("图书购买业务....");
}
}
  • ProductServiceImpl实现类
package com.example.proxy03;

/**
* 另外一种业务功能的目标对象
*/
public class ProductServiceImpl implements Service{
@Override
public void buy() {
System.out.println("产品购买业务....");
}
}
  • 静态代理对象
package com.example.proxy03;

/**
* 静态代理对象
*/
public class Agent implements Service{
//接口类型的参数
Service target; //传入接口类型的参数,灵活调用多种Service接口的实现类
public Agent(Service target){
this.target = target;
} @Override
public void buy() {
try{
System.out.println("开启事务....");
target.buy();
System.out.println("提交事务....");
}catch (Exception e){
System.out.println("关闭事务....");
}
}
}

测试

  • 面向接口编程,可灵活代理多种Service接口的实现类,灵活切换受代理对象
package com.example.test;

import com.example.proxy03.Agent;
import com.example.proxy03.ProductServiceImpl;
import com.example.proxy03.Service;
import org.junit.Test; public class TestProxy03 {
@Test
public void testProxy03(){
//可以灵活切换受代理对象,因为接口类型的参数都能接住
//Service agent = new Agent(new BookServiceImpl());
Service agent = new Agent(new ProductServiceImpl());
agent.buy();
}
}

AOP版本4

优化原理

  • AOP版本3中,虽然受代理对象可以灵活切换,但是不同的受代理对象被绑定到相同的切面功能,切面功能无法灵活切换

  • 可以将上述切面功能上升到接口层次,针对不同切面功能有不同实现类

  • 核心:就像Agent代理对象持有Service接口类型的变量一样,若持有切面接口类型的变量,则可以接收不同切面接口的实现类,实现不同切面功能的灵活切换

  • 考虑到切面功能出现在业务功能的前后关系,以及异常处理等情况,可以根据切面出现的时机定义切面接口中的方法

  • 推导出需要定义切面接口以及如何定义接口中方法的思路图示

接口

  • 业务接口:Service接口
package com.example.proxy04;

/**
* 静态代理接口
*/
public interface Service {
//定义业务功能
void buy();
}
  • 切面接口:Aop接口
package com.example.proxy04;

/**
* 自定义Aop,切面接口
*/
public interface Aop {
default void before(){} //default关键字,jdk8的新特性,可以提供空实现,不强迫接口实现类实现所有接口中的方法
default void after(){} //实现类需要实现哪个方法就实现哪个方法
default void exception(){}
}

实现类

  • 业务功能实现类:BookServiceImpl和ProductServiceImpl
package com.example.proxy04;

/**
* 目标对象
*/
public class BookServiceImpl implements Service {
@Override
public void buy() {
System.out.println("图书购买业务....");
}
}
package com.example.proxy04;

public class ProductServiceImpl implements Service {
@Override
public void buy() {
System.out.println("产品生产业务....");
}
}
  • 切面功能实现类:TransAopImpl和LogAopImpl
package com.example.proxy04;

public class TransAopImpl implements Aop{
@Override
public void before() {
System.out.println("开启事务....");
} @Override
public void after() {
System.out.println("提交事务....");
} @Override
public void exception() {
System.out.println("回滚事务....");
}
}
package com.example.proxy04;

//切面接口中定义方法时使用了default,不必实现所有接口方法,按需实现方法即可
public class LogAopImpl implements Aop{
@Override
public void before() {
System.out.println("前置日志输出....");
}
}

测试

  • 可以实现业务功能和切面功能的灵活组合
  • 而且就像下面第2个测试一样,因为代理对象也是Service的一个实现类,所以代理对象还可以再次被代理,一个业务功能被多个切面包围,实现多切面
  • 此时的版本已经很灵活,也已经揭示出了AOP面向切面编程的核心
package com.example.test;

import com.example.proxy04.*;
import org.junit.Test; public class TestProxy04 { //测试:单个业务功能 + 单个切面功能
@Test
public void testProxy04(){
//分别传入要完成的业务功能和要切入的功能,可以灵活组合,这里就可以有业务功能和切面功能的4种组合:2 x 2
//Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
//Service agent = new Agent(new ProductServiceImpl(), new TransAopImpl());
//Service agent = new Agent(new BookServiceImpl(), new LogAopImpl());
Service agent = new Agent(new BookServiceImpl(), new TransAopImpl());
agent.buy();
} //测试:单个业务功能 + 多个切面功能(本例为:日志切面 + 事务切面)
@Test
public void testManyProxies(){
Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
Service agent2 = new Agent(agent, new TransAopImpl());
agent2.buy();
}
}

AOP版本5

优化原理

  • 静态代理可以做到受代理对象的灵活切换,但是不可以做到代理功能的灵活切换,就像我们用动态代理优化静态代理一样,还可以用jdk动态代理继续优化上述AOP版本4
  • 在Spring原生的AOP框架中,底层就是使用的jdk动态代理,AOP版本5最接近Spring原生AOP框架

实体类

  • AOP版本5中除了用ProxyFactory代理工厂来动态获取代理对象外(不再写AOP版本4中的Agent类,4版本是静态的,现在不用写了),其他接口和实现类与AOP版本4完全一致,不再赘述

  • 新增ProxyFactory类,代替AOP版本4中的Agent类

  • 参数比较多,看起来有些乱(包涵 包涵),若对jdk动态代理不是很熟悉,可以参考mybatis博客集(mybatis 01 对jdk动态代理有详细讨论)

package com.example.proxy05;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* 代理工厂,获取动态代理对象
*/
public class ProxyFactory { //静态方法获取jdk动态代理对象:传入业务功能对象 + 切面功能对象
public static Object getProxy(Service target, Aop aop){ //该方法有三个参数,第三个参数是一个匿名内部类
return Proxy.newProxyInstance(
//参数1
target.getClass().getClassLoader(),
//参数2
target.getClass().getInterfaces(), //参数3:匿名内部类重写的方法又有三个参数
//其中method用来反射调用外部调用的那个方法,args是调用目标方法时要传的参数
new InvocationHandler() {
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
//用来存放目标对象被调用的方法的返回值
Object res = null;
try{
//切面功能
aop.before();
//业务功能,根据外部调用的功能,动态代理目标对象被调用的方法
res = method.invoke(target, args);
//切面功能
aop.after();
}catch (Exception e){
//切面功能
aop.exception();
}
//返回目标对象被调用的方法的返回值给外部调用者
return res;
}
}
);
}
}

测试

package com.example.test;

import com.example.proxy05.BookServiceImpl;
import com.example.proxy05.ProxyFactory;
import com.example.proxy05.Service;
import com.example.proxy05.TransAopImpl;
import org.junit.Test; public class TestProxy05 {
//测试:AOP版本5
@Test
public void testProxy05(){
//获取动态代理对象,传入业务功能对象和切面功能对象,这里传入的业务对象和切面对象可以有多种组合
Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
//完成业务功能和切面功能的组合
agent.buy();
}
}

测试输出

开启事务....
图书购买业务....
提交事务.... Process finished with exit code 0

扩展测试

  • 为了体现动态代理的优点,并测试有参数和有返回值的方法都可被代理,为Service接口扩展功能:order(预定图书的功能)

接口

  • Service接口新增order方法
package com.example.proxy05;

/**
* 静态代理接口
*/
public interface Service {
//定义业务功能
void buy(); //新扩展一个预定功能
default String order(int orderNums){return null;}
//default,不强制实现类都实现该方法,按需实现
}

实现类

  • 让BookServiceImpl实现该新增的方法
package com.example.proxy05;

/**
* 目标对象
*/
public class BookServiceImpl implements Service {
@Override
public String order(int orderNums) {
System.out.println("新预定图书: " + orderNums + " 册");
return "预定成功";
}
@Override
public void buy() {
System.out.println("图书购买业务....");
} }

测试

package com.example.test;

import com.example.proxy05.BookServiceImpl;
import com.example.proxy05.ProxyFactory;
import com.example.proxy05.Service;
import com.example.proxy05.TransAopImpl;
import org.junit.Test; public class TestProxy05 {
@Test
public void testProxy05(){
//获取动态代理对象,传入业务功能对象和切面功能对象
Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
//完成业务功能和切面功能的组合
String res = agent.order(10);
System.out.println("返回结果: " + res);
}
}

测试结果

开启事务....
新预定图书: 10 册
提交事务....
返回结果: 预定成功 Process finished with exit code 0

小结

  • 在AOP版本3优化了业务功能(静态代理)
  • 在AOP版本4优化了切面功能(AOP面向切面编程)
  • 在AOP版本5优化了代理功能(jdk动态代理)
  • 此时手写的AOP版本5可以做到被代理对象的灵活切换,代理功能的灵活切换,业务功能和切面功能的灵活组合
  • AOP版本5最接近Spring中AOP的原生实现原理

Spring 08: AOP面向切面编程 + 手写AOP框架的更多相关文章

  1. Spring-05 -AOP [面向切面编程] -Schema-based 实现aop的步骤

    一.AOP [知识点详解] AOP:中文名称面向切面编程 英文名称:(Aspect Oriented Programming) 正常程序执行流程都是纵向执行流程 3.1 又叫面向切面编程,在原有纵向执 ...

  2. [转] AOP面向切面编程

    AOP面向切面编程 AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  3. Spring:AOP面向切面编程

    AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果. AOP是软件开发思想阶段性的产物,我们比较熟悉面向过程O ...

  4. Spring Boot2(六):使用Spring Boot整合AOP面向切面编程

    一.前言 众所周知,spring最核心的两个功能是aop和ioc,即面向切面和控制反转.本文会讲一讲SpringBoot如何使用AOP实现面向切面的过程原理. 二.何为aop ​ aop全称Aspec ...

  5. 详细解读 Spring AOP 面向切面编程(二)

    本文是<详细解读 Spring AOP 面向切面编程(一)>的续集. 在上篇中,我们从写死代码,到使用代理:从编程式 Spring AOP 到声明式 Spring AOP.一切都朝着简单实 ...

  6. 浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~

    简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring ...

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

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

  8. 基于SpringBoot AOP面向切面编程实现Redis分布式锁

    基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式 ...

  9. Javascript aop(面向切面编程)之around(环绕)

    Aop又叫面向切面编程,其中“通知”是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被 ...

随机推荐

  1. Linux Cgroup v1(中文翻译)(1):Control Group

    英文原文:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html 1 控制组 1.1 什么是控制组? 控制组 ...

  2. 开发工具-SVG占位图片

    更新日志 2022年6月10日 初始化链接. https://toolb.cn/imageholder

  3. docker和docker compose安装使用、入门进阶案例

    一.前言 现在可谓是容器化的时代,云原生的袭来,导致go的崛起,作为一名java开发,现在慌得一批.作为知识储备,小编也是一直学关于docker的东西,还有一些持续继承jenkins. 提到docke ...

  4. 从 CPU 讲起,深入理解 Java 内存模型!

    Java 内存模型,许多人会错误地理解成 JVM 的内存模型.但实际上,这两者是完全不同的东西.Java 内存模型定义了 Java 语言如何与内存进行交互,具体地说是 Java 语言运行时的变量,如何 ...

  5. Spring Security认证器实现

    目录 拦截请求 验证过程 返回完整的Authentication 收尾工作 结论 一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制 Spring Security的登陆请 ...

  6. CentOS中实现基于Docker部署BI数据分析

    作为一个专业小白,咱啥都不懂. linux不懂,docker不懂. 但是我还想要完成领导下达的任务:在linux中安装docker后部署数据可视化工具.作为一名敬业 的打工人摆烂不可以,躺平不可以,弱 ...

  7. SAP BPC 清除CUBE 中的数据

    原理:先根据模型和查询条件取出数据,然后把金额设置为0,再写回CUBE. 1.获取数据并清空金额 *&--------------------------------------------- ...

  8. 不可思议的返回功能——python

    今天给大家分享 3 个比较冷门的知识.教程点这(https://jq.qq.com/?_wv=1027&k=zLK3I0M5) 第一个:神奇的字典键 (https://jq.qq.com/?_ ...

  9. Codeforces Round #802 (Div. 2)

    题集链接 A Optimal Path 水 代码 #include <bits/stdc++.h> #define endl "\n" using namespace ...

  10. JDBCToolsV2:利用ThreadLocal保证当前线程操作同一个数据库连接对象。

    JDBCToolsV2:     利用ThreadLocal保证当前线程操作同一个数据库连接对象. package com.dgd.test; import com.alibaba.druid.poo ...