当前位置: 首页 > article >正文

【Java校招面试】基础知识(二)——Spring Framework AOP

目录

  • 前言
  • 一、Spring Framewwork基础知识
  • 二、Spring AOP基础概念
    • 1. 切面(Aspect)
    • 2. 织入(Weaving)
    • 3. 增强(Advice)
    • 4. 动态代理
  • 三、JDK动态代理
    • 1. 基本用法
    • 2. 原理分析
  • 四、CGLib动态代理
    • 1. 基本用法
    • 2. 原理分析
    • 3. MethodProxy
    • 4. FastClass机制
  • 五、SDK动态代理和CGLib动态代理的区别
  • 六、AspectJ
    • 1. 基本用法
    • 2. AspectJ提供的几种增强注解
  • 后记


前言

本篇主要介绍Spring FrameworkAOP面向切面编程的源码解析。

“基础知识”是本专栏的第一个部分,本篇博文是第二篇博文,如有需要,可:

  1. 点击这里,返回本专栏的索引文章
  2. 点击这里,返回上一篇《【Java校招面试】基础知识(一)——Java常用类库》
  3. 点击这里,前往下一篇《【Java校招面试】基础知识(三)——多线程与并发》

一、Spring Framewwork基础知识

1. IoC、DL、DI的关系
在这里插入图片描述
IoC即控制反转,DL即依赖查找,DI即依赖注入

从上图中可见,DL和DI是IoC的两种实现方式。DL因为对业务代码具有入侵性,所以已被抛弃。 而实现DI的方式又分为Set注入接口注入注解注入构造器注入4种。

2. IoC容器
在这里插入图片描述
从图中可知:

  1. IoC容器首先从各种配置文件、注解中读取Bean的配置信息,存放在Bean定义注册表中。
  2. 接着根据Bean注册表去实例化Bean。
  3. 然后将实例化好的Bean实例存放在Bean缓存池中。
  4. 在应用程序需要用到Bean实例时,从Bean缓存池取出对应的实例,注入进去。

3. BeanFactory
它是Spring框架最核心的接口,其作用有:

  • 提供IoC的配置机制;
  • 包含各种Bean的定义,便于实例化Bean;
  • 建立Bean之间的依赖关系;
  • 控制Bean的生命周期。

4. ApplicationContext接口
它继承了多个接口:

  • BeanFactory:管理、装配Bean;
  • ResourcePatternResolver:加载资源文件;
  • MessageSource:实现国际化等功能;
  • ApplicationEventPublisher:注册监听器,实现监听机制。

5. Bean的作用域

  • singleton:Spring的默认作用域,容器里拥有唯一的Bean实例;
  • prototype:针对每个getBean请求,容器都会创建一个Bean实例;
  • request:会为每个Http请求创建一个Bean实例;
  • session:会为每个session创建一个Bean实例;
  • globalSession:会为每个全局Http Session创建一个Bean实例,该作用域仅针对Portlet有效。

6. Bean的生命周期
在这里插入图片描述
从图中可以看出,Bean的生命周期分为4个部分:
1)Bean的实例化
2)Bean的初始化
3)Bean的使用
4)Bean的销毁


二、Spring AOP基础概念

AOP的出现并不是要完全替代OOP,而仅是作为OOP的有益补充。 AOP的应用场合是受限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理以及日志记录。

以下是Spring AOP的一些基础概念:

1. 切面(Aspect)

1)静态切面: 指在生成代理对象时,就确定了增强是否需要织入到目标类连接点上;
2)动态切面: 指必须在运行期根据方法入参的值来判断增强是否需要织入到目标类连接点上;
3)流程切点: 代表由某个方法直接或间接发起调用的其他方法,流程切面由流程切点和相应的增强组成;
4)复合切面: 有时,一个切点难以描述目标连接点的信息,我们就需要创建复合切点切面;
5)引介切面: 是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的链接点是类级别的,而非方法级别的。通过引介增强,我们可以为一个目标类添加一个接口实现。

2. 织入(Weaving)

织入是将增强添加到目标类具体连接点上的过程。AOP有3种织入的方式:
1)编译器织入: 这要求使用特殊的Java编译器;
2)类装载期织入: 这要求使用特殊的类装载器;
3)动态代理织入: 在运行期为目标类添加增强生成子类的方式。
Spring Framework采用动态代理织入

