核心解读

  • 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. go-zero 微服务实战系列(二、服务拆分)

    微服务概述 微服务架构是一种架构风格,它将一个大的系统构建为多个微服务的集合,这些微服务是围绕业务功能构建的,服务关注单一的业务功能,这些服务具有以下特点: 高度可维护和可测试 松散的耦合 可独立部署 ...

  2. 7. Docker CI、CD

    在上图这个新建的docker-compose.yml文件中把刚才的代码粘贴进去. 可把上述文件保存后,然后到/etc/ssh/sshd_config文件中更改下对应的端口号即可. 然后重新启动sshd ...

  3. 数位 dp 总结

    数位 dp 总结 特征 问你一个区间 \([L,R]\) 中符合要求的数的个数 一个简单的 trick :把答案拆成前缀和 \(Ans(R)-Ans(L-1)\) 如何求 \(Ans()\) ,就要用 ...

  4. VBA驱动SAP GUI实现办公自动化(一)

    小爬之前写过一系列Python驱动SAP GUI实现办公自动化的文章,其实如果我们的实际业务不是太复杂,且我们对VBA语法比较熟悉的话,我们完全可以借助Excel VBA来驱动SAP GUI做很多自动 ...

  5. Arduino WeMos D1 开发环境搭建

    更新记录 2022年4月16日:本文迁移自Panda666原博客,原发布时间:2021年9月2日. WeMos D1介绍 WeMos D1开发板全称是WeMos D1 WiFI UNO R3开发板,基 ...

  6. AsList()方法详解

    AsList()方法详解 在Java中,我们应该如何将一个数组array转换为一个List列表并赋初始值?首先想到的肯定是利用List自带的add()方法,先new一个List对象,再使用add()方 ...

  7. CSCMS代码审计

    很久之前审的了. 文章首发于奇安信攻防社区 https://forum.butian.net/share/1626 0x00 前言 CSCMS是一款强大的多功能内容管理系统,采用php5+mysql进 ...

  8. 六张图详解LinkedList 源码解析

    LinkedList 底层基于链表实现,增删不需要移动数据,所以效率很高.但是查询和修改数据的效率低,不能像数组那样根据下标快速的定位到数据,需要一个一个遍历数据. 基本结构 LinkedList 是 ...

  9. css做旋转相册效果

    css做旋转相册效果 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...

  10. KTL 一个支持C++14编辑公式的K线技术工具平台 - 第七版,体验GPGPU。

    K,K线,Candle蜡烛图. T,技术分析,工具平台 L,公式Language语言使用c++14,Lite小巧简易. 项目仓库:https://github.com/bbqz007/KTL 国内仓库 ...