剑指架构师系列-struts2构造函数的循环依赖注入

     2022-03-22     534

关键词:

 

Struts2可以完成构造函数的循环依赖注入,来看看Struts2的大师们是怎么做到的吧!

首先定义IBlood与BloodImpl类: 

public interface IBlood {
}

public class BloodImpl implements IBlood{
	
	private IPeople people;

	
	@Inject
	public BloodImpl(@Inject IPeople people) {
		System.out.println("Blood 构造函数被调用.");
		this.people = people;
	}
}

再定义个IPeople与PeopleImpl类:

public interface IPeople {
}

public class PeopleImpl implements IPeople{
	
	private IBlood blood;
	
	@Inject
	public PeopleImpl(@Inject IBlood blood){
		System.out.println("People 构造函数被调用 ");
		this.blood = blood;
	}
}

为什么要为两个实现类定义接口呢?因为两者的依赖注入需要使用JDK的动态代码,而JDK的动态代码需要使用接口来实现。

 

在看源码实现前还是先来学习两个实例吧。

 (1)学习Struts2的工厂创建实例及管理实例的范围

定义一个InternalFactory,这个类非常重要。Struts2所有的类实例都是通过这个工厂中的create方法创建出来的。

public interface InternalFactory<T> extends Serializable {
	T create();
}

Struts2不仅可以创建实例,而且还可以管理实例的Scope范围,比如这个实例是单例的,还是每次请求时创建一个新的实例等等...都通过一个枚举Scope类来实现,如下:

 

public enum Scope {

	DEFAULT {
		@Override
		public <T> InternalFactory<? extends T> scopeFactory(String name,final InternalFactory<? extends T> factory) {
			return new InternalFactory<T>() { // 这是一个局部内部内对象
				public T create() {
					return factory.create(); //同一个方法scopeFactory中的局部变量factory
				}
			};
		}
	};

	public abstract <T> InternalFactory<? extends T> scopeFactory(String name,InternalFactory<? extends T> factory);

}

局部内部类的对象可以访问同一个方法中的局部变量,只要这个变量被定义为final的。那么:为什么定义为final变可以呢?定义为final后,编译程序就好实现了。具体实现方法是:将所有的局部内部类对象

要访问的final型局部变量,都当作内部类对象中的一个数据成员。这样,即使栈中局部变量(含final)已死亡,但由于它是final,其值永不变,因而局部内部类对象在变量死亡后,照样可以访问final型局部变量

下面来模仿Struts来通过工厂创建并管理实例的范围,如下:

public class Manager {
	 final static Map<String, InternalFactory<?>> factories = new HashMap<String, InternalFactory<?>>();
	
	 public <T> void factory(String name,Scope scopet) {
		 
		    InternalFactory<? extends T> factory = new InternalFactory<T>() {
					public T create() {
						return (T) new PeopleImpl();
					}
		    };
		    final InternalFactory<? extends T> scopedFactory = scopet.scopeFactory(name, factory);
			factories.put(name, scopedFactory);
    } 
	 
	 
	 public static void main(String[] args) {
		 new Manager().factory("mazhi", Scope.DEFAULT);
		 InternalFactory<IPeople> x = (InternalFactory<IPeople>) factories.get("mazhi");
		 x.create();
	}
}

其实在每次调用工厂方法的create()时都会创建一个新的实例,通过在PeopleImpl的构造函数中打印可以得到验证,当然我们可以创建单实例,这些Strus2都有详细的实现。  

  

 (2)学习JDK动态代码

 

class ConstructionContext<T> {

	List<DelegatingInvocationHandler<T>> invocationHandlers;


	Object createProxy(Class<? super T> expectedType) {
		// if I create a proxy which implements all the interfaces of
		// the implementation type, I‘ll be able to get away with one proxy
		// instance (as opposed to one per caller ).
        // JDK只支持接口的代理,不支持类的代理
		if (!expectedType.isInterface()) {
			System.out.println("不是接口");
		}

		if (invocationHandlers == null) {
			invocationHandlers = new ArrayList<DelegatingInvocationHandler<T>>();
		}
		// Java的代理类
		DelegatingInvocationHandler<T> invocationHandler = new DelegatingInvocationHandler<T>();
		invocationHandlers.add(invocationHandler);

		return Proxy.newProxyInstance(
					expectedType.getClassLoader(),
					new Class[] { expectedType },  // 一组interfaces
					invocationHandler
				);
	}