3. 增强(Advice)

增强即在一些业务逻辑外,需要频繁来写的非业务逻辑代码,如有一类方法执行前都要执行一些通用的初始化步骤;有一类方法执行完成后都要执行一些通用的反初始化步骤;甚至有的方法两者都需要。这些通用的代码有:性能监测、访问控制、事务管理以及日志记录等。

增强的类型分为:

  • 方法级别的增强: 前置增强、后置增强、环绕增强、异常抛出增强
  • 类级别的增强: 引介增强

4. 动态代理

Spring AOP使用了两种代理机制:

  • 基于JDK的动态代理;
  • 基于CGLib的动态代理。

之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。


三、JDK动态代理

JDK1.3以后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例。

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:ProxyInvocationHandler

  • InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
  • Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

1. 基本用法

以下对JDK动态代理列举一段代码进行示例:

1)我们自己的,需要织入增强的业务接口:

	public interface Business {
	    void doBusiness();
	}

2)业务逻辑实现类:

	public class BusinessImpl implements Business {
	    @Override
	    public void doBusiness() {
	        System.out.println("I'm doing business");
	    }
	}

这里的业务逻辑中,因为只做演示,我们只输出了一句话I'm doing business,实际开发中的业务逻辑可以是任意代码。

3)业务逻辑代理类:

	public class BusinessProxy implements InvocationHandler {
		//增强的目标,这里指的是业务逻辑实现类BusinessImpl
	    private Object target;
	    public BusinessProxy(Object target) {
	        this.target = target;
	    }
		
		//获取代理对象
	    public <T> T getProxy() {
	        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	    }
	
		//实现增强逻辑
	    @Override
	    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	        System.out.println("Doing pre-advice.");
	        Object result = method.invoke(target, args);
	        System.out.println("Doing post-advice.");
	        return result;
	    }
	}

这里的增强逻辑我们实现了一个环绕增强,即在业务逻辑前输出一句话Doing pre-advice.,在业务逻辑后输出一句话Doing post-advice.。实际开发中的增强逻辑也可以是性能监测、访问控制、事务管理以及日志记录等逻辑。

4)业务逻辑调用类:

	public class CallBusiness {
	    public static void main(String[] args) {
	        Business business = new BusinessProxy(new BusinessImpl()).getProxy();
	        business.doBusiness();
	    }
	}

5)输出结果

Doing pre-advice.
I’m doing business
Doing post-advice.

可以看到,通过JDK动态代理,我们实现了业务逻辑的增强。

2. 原理分析

1)其大致流程为:
① 为接口创建代理类的字节码文件;
② 使用ClassLoader将字节码文件加载到JVM;
③ 创建代理类实例对象,执行对象的目标方法。

2)生成的目标字节码文件内容:

	package com.sun.proxy;
	
	import java.lang.reflect.InvocationHandler;
	import java.lang.reflect.Method;
	import java.lang.reflect.Proxy;
	import java.lang.reflect.UndeclaredThrowableException;
	import test.jdkproxytest.Business;
	
	public final class $Proxy0 extends Proxy implements Business {
	    private static Method m1;
	    private static Method m2;
	    private static Method m0;
	    private static Method m3;
	
	    public $Proxy0(InvocationHandler var1) throws  {
	        super(var1);
	    }
	
	    public final boolean equals(Object var1) throws  {
	        try {
	            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
	        } catch (RuntimeException | Error var3) {
	            throw var3;
	        } catch (Throwable var4) {
	            throw new UndeclaredThrowableException(var4);
	        }
	    }
	
	    public final String toString() throws  {
	        try {
	            return (String)super.h.invoke(this, m2, (Object[])null);
	        } catch (RuntimeException | Error var2) {
	            throw var2;
	        } catch (Throwable var3) {
	            throw new UndeclaredThrowableException(var3);
	        }
	    }
	
	    public final int hashCode() throws  {
	        try {
	            return (Integer)super.h.invoke(this, m0, (Object[])null);
	        } catch (RuntimeException | Error var2) {
	            throw var2;
	        } catch (Throwable var3) {
	            throw new UndeclaredThrowableException(var3);
	        }
	    }
	
	    public final void doBusiness() throws  {
	        try {
	            super.h.invoke(this, m3, (Object[])null);
	        } catch (RuntimeException | Error var2) {
	            throw var2;
	        } catch (Throwable var3) {
	            throw new UndeclaredThrowableException(var3);
	        }
	    }
	
	    static {
	        try {
	            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
	            m2 = Class.forName("java.lang.Object").getMethod("toString");
	            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
	            m3 = Class.forName("test.jdkproxytest.Business").getMethod("doBusiness");
	        } catch (NoSuchMethodException var2) {
	            throw new NoSuchMethodError(var2.getMessage());
	        } catch (ClassNotFoundException var3) {
	            throw new NoClassDefFoundError(var3.getMessage());
	        }
	    }
	}

