`

四、Spring源码分析——动态代理

阅读更多

 

原创内容,转载请注明出处

首先在写这篇文章前我给自己提了以下几个问题。Spring Aop是什么?为什么要有它的存在?它能解决什么实际问题吗?

 

先举一个编程的例子。比如一个功能,实现的是对数据库的一些增删改查的功能(方法名分别是add、delete、update、find),现在由于业务的变更,需要在对数据库操作完成后增加日志记录功能,记录当前人对数据库进行了什么样的操作,操作是否成功。这时首先想到的实现方式应该是在add、delete、update、find的方法内部最后面增加记录日志的实现方式。如果又因为业务的变更,还需要判断这个人是否有该操作的权限,如果没有,则不让该用户进行操作,这时我们又得修改所有的数据库操作的方法,在每个方法执行前校验用户的权限。

 

从上面这个例子可以看出实际的业务功能和权限功能、日志功能耦合在一起,当哪天需要修改或者剔除权限功能和日志功能的时候,这时就需要注释或修改代码了,这样因为一个需求的变更导致工作量成倍的增加显然不是程序员想要的结果。这时,我们可能会想,是否可以将这种通用的功能和业务功能分离开呢?答案是可以的,使用Jdk的动态代理就可以实现在不改变原来的业务功能基础上增加日志功能,如下面这个例子。

 

1、Jdk动态代理实现

用过Jdk动态代理的同学应该都知道,Jdk动态代理主要的类有Proxy和InvocationHandler。先看个例子

新建一个Action接口类,代码如下

package com.test.jdkproxy;

public interface Action {

	void action();
}

 新建Action的实现类ActionImp类,代码如下

package com.test.jdkproxy;

public class ActionImp implements Action {

	public void action() {
		// TODO Auto-generated method stub
		System.out.println("-------执行操作--------");
	}

}

 新建HandlerImp类,实现了InvocationHandler接口,代码如下

 package com.test.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class HandlerImp implements InvocationHandler {
	private Object action;
	
	public HandlerImp(Action action){
		this.action = action;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("-------------记录开始日志-----------------");
		Object obj = method.invoke(this.action, args);
		System.out.println("-------------记录成功日志-----------------");
		return obj;
	}

}

 新建Junit测试代码

	/**
	 * 测试jdk动态代理
	 */
	@Test
	public void testJdkProxy(){
		Action obj = (Action) Proxy.newProxyInstance(ActionImp.class.getClassLoader(), ActionImp.class.getInterfaces(), new HandlerImp(new ActionImp()));
		obj.action();
	}

 执行测试代码,测试结果如下 

如上,Proxy生成了一个Action的动态代理对象,并为Action的方法前后都增加了记录日志功能。

 

2、Spring 动态代理实现

上述的例子是一个Jdk动态代理实现的简单案例,也是一个简单的Aop实现。Spring Aop中的实现有两种,一种是Cglib实现,一种是Jdk动态代理。当目标类是接口的时候,Spring使用Jdk动态代理实现,否则Spring使用Cglib实现。下面来看看Spring Aop的实现例子。

 

创建业务接口类SqlManager,代码如下

package com.test.proxy;

public interface SqlManager {

	public abstract void insert(String sql);

}

 

创建业务实现类SqlManagerImp,代码如下

package com.test.proxy;

public class SqlManagerImp implements SqlManager {
	
	public void insert(String sql){
		System.out.println("--------执行插入!----------");
	}

}

 

创建日志通知类LogAdvice,代码如下

package com.test.proxy;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class LogAdvise implements AfterReturningAdvice {

	public void afterReturning(Object returnValue, Method method,
			Object[] args, Object target) throws Throwable {
		System.out.println("---------记录日志-------");
	}

}

 

创建bean.xml文件,如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <bean id="sqlManager" class="com.test.proxy.SqlManagerImp" lazy-init="true"/>
    <bean id="logAdvice" class="com.test.proxy.LogAdvise" lazy-init="true"/>
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<property name="target" ref="sqlManager"></property>
    	<property name="proxyInterfaces">
    		<value>com.test.proxy.SqlManager</value>
    	</property>
    	<property name="interceptorNames">
    		<list>
    			<value>logAdvice</value>
    		</list>
    	</property>
    </bean>
</beans>

 

创建Junit测试代码如下

	@Test
	public void testProxyFactoryBean(){
		ApplicationContext bf = new FileSystemXmlApplicationContext("H:\\workspaceST\\cygoattest\\src\\test\\resources\\bean.xml");
		SqlManager manager = (SqlManager) bf.getBean("factoryBean");
		manager.insert("");
	}

 

执行测试,结果如下

 到此,Spring动态代理完成了添加日志功能,在这个例子中使用了ProxyFactoryBean,它实现了FactoryBean接口,在BeanFactory篇中有提到过FactoryBean。FactoryBean相当于工厂模式的Factory,是用来生产Bean的,如果在上例中是调用getBean(“&factoryBean”),则会返回一个FactoryBean实例;如果是调用getBean(“factoryBean”),则会先返回ProxyFactoryBean实例,然后通过ProxyFactoryBean的getObject()方法去返回一个SqlManager实例。

 

3、源码分析

在BeanFactory篇中依赖注入里面有讲到AbstractBeanFactory的getObjectForBeanInstace方法,在该方法会判断当前获取的bean是否是FactoryBean,如果是的话则会调用getObject方法去生产实际的bean,上例中是SqlManager。下面开始分析ProxyFactoryBean的getObject方法源码。

1.首先调用initializeAdvisorChain方法,该方法是将Advice转换对应的Advisor,然后保存到一个LinkedList集合中。

 

2.如果对象是单例的话调用getSingletonInstance方法,去获得单例动态代理对象,否则调用newPrototyInstance方法去获取动态代理对象。

 

3.织入通知Advice。Jdk的织入方式入口是InvocationHandler的invoke方法,Cglib方式暂不研究。

 

 3.1、初始化Advisor链

1.在初始化Advisor链时,首先会判断初始化Advisor链标识是否为true,为true代表已经初始化过,直接返回,不需要重复初始化Advisor链。

2.遍历所有Advice名称。然后通过Advice名称让BeanFactory工厂去获取对应的Advice对象。(注:由于ProxyFactoryBean实现了BeanFactoryAware,故而在BeanFactory生成ProxyFactoryBean实例时,会往ProxyFactoryBean实例注入BeanFactory工厂

3.通过DefaultAdvisorAdapterRegistry对象的wrap包装方法,将获取Advice对象包装成Advisor对象,这里包装成DefaultPointcutAdvisor。

4.将包装好的Advisor对象装入LinkedList集合中。

 3.2、获取代理对象

1.调用DefaultAopProxyFactorycreateAopProxy方法去获得AopProxy实例,如果目标类是接口则返回JdkDynamicAopProxy实例,否则返回CglibAopProxy实例。

 

2.调用对应AopProxy实例的getProxy方法去返回动态代理对象,其中JdkDynamicAopProxy是调用Proxy.newProxyInstance返回动态代理对象,也就是说JdkDynamibiancAopProxy的实现就是Jdk动态代理。

 

 3.3、织入通知Advice(Jdk方式)

JdkDynamicAopProxy本身实现了InvocationHandler接口,所以在调用Proxy.newProxyInstance(classLoader,proxiedInterfaces,this)使用的是this,下面来看JdkDynamicAopProxyProxy是怎么实现InvocationHandler的invoke方法。

1.通过AdvisedSupport类的getInterceptorAndDynamicInterceptorAdvice去获取方法对应的方法拦截器MethodInterceptor链。

 

2.如果方法拦截器链是空,则直接通过Java的反射调用方法的执行;否则遍历执行所有拦截器,然后再通过Java反射调用方法的执行。

 

 3.4、拦截器链获取

1.AdvisedSupport类首先去缓存中获取拦截器,如果缓存中不存在拦截器,则调用DefaultAdvisorChainFactory类的getInterceptorsAndDynamicInterceptorAdvice去获取拦截器。

 

2.在DefaultAdvisorChainFactory的getInterceptorsAndDynamicInterceptorAdvice获取拦截器的方法中,实际上调用了DefaultAdvisorAdaterRegistryAdvisor类的getInterceptors方法去将Advisor适配成对应方法拦截器MethodInterceptor(使用适配器模式,通过对应的适配器,将Advisor适配成对应的MethodInterceptor。比如MethodBeforeAdviceAdapter适配器将MethodBeforeAdvice适配成MethodBeforeAdviceInterceptor)。

 

2.1.在DefaultAdvisorAdaterRegistryAdvisor类的构造函数中,首先注册了一系列的默认适配器(MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter)。

 

2.2.在getInterceptors方法中,循环所有适配器去适配Advisor,并将Advisor适配成对应的MethodInterceptor,并返回。

 

2.3.如果advisor实例是PointcutAdvisor,则获取该实例的MethodMatcher,调用matches方法去判断目标方法是否匹配,如果匹配,则返回适配后的MethodInterceptor。

 

3.JdkDynamicAopProxy将相关代理信息(代理对象,目标对象,方法和参数,目标类,拦截器)封装成一个ReflectiveMethodInvocation实例,并调用该实例的proceed方法处理

 

3.1该方法循环遍历List集合interceptorsAndDynamicMethodMatchers拦截器链中的方法拦截器(MethodInterceptor或InterceptorAndDynamicMethodMatcher(该对象内有MethodInterceptor实例成员)对象)。

 

3.2调用MethodInterceptor的invoke方法去完成Advice通知的执行

 

 源代码如附件

  • 大小: 1.7 KB
  • 大小: 13.2 KB
  • 大小: 14.3 KB
分享到:
评论

相关推荐

    五、Spring源码分析——Spring Aop

    《Spring AOP 源码分析》 在深入探讨Spring AOP之前,我们先要理解AOP(面向切面编程)的基本概念。AOP是一种编程范式,它将关注点分离,使得我们可以将横切关注点(如日志、事务管理、安全检查等)与业务逻辑解耦...

    二、Spring源码分析——BeanFactory

    《Spring源码分析——BeanFactory》 在Java的IoC(Inversion of Control)和DI(Dependency Injection)领域,Spring框架扮演着至关重要的角色。BeanFactory是Spring的核心组件之一,它是容器的基石,负责管理应用...

    三、Spring源码分析——ApplicationContext

    《Spring源码分析——ApplicationContext》 在Java世界中,Spring框架是不可或缺的一部分,它以其强大的IoC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)特性,极大地...

    Spring5 源码分析(第 2 版)-某Tom老师

    《Spring5 源码分析(第 2 版)》是某Tom老师精心编写的深度解析文档,旨在帮助读者全面理解Spring5的核心机制和设计理念。Spring作为Java领域最为广泛应用的框架之一,其源码的深入理解对于开发者来说至关重要。这篇...

    Spring源码解析.zip

    本压缩包“Spring源码解析”提供了对Spring框架核心组件——IOC(Inversion of Control,控制反转)、AOP(Aspect Oriented Programming,面向切面编程)以及Transaction(事务管理)的源码分析,帮助开发者更全面地...

    Spring 源码分析文档----自用

    ### Spring 源码分析——设计模式篇 #### 一、引言 Spring框架作为Java企业级开发领域中不可或缺的一部分,其内部集成了多种设计模式,不仅有助于提高系统的可维护性和扩展性,还能够帮助开发者更好地理解和应用...

    spring 源码中文注释

    在源码分析的过程中,读者会深入理解Spring的内部工作机制,例如如何解析配置、如何创建bean实例、如何实现AOP代理等。这将有助于开发者编写更高效、更健壮的代码,也能为参与Spring的扩展或定制打下坚实基础。 总...

    死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理(csdn)————程序.pdf

    在 Spring 中,AOP 的实现主要依赖于代理模式,有两种代理方式:JDK 动态代理和 CGLIB 动态代理。 JDK 动态代理是基于接口的,它要求被代理的目标对象必须实现至少一个接口。Spring 使用 `java.lang.reflect.Proxy`...

    spring高级源码分析

    《Spring高级源码分析》是针对Java开发人员深入理解Spring框架的一份宝贵资源。Spring作为Java企业级应用的基石,其强大的功能和灵活性源于其深厚的设计理念和精巧的源码实现。本分析将深入探讨Spring的核心机制,...

    spring源码UML图

    《Spring框架源码分析——基于UML图的解读》 在深入探讨Spring框架源码之前,我们首先要理解什么是UML(统一建模语言)。UML是一种标准的图形化建模语言,用于软件设计和系统分析,它通过图表来表示系统的结构、...

    struts2+spring集成bug——使用AOP时可能遇到的问题分析

    3. **AOP代理问题**:Spring的AOP有两种代理模式,即JDK动态代理和CGLIB代理。如果动作类没有实现接口,Spring默认会使用CGLIB代理。但有些Struts2的插件或者拦截器可能期望动作类是一个接口,这可能导致问题。确保...

    Spring源码分析

    ### Spring源码分析 #### Spring框架概述 Spring框架是一个开源框架,它于2003年由Rod Johnson创建,旨在简化企业级应用的开发过程。Spring框架最初的理念来源于Rod Johnson在其著作《Expert One-On-One J2EE ...

    spring源码解析和mybatis学习

    6. **Spring源码分析**:通过阅读源码,理解Spring框架的工作原理,增强对框架的深入理解。 接下来,我们转向MyBatis,这是一个轻量级的持久层框架,它提供了灵活的SQL映射机制,使得数据库操作变得简单。"MyBatis3...

    小读spring ioc源码(一)——整体介绍

    在深入理解Spring框架的过程中,源码分析是不可或缺的一环。本文将对Spring的IOC(Inversion of Control,控制反转)容器的源码进行初步探讨,旨在帮助读者从整体上把握其设计理念和实现方式。 Spring的核心就是IOC...

    Spring有关资料 源码

    5. **AOP代理**:Spring提供了两种AOP代理——JDK动态代理和CGLIB代理。前者用于接口实现类,后者用于无接口类。代理允许在方法调用前后插入额外的行为。 6. **数据访问集成**:Spring提供了对各种数据库访问技术的...

    spring源码编译缺少的cglib和objenesis包

    通常,Spring源码的构建会依赖于一系列的外部库,包括但不限于这些特定的版本——spring-cglib-repack-3.2.0.jar和spring-objenesis-repack-2.1.jar。 在编译Spring源码时,确保所有依赖项正确地包含在构建路径中是...

    spring源码, 可以很好的学习spring源码, 对spring更深入理解

    总之,通过深入学习Spring源码,开发者不仅可以提升编程能力,还能掌握更多软件设计和架构方面的知识,对于职业发展大有裨益。在实际开发中,结合源码理解,能够更好地解决遇到的问题,优化代码,提高工作效率。

    java毕业设计&课设-spring源码深度解析+注解开发全套视频教程(视频+源码).doc

    ### Java毕业设计&课设——Spring源码深度解析+注解开发全套视频教程知识点概览 #### 一、Spring框架简介 Spring框架是由Rod Johnson创建的一款开源轻量级Java EE应用框架,旨在简化企业级应用的开发过程。Spring...

    spring的二次代理原因及如何排查

    在使用Spring时,我们可能会遇到一个术语——“二次代理”,这通常涉及到Spring AOP代理机制。本文将深入探讨Spring的二次代理原因以及如何进行问题排查。 首先,我们需要理解什么是代理。在AOP中,代理是Spring...

Global site tag (gtag.js) - Google Analytics