剑指架构师系列-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 ...
随机推荐
- 关于《精通移动App测试实战:技术、工具和案例》图书勘误信息
首先,对由于我们工作的疏忽向<精通移动App测试实战:技术.工具和案例>读者朋友们表示歉意,同时已将这些问题反馈给了出版社编辑同志,再版时将会统一修正: 其次,勘误信息请参看附件pdf文档 ...
- c#之第三课
学习获取终端输入的参数并且打印,以及使用循环. using System; public class CommandLine { public static void Main(string[] ar ...
- C# 动态修改dll的签名 以及修改引用该dll文件的签名
在读取RedisSessionStateProvider配置 提到用mono ceil 来修改程序集以及它的签名,里面GetPublicKey 和GetPubliKeyToken 方法里面那个字符串的 ...
- 用C/C++实现对STORM的执行信息查看和控制
近期公司有个需求.须要在后端应用server上实时获取STORM集群的执行信息和topology相关的提交和控制,经过几天对STORM UI和CMD源代码的分析,得出能够通过其thrift接口调用实现 ...
- 用于主题检测的临时日志(b2d5c7b3-e3f6-4b0f-bfa4-a08e923eda9b - 3bfe001a-32de-4114-a6b4-4005b770f6d7)
这是一个未删除的临时日志.请手动删除它.(1c773d57-4f35-40cf-ad62-bd757d5fcfae - 3bfe001a-32de-4114-a6b4-4005b770f6d7)
- RabbitMQ学习笔记3-使用topic交换器
topic的路由规则里使用[.]号分隔单词,使用[*]号匹配1个单词,使用[#]匹配多个.和多个*. 在下面的例子中: logger.*可以匹配logger.error和logger.warning, ...
- console 让 js 调试更简单
浏览器的控制台(console)是最重要的面板,主要作用是显示网页加载过程中产生各类信息. 显示信息 console.log('hello world'); console.debug('debug' ...
- WWDC2016-session401-CodeSign大改版
自动签名机制和手动签名都明显看起来很好用. 自动签名有log 手动签名有具体的错误提示信息. session401 Xcode Signing. 亲,你的眼睛好大 相声演员吗? Yeah,You ar ...
- 让Extjs EditorGridPanel 编辑时支持方向键
在用 extjs editorgridpanel 进行输入编辑的时候, 默认情况下只支持使用 tab 键可以实现焦点切换, 如果想让editorgridpanel 在编辑时通过方向键来实现焦点跳转切换 ...
- Android-NDK编译:cocos2d-x(三) eclipse 导入工程
NDK 编译后,用eclipse导入cocos2d-x工程 菜单[File]-->[New]-->[Project] ,弹出New Project 对话框 窗口下方 选 [Android] ...