根据生成的字节码文件,我们得出以下结论:

生成的代理类继承了我们定义的代理类并且实现了要代理的接口,由于Java不支持多继承,所以JDK动态代理不能代理类;
② 有一个静态代码块,通过反射或者代理类的所有方法;
③ 通过invoke执行代理类中的目标方法;


四、CGLib动态代理

使用JDK创建代理有一个限制,即它只能为接口创建代理实例。对于没有通过接口定义业务方法的类,CGLib作为一个替代者,填补了这个空缺。

CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

1. 基本用法

同样,以下对JDK动态代理列举一段代码进行示例:

业务接口业务逻辑实现类沿用前文中的的代码

1)业务逻辑代理类

	public class BusinessProxy implements MethodInterceptor {
	    private Enhancer enhancer = new Enhancer();
	    public Object getProxy(Class clazz) {
	        enhancer.setSuperclass(clazz);
	        enhancer.setCallback(this);
	        return enhancer.create();
	    }
	
	    @Override
	    public Object intercept(Object o, Method method, Object[] os, MethodProxy mp) throws Throwable {
	        System.out.println("Doing pre-advice");
	        Object result = mp.invokeSuper(o, os);
	        System.out.println("Doing post-advice");
	        return result;
	    }
	}

2)业务逻辑调用类:

	public class CallBusiness {
		public static void main(String[] args) {
	        BusinessProxy proxy = new BusinessProxy();
	        BusinessImpl businessImpl = (BusinessImpl) proxy.getProxy(BusinessImpl.class);
	        businessImpl.doBusiness();
	    }
	}

输出结果相同,此处不再赘述。

2. 原理分析

1)生成的代理类:
调用CallBusiness之后,会生成3个代理类的Class文件:
在这里插入图片描述
我们先打开第二个文件:

	public class BusinessImpl$$EnhancerByCGLIB$$d53a0f55 extends BusinessImpl implements Factory {
	    ......
	    private MethodInterceptor CGLIB$CALLBACK_0;  //拦截器
	    ......
	    private static final Method CGLIB$doBusiness$0$Method;  //被代理方法
	    private static final MethodProxy CGLIB$doBusiness$0$Proxy;  //代理方法
	    ......
	
	    static void CGLIB$STATICHOOK1() {
			......
			//代理类
	        Class var0 = Class.forName("test.cglibproxytest.BusinessImpl$$EnhancerByCGLIB$$d53a0f55");
	        //被代理类
	        Class var1;
	        ......
	        CGLIB$doBusiness$0$Method = ReflectUtils.findMethods(new String[]{"doBusiness", "()V"}, (var1 = Class.forName("test.cglibproxytest.BusinessImpl")).getDeclaredMethods())[0];
	        CGLIB$doBusiness$0$Proxy = MethodProxy.create(var1, var0, "()V", "doBusiness", "CGLIB$doBusiness$0");
	    }
	}

通过代理类的源码可以看到,代理类会获得所有从父类继承来的方法,并且会有MethodProxy与之对应,比如Method:CGLIB$doBusiness$0$Method,MethodProxy:CGLIB$doBusiness$0$Proxy

2)方法的调用

		//代理方法(methodProxy.invokeSuper会调用)
	    final void CGLIB$doBusiness$0() {
	        super.doBusiness();
	    }
		
		//被代理方法(methodProxy.invoke会调用,在拦截器中调用methodProxy.invoke会死循环,因为其会调用拦截器)
	    public final void doBusiness() {
	        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
	        if (this.CGLIB$CALLBACK_0 == null) {
	            CGLIB$BIND_CALLBACKS(this);
	            var10000 = this.CGLIB$CALLBACK_0;
	        }
	
	        if (var10000 != null) {
	        	//调用拦截器
	            var10000.intercept(this, CGLIB$doBusiness$0$Method, CGLIB$emptyArgs, CGLIB$doBusiness$0$Proxy);
	        } else {
	            super.doBusiness();
	        }
	    }