	void setProxyDelegates(T delegate) {
		if (invocationHandlers != null) {
			for (DelegatingInvocationHandler<T> invocationHandler : invocationHandlers) {
				invocationHandler.setDelegate(delegate);
			}
		}
	}

	static class DelegatingInvocationHandler<T> implements InvocationHandler {
		T delegate;
		public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
			if (delegate == null) {
				throw new IllegalStateException(
						"Not finished constructing. Please don‘t call methods on this"
								+ " object until the caller‘s construction is complete.");
			}

			try {
				return method.invoke(delegate, args);// delegate表示希望被代理的对象
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			}
		}

		void setDelegate(T delegate) {
			this.delegate = delegate;
		}
	}
}

主要用这个类来辅助获取JDK代理对象,并在随后设置真正的被代理对象的。

interface A{
	public void tt();
}
class B implements A{

	public void tt() {
       System.out.println("调用了我");		
	}
}

class C {
	private A b;
	
	@Inject
	public C(A b){
		this.b = b;
	}
	
	public void print(){
		b.tt();
	}
}

定义了3个类,其中C中需要注入B,B实现了接口A。看一下@Inject注解的实现吧。

@Target({ METHOD, CONSTRUCTOR, FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface Inject {
	String value() default "default";
	boolean required() default true;
}

写个测试用例:

public class TestJDKProxy {

	public static void main(String[] args) throws Exception {
		new TestJDKProxy().test();
	}
	
	 public  void test() throws Exception{
		 ConstructionContext constructionContext = new ConstructionContext();
		 Object obj = constructionContext.createProxy(struts2.learn.jdk.A.class);
         Constructor cn = findConstructorIn(struts2.learn.jdk.C.class);
         Object tempC = cn.newInstance(new Object[]{obj});  // 先获取接口代理对象并注入C中
         System.out.println(cn);
         constructionContext.setProxyDelegates(new B());    // 随后还需要将真正的被代理对象设置进去
         ((C)tempC).print(); 
     }
     
	private Constructor findConstructorIn(Class implementation) {
		Constructor found = null;
		Constructor[] declaredConstructors = (Constructor[]) implementation.getDeclaredConstructors();
		for (Constructor constructor : declaredConstructors) {
			if (constructor.getAnnotation(Inject.class) != null) {
				if (found != null) {
					// 不能有多于一个构造函数上标有@Inject注解
					throw new DependencyException("More than one constructor annotated with @Inject found in "	+ implementation + ".");
				}
				found = constructor;
			}
		}
		if (found != null) {
			return found;
		}
		// If no annotated constructor is found, look for a no-arg constructor instead.
		try {
			return implementation.getDeclaredConstructor();
		} catch (NoSuchMethodException e) {
			throw new DependencyException("Could not find a suitable constructor" + " in " + implementation.getName() + ".");
		}
	}
	
}

有机会再补充几个反射的例子。阅读Struts2的依赖注入源代码时,这都是必不可少的知识点,要不然逻辑层层嵌套,一会儿就迷糊了。  

  

  

  

 

 

 

 

 

 

  

 

剑指架构师系列-设计模式

 1、单例模式:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式有以下几个要素:私有的构造方法指向自己实例的私有静态引用以自己实例为返回值的静态的公有的方法     ... 查看详情

架构师技能7:循环依赖引发的架构设计思考(代码片段)

本文单纯是个人的思考和总结。一、背景1、问题最近团队项目一个服务出现循环依赖的问题,导致无法启动。循环依赖即:beanA依赖于另一个beanB,而beanB又依赖于beanA,这个时候就很容易形成一个闭环甚至死循环... 查看详情

剑指架构师系列-activemq队列的使用

 安装ActiveMQ只需要下载包后解压,然后就可以启动与关闭ActiveMQ了,如下: ./activemqstart./activemqstop访问管理页面:http://10.10.20.20:8161/admin用户名和密码默认为:admin/admin spring.activemq.broker-url--指定ActiveMQbroker的URL,默认自... 查看详情

剑指架构师系列-hibernate需要掌握的annotation

 1、一对多的关系配置@Entity@Table(name="t_order")publicclassOrder{ @Id @GeneratedValue privateintid; privateStringname; /**该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,*而且这种... 查看详情

剑指架构师系列-springboot的logback日志记录

  SpringBoot集成了Logback日志系统。Logback的核心对象主要有3个:Logger、Appender、Layout 1、Logback Logger:日志的记录器 主要用于存放日志对象,也可以定义日志类型、级别。级别:ERROR、WARE、INFO、DEBUG和TRACE。没有FATA... 查看详情

剑指架构师系列-logstash分布式系统的日志监控

 Logstash主要做由三部署组成:Collect:数据输入Enrich:数据加工,如过滤,改写等Transport:数据输出下面来安装一下:wgethttps://download.elastic.co/logstash/logstash/logstash-2.3.2.tar.gztar-zxvflogstash-2.3.2.tar.gz  在logstash-2.3.2目录下创建文 查看详情

剑指架构师系列-innodb存储引擎spring事务与缓存

 事务与锁是不同的。事务具有ACID属性:原子性:持久性:由redolog重做日志来保证事务的原子性和持久性,一致性:undolog用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的结果,如幻读。锁是用于解决隔离... 查看详情

架构师技能7:循环依赖引发的架构设计思考(代码片段)

本文单纯是个人的思考和总结。一、背景1、问题最近团队项目一个服务出现循环依赖的问题,导致无法启动。循环依赖即:beanA依赖于另一个beanB,而beanB又依赖于beanA,这个时候就很容易形成一个闭环甚至死循环... 查看详情

架构师技能7:循环依赖引发的架构设计思考(代码片段)

本文单纯是个人的思考和总结。一、背景1、问题最近团队项目一个服务出现循环依赖的问题,导致无法启动。循环依赖即:beanA依赖于另一个beanB,而beanB又依赖于beanA,这个时候就很容易形成一个闭环甚至死循环... 查看详情

剑指架构师系列-持续集成之maven+nexus+jenkins+git+springboot

 1、Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要粘贴个Jar包的地址,Maven就会自动到... 查看详情

spring源码-循环依赖,java架构师必学

Spring在哪些情况下会出现循环依赖错误?哪些情况下能自身解决循环依赖,又是如何解决的?本文将介绍笔者通过本地调试Spring源码来观察循环依赖的过程。1.注解属性注入首先本地准备好一份Spring源码,笔者是从Github上Clone下... 查看详情

Java构造函数中的循环依赖

】Java构造函数中的循环依赖【英文标题】:CirculardependencyinJavaconstructors【发布时间】:2011-04-0810:46:24【问题描述】:我有以下课程。publicclassBpublicAa;publicB()a=newA();System.out.println("CreatingB");和publicclassApublicBb;publicA()b=newB();System.out.p 查看详情

注意细节,阿里架构师一文详解springdi的四种依赖注入方式

参考技术A2.1了解DI的思想顾名思义,依赖注入是由“依赖”和“注入”两个词汇组合而成,那么我们再一次顺藤摸瓜,分别分析这两个词语!2.2依赖关于谁依赖与谁,当然是应用程序依赖于IOC容器。因为应用程序依赖于IOC容器提... 查看详情

成为架构师课程系列架构师的核心能力地图

目录架构师核心能力总结#综合技术能力分层总结#数据结构和算法知识图谱总结#Java工程师【核心基础】知识图谱总结 查看详情

《我要进大厂系列九》-谈谈spring循环依赖(代码片段)

文章目录1.前言2.什么是循环依赖?3.两种Spring容器循环依赖3.1.构造器循环依赖(无法解决)3.2.setter循环依赖(可以解决)3.3.小结4.循环依赖检查5.循环依赖的处理5.1.单例setter循环依赖5.2.Spring解决循环依赖5.3.... 查看详情

springboot循环依赖(代码片段)

...oot应用程序中,循环依赖通常是由以下几种情况引起的:构造函数循环依赖: 两个或更多的组件在它们的构造函数中互相依赖。属性循环依赖: 两个或更多的组件在它们的属性中互相依赖。方法循环依赖: 两个或更... 查看详情

spring系列五:spring怎么解决循环依赖(代码片段)

 15.说说循环依赖?什么是循环依赖?Spring循环依赖Spring循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖。鸡和蛋只有单例的Bean才存在循环依赖的情况,原型(Prototype)情况下,Spring会直接抛出异常... 查看详情

spring读源码系列05----bean的加载---中

Spring读源码系列05----bean的加载---中循环依赖什么是循环依赖spring是如何解决循环依赖的1.构造器循环依赖2.setter循环依赖3.prototype范围的依赖处理创建BeanAbstractAutowireCapableBeanFactory#createBean—创建bean前的准备AbstractAutowireCapableBeanFac... 查看详情