剑指架构师系列-Struts2构造函数的循环依赖注入
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的依赖注入源代码时,这都是必不可少的知识点,要不然逻辑层层嵌套,一会儿就迷糊了。
剑指架构师系列-Struts2构造函数的循环依赖注入的更多相关文章
- 剑指架构师系列-Struts2的缓存
Struts2的缓存中最重要的两个类就是ReferenceMap与ReferenceCache.下面来解释下ReferenceCache中的get()方法. public V get(final Ob ...
- 剑指架构师系列-spring boot的logback日志记录
Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...
- 剑指架构师系列-持续集成之Maven+Nexus+Jenkins+git+Spring boot
1.Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要 ...
- 剑指架构师系列-tomcat6通过IO复用实现connector
由于tomcat6的配置文件如下: <Connector port="80" protocol="org.apache.coyote.http11.Http11Ni ...
- 剑指架构师系列-tomcat6通过伪异步实现connector
首先在StandardService中start接收请求的线程,如下: synchronized (connectors) { for (int i = 0; i < connectors.le ...
- 剑指架构师系列-Hibernate需要掌握的Annotation
1.一对多的关系配置 @Entity @Table(name = "t_order") public class Order { @Id @GeneratedValue priva ...
- 剑指架构师系列-InnoDB存储引擎、Spring事务与缓存
事务与锁是不同的.事务具有ACID属性: 原子性:持久性:由redo log重做日志来保证事务的原子性和持久性,一致性:undo log用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的 ...
- 剑指架构师系列-Linux下的调优
1.I/O调优 CentOS下的iostat命令输出如下: $iostat -d -k 1 2 # 查看TPS和吞吐量 参数 -d 表示,显示设备(磁盘)使用状态:-k某些使用block为单位的列强制 ...
- 剑指架构师系列-MySQL调优
介绍MySQL的调优手段,主要包括慢日志查询分析与Explain查询分析SQL执行计划 1.MySQL优化 1.慢日志查询分析 首先需要对慢日志进行一些设置,如下: SHOW VARIABLES LI ...
随机推荐
- Undefined symbols for architecture i386: "_deflate", referenced from:
Undefined symbols for architecture i386: "_deflate", referenced from: PlatCompress(enumCom ...
- Maven full settings.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Soft ...
- SoapUI Pro Project Solution Collection-DataSource(jdbc,excel)
here give a solution for excel file change the excel configuration these: Set Excel file path in cur ...
- Ios开发之定位CLLocationManager
Ios中的定位功能是通过 Core Location框架实现的.它和地图开发框架是相互独立的.在Core Location中主要实现了定位和地理编码的功能! 下面我们就来介绍一下它的属性,方法和代理方 ...
- python 中偏函数 partial 的使用
函数的partial应用 函数在执行时,要带上所有必要的参数进行调用.但是,有时参数可以在函数被调用之前提前获知.这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用. 例 ...
- 如何使Session永不过期
转载:http://blog.csdn.net/wygyhm/article/details/2819128 先说明情况:公司做监控系统,B/S结构,主要用在局域网内部!监控系统开机可能要开好长时间, ...
- javascript - encodeURI和encodeURIComponent的区别
这两个函数功能上面比较接近,但是有一些区别. encodeURI:不会进行编码的字符有82个 :!,#,$,&,',(,),*,+,,,-,.,/,:,;,=,?,@,_,~,0-9,a-z, ...
- 计算空间直线与平面的交点 (C#)
public class NGlbVec3d {// 三维点 public double x, y, z; public NGlbVec3d() { ...
- Navi.Soft30.产品.Net对象查看器.操作手册
1系统简介 1.1功能简述 在软件开发过程中,我们会编写各种类以及创建类的属性,方法,事件等.特别是第三方控件或组件,刚拿到手时,若没有完善的开发文档,很难下手.这时,若是可以查看这些DLL的成员对象 ...
- 安装与配置 Elasticsearch
环境:centos6.7 #查询已经安装的JDK rpm -qa | grep jdk #卸载 yum -y remove java-1.8.0-openjdk-headless-1.8.0.91- ...