由此可以从中看出方法的调用链: 代理对象调用this.doBusiness方法 --> 调用拦截器 --> methodProxy.invokeSuper --> CGLIB$doBusiness$0 --> 被代理对象doBusiness方法

3. MethodProxy

拦截器MethodInterceptor中就是由MethodProxy的invokeSuper方法调用代理方法的,MethodProxy非常关键,需要分析一下它具体做了什么。

1)创建MethodProxy

	public class MethodProxy {
	    private Signature sig1;
	    private Signature sig2;
	    private MethodProxy.CreateInfo createInfo;
	    private final Object initLock = new Object();
	    private volatile MethodProxy.FastClassInfo fastClassInfo;
	    /** 
	     * @param c1 被代理对象Class
	     * @param c2 代理对象Class
	     * @param desc 入参类型列表字符串
	     * @param name1 被代理方法名
	     * @param name2 代理方法名
	     */
	    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
	        MethodProxy proxy = new MethodProxy();
	        // 被代理方法签名
	        proxy.sig1 = new Signature(name1, desc);
	        // 代理方法签名
	        proxy.sig2 = new Signature(name2, desc);
	        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
	        return proxy;
	    }
	
		private static class CreateInfo {
		    Class c1;
		    Class c2;
		    NamingPolicy namingPolicy;
		    GeneratorStrategy strategy;
		    boolean attemptLoad;
		
		    public CreateInfo(Class c1, Class c2) {
		        this.c1 = c1;
		        this.c2 = c2;
		        AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
		        if(fromEnhancer != null) {
		            this.namingPolicy = fromEnhancer.getNamingPolicy();
		            this.strategy = fromEnhancer.getStrategy();
		            this.attemptLoad = fromEnhancer.getAttemptLoad();
		        }
		    }
		}
	}

2)invokeSuper调用

		public Object invokeSuper(Object obj, Object[] args) throws Throwable {
		        try {
		            this.init();
		            MethodProxy.FastClassInfo fci = this.fastClassInfo;
		            return fci.f2.invoke(fci.i2, obj, args);
		        } catch (InvocationTargetException var4) {
		            throw var4.getTargetException();
		        }
		    }
		
		private static class FastClassInfo {
		    FastClass f1;  //被代理类FastClass
		    FastClass f2;  //代理类FastClass
		    int i1;   //被代理类的方法签名(index)
		    int i2;  //代理类的方法签名
		
		    private FastClassInfo() {}
		}

上面代码调用过程就是获取到代理类对应的FastClass,并执行了代理方法。之前生成3个class文件中:

  • 第一个就是代理类的FastClass
  • 第三个就是被代理类的FastClass

4. FastClass机制

Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。

这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。

public class BusinessImpl$$FastClassByCGLIB$$e73ca2d3 extends FastClass {
    public BusinessImpl$$FastClassByCGLIB$$e73ca2d3(Class var1) {
        super(var1);
    }

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case 765831722:
            if (var10000.equals("doBusiness()V")) {
                return 0;
            }
            break;
		......
        return -1;
    }

    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        BusinessImpl var10000 = (BusinessImpl)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.doBusiness();
                return null;
            ......
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
}

FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中。

		// MethodProxy invoke/invokeSuper都调用了init()
		private void init() {
	        if(this.fastClassInfo == null) {
	            Object var1 = this.initLock;
	            synchronized(this.initLock) {
	                if(this.fastClassInfo == null) {
	                    MethodProxy.CreateInfo ci = this.createInfo;
	                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
						// 如果缓存中就取出,没有就生成新的FastClass
	                    fci.f1 = helper(ci, ci.c1);
	                    fci.f2 = helper(ci, ci.c2);
						// 获取方法的index
	                    fci.i1 = fci.f1.getIndex(this.sig1);                      					fci.i2 = fci.f2.getIndex(this.sig2);
	                    this.fastClassInfo = fci;
	                    this.createInfo = null;
	                }
	            }
	        }
	    }

