JFianl自定义IOC工具

来源:互联网 发布:科技美学知乎 编辑:程序博客网 时间:2024/06/09 14:59

学习和了解了关于JFianl框架的一些东西后,发现JFinal并没提供IOC,虽然网上有人说可以通过IOC插件实现,但毕竟JFinal没有原生支持,算是个遗憾。在昨天我自己写了一个IOC工具,今天跟大家分享一下,并且这个工具在我自己的一个Demo项目中也被应用了。
首先说说IOC实现的原理,如果不是很懂IOC的朋友请先搜索一下相关的知识,最起码要了解IOC的功能,而我会直奔主题,我是如何实现的。这里有三个准备工作:
1、IOC注解,作为注入工具,首先我要拿到实例创建的或者引用赋值的权利,而标志需要被IOC管理的类级元素或者成员,我会用注解来描述。
2、IOC注解的翻译器,只要发现了有类级元素或者成员被IOC注解修饰过,那么就需要被管理器接管。
3、IOC管理器,管理所有被托管的类型,负责对外提供类的实例。
针对IOC注解,这里我目前只定义了一个,可能不是很恰当,主要原因有两个:一个是该注解不能很好的描述被标记的类元素所属的层级(MVC层);第二个是IOC管理器目前接管了所有被标记的类,当托管容量过大的时候,或者类型略复杂的时候,这个注解显得过于单薄,这一点以后有时间我会做些改动。目前该注解如下:

/** * @author Rocky * 当该注解被修饰类级元素时,会被IocProcessor加入到IocManager中,如果未指定name,则取ClassForName()作为它的值; * 当该注解被修饰域时,IocProcessor则会从IocManager中取得实例,此时isSingle不发生任何作用 */@Target({ElementType.TYPE, ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface InjectBind {     String name() default "";     boolean isSingle() default true;}
首先它描述了两个作用范围:一个是Type,一个是Field(这里就是我上面提到的一点不足,因为仅有一个注解,那么我需要让它的功能变得强大,而代价就是变得复杂)。当InjectBind修饰一个类的时候,那么表明,该类是需要被IOC管理器接管的;如果修饰一个类的域,那么表明,在该类的对象被初始化后,IOC管理器需要为该域赋值(从管理器中取得一个实例,或者新创建,或者单例取得)。
该注解之后就是对应的处理器,处理器相对简单,先上代码,然后有几点再仔细说明:

/** * @author Rocky * Ioc处理器,翻译InjectBind,实现注入绑定和获得实例功能 */public class IocProcessor {/**   * Ioc管理器   */private static IocManager im = IocManager.getIocManager();/** * 如果当前Class被InjectBind修饰则加入Ioc管理器;  * 如果当前Class有字段被InjectBind修饰则通过Ioc管理器取得实例  * @param obj如果obj是Class类型的时候,表明该类需要被判断是否加入IocManger,否则process就应当在对象实例化之后使用  */public static void process(Object obj) {Class<?> clazz = null;if(obj instanceof Class) {clazz = (Class<?>) obj;} else {clazz = obj.getClass();}if(clazz.isAnnotationPresent(InjectBind.class)) {InjectBind jb = clazz.getAnnotation(InjectBind.class);String name = jb.name();boolean isSingle = jb.isSingle();if(name.isEmpty()) {im.add(clazz, isSingle);} else {im.add(name, clazz, isSingle);}} else {Field[] fields = clazz.getDeclaredFields();for(Field f : fields) {if(f.isAnnotationPresent(InjectBind.class)) {InjectBind jb = f.getAnnotation(InjectBind.class);String name = jb.name();Object inject = null;if(name.isEmpty()) {inject = IocManager.getInstance(f.getClass().getName());} else {inject = IocManager.getInstance(name);}try {f.setAccessible(true);f.set(obj, inject);} catch (IllegalArgumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}}}}
首先要说明的是该处理器持有一个IOC管理器,因为IOC注解工具在翻译注解之后,一旦发现该类或者该类的成员有被IOC注解修饰,那么就需要通过IOC管理器添加到托管集合中,或者从托管集合中取出实例。
第二点就是process()方法接收的参数是Object类型,这里的处理是情非得已。原因有三:第一是因为IOC注解并没有区分究竟是描述的类,还是域,那么process方法就需要把两个情况都考虑到;第二就是当IOC注解描述的是成员的时候,process需要通过IOC管理器取得实例,并为该成员赋值,这里就有一个问题,此时的类对象必须存在,也就是说此时process接收到的一定是一个Object类型的对象实例;第三Class类型也是派生自Object,所以当我使用更底层的类型,是没有问题的,仅需要判断下此时的obj对象的类型是不是Class即可。
最后在看看IOC管理器:

/** * @author Rocky * Ioc管理器,其内部维护着两个Map,分别持有单例和非单例的类映射 */public class IocManager { private static final IocManager im = new IocManager();private Map<String, Class<?>> map = new HashMap<String, Class<?>>();private Map<String, Object> sMap = new HashMap<String, Object>();private IocManager() {}public static IocManager getIocManager() {return im;}/**  * 添加一个映射,当未指定key值的时候,取value.getName()  * @param clazz映射value  * @param isSingle是否单例模式  * @return  */public IocManager add(Class<?> clazz, boolean isSingle) {add(clazz.getName(), clazz, isSingle);return this;}/**  * 添加一个映射,当未指定value值的时候,取Class.forName()  * @param name映射key  * @param isSingle是否单例模式  * @return  */public IocManager add(String name, boolean isSingle) {try {add(name, Class.forName(name), isSingle);} catch (ClassNotFoundException e) {e.printStackTrace();}return this;}/**  * @param name映射key  * @param clazz映射value  * @param isSingle是否单例模式  * @return  */public IocManager add(String name, Class<?> clazz, boolean isSingle) {if(isSingle) {try {sMap.put(name, clazz.newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}} else {map.put(name, clazz);}return this;}/**  * 取得实例  * @param name映射key  * @return  */public static Object getInstance(String name) {if(im.map.containsKey(name)) {try {return im.map.get(name).newInstance();} catch (InstantiationException e) {e.printStackTrace();return null;} catch (IllegalAccessException e) {e.printStackTrace();return null;}} else {return im.sMap.get(name);}}}
这里也有几点需要说明,IOC管理器必须是唯一的,因此是单例的。其次IOC管理器内部维护着两个Map,一个是单例模式被托管的类,一个是非单例的。最后管理器提供了两个主要功能,一个是向其内部的两个Map中添加元素,一个是通过getInstance方法对外提供实例。
至此准备工作结束,最后我来说明下它们是怎么工作的。
在应用部署服务器之后,启动容器,此时我会一个处理,就是扫描指定的路径,获得该路径下的所有的类,然后依次检查是否有被IOC注解修饰(仅检查类级元素),如果有就被加入到IOC管理器中。
之后在运行期间,每当一个实例被创建执行其内部方法的时候,我需要在方法调用前拦截,检查该对象内部是否有成员被IOC注解修饰,如果有则为其赋值。
那么可以简单的模拟一下:
public class A {@InjectBind(name="B")private B b;public void test() {System.out.println(b);System.out.println(b.getTest());}}
类A有一个B类型的成员b,已经被InjectBind修饰,继续:

@InjectBind(name="B")public class B {public String getTest() {return "成功绑定";}}
类B被InjectBind修饰,需要在应用启动时加入到IOC管理器中,最后模拟环境如下:
public class IocTest {@Testpublic void isBindSuccess() {IocProcessor.process(B.class);A a = new A();IocProcessor.process(a);a.test();}}
isBindSuccess方法首先要处理B.class,这是模拟了容器启动后的扫描工作,将被IOC注解修饰的类型加入到IOC管理器中。然后在执行test()方法前处理a对象,这是模拟执行对象方法前的拦截,将对象进行扫描,检查是否有成员被IOC注解修饰,如果有为其赋值,看看结果吧:

最后我要说是,这个IOC工具编写的很简陋,但是方向应该没有太大的偏差,当然这是我自己认为的……如果有朋友发现哪里有问题,希望能及时联系我,感激不尽。

1 0
原创粉丝点击