五、SDK动态代理和CGLib动态代理的区别

  1. JDK动态代理实现了被代理对象的接口,CGLib动态代理继承了被代理对象;
  2. 两者都是在运行期生成字节码,JDK直接写Class字节码,CGLib使用ASM框架写Class字节码,CGLib实现更复杂,生成代理类的效率比JDK低;
  3. 调用代理方法时,JDK是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,执行效率比JDK更高。

六、AspectJ

它是一个基于Java的AOP框架,比Spring AOP更复杂,性能更好。

1. 基本用法

同样,以下对AspectJ列举一段代码进行示例:

业务接口沿用前文中的的代码

1)业务逻辑切面类

	@Aspect
	public class BusinessAspect {
	    @Before("execution(* doBusiness(..))")
	    public void beforeDoingBusiness(){
	        System.out.println("Doing pre-advice");
	    }
	    ......
	}

这里的@Before("execution(* doBusiness(..))")称为前置增强注解。表达的意思是,在execution:执行*:任何类的doBusiness(..):方法@Before:之前,都执行下面的beforeDoingBusiness方法。

同样的@AfterReturning后置增强注解的代码省略。

2)业务逻辑调用类

	public class CallBusiness {
	    public static void main(String[] args) {
	        BusinessImpl business = new BusinessImpl();
	        AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
	        proxyFactory.setTarget(business);
	        proxyFactory.addAspect(BusinessAspect.class);
	        BusinessImpl proxy = proxyFactory.getProxy();
	        proxy.doBusiness();
	    }
	}

输出结果相同,此处不再赘述。

可见,AspectJ在用法,代码更简洁,便于记忆。唯一的难点是:引入的切面规则语法,需要一定的学习和记忆。

2. AspectJ提供的几种增强注解

  1. @Before: 前置增强,相当于BeforeAdvice的功能;
  2. @AfterReturning: 后置增强,相当于AfterReturningAdvice;
  3. @Around: 环绕增强,相当于MethodInterceptor;
  4. @AfterThrowing: 抛出增强,相当于ThrowsAdvice;
  5. @After: Final增强,不管抛出异常还是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}的控制流;
  6. @DeclareParents: 引介增强,相当于IntroductionInterceptor。

后记

这一篇内容也不少,不过大部分是代码实例。

Spring Framework相关内容,我的免费专栏 《Spring学习笔记》 中有更为详尽的概念、原理及实践方法,如有需要可移步。

本篇中:
Spring Framework部分: 仅列举了核心的概念。

Spring AOP部分: 概念和原理确实应该知道以应对面试,不过这里也具有很高的实践价值,列举一些场景:如使用环绕增强实现接口耗时统计、使用后置增强上报接口调用、安卓端用户点击某些功能按钮的比例上报统计等。AOP面向切面编程虽然不能替代OOP面向对象编程,但作为其补充,则可以省略很多重复的代码,大有用处。


http://www.kler.cn/a/16614.html

相关文章:

  • win11 新建一个批处理,双击查看本机的IP地址
  • 性能优化、安全
  • Pycharm PyQt5 环境搭建创建第一个Hello程序
  • 2411C++,C++26反射示例
  • 11张思维导图带你快速学习java
  • ML 系列: 第 23 节 — 离散概率分布 (多项式分布)
  • java stream 实践篇
  • day1_内存区域
  • 枚举法计算24点游戏
  • C++Primer第五版【阅读笔记】
  • LeetCode 560. 和为 K 的子数组
  • kettle不同数据源的字段不一致的合并后插入数据库
  • 如何使用快速排序算法对整数数组进行就地排序?
  • 从4k到42k,软件测试工程师的涨薪史,给我看哭了
  • 我的医学预测模型评价步骤(仅供参考)
  • SmartEngine流程引擎之Custom模式
  • ApplicationContextAware接口
  • ETL工具 - Kettle 输入输出算子介绍
  • MyBatisPlus代码生成器使用
  • Linux Ansible角色介绍
  • Python使用AI animegan2-pytorch制作属于你的漫画头像/风景图片
  • 3.3 泰勒公式例题分析
  • c++ 11标准模板(STL) std::vector (三)
  • 同时使用注解和 xml 的方式引用 dubbo 服务产生的异常问题排查实战
  • 抓马,互联网惊现AI鬼城:上万个AI发帖聊天,互相嗨聊,人类被禁言
  • ASIC-WORLD Verilog(6)运算符