一直使用spring,说起来就是IOC和AOP,看过不少原理的书,但是spring的代码太多,梳理起来很困难,于是想自己实现一下,昨天下午写出代码来,分享一下。
目标:
1、使用annotation编程进行分层,有service层和dao层(mapper层)。
2、设置容器,将所有的实例注入到容器里,类似spring的applicationContext。
3、dao层使用接口,没有实现类,类似于ibitas的使用方式。
4、读取本地文件内容。
根据目标大概思考并实践了以下几点:
1、动态代理选用cglib实现,首先这是spring的实现方式,其次亲测使用jdk的反射包无法实现对接口的动态代理,无法满足上述目标3。
2、容器使用单例模式创建,保证所有的bean只有一个。
代码如下:
首先是自定义的Annotation,为了和spring的加以区别,我都加上了Fx前缀。
第一个是FxMapper,类似spring的@Repository,也就是Dao层应该加的Annotation
package com.smikevon.proxy.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by fengxiao on 15-1-27. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FxMapper { String value(); }
第二个是FxService对应于spring的service层,类似spring的@Service。
package com.smikevon.proxy.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by fengxiao on 15-1-27. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FxService { String value(); }
第三个FxResource对应spring里的@Resource,也就是类内饮用的注释
package com.smikevon.proxy.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by fengxiao on 15-1-27. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface FxResource { String value(); }
Annotation类全部贴完,下面贴出各层的一个示例类。
按上面顺序,第一个mapper类
package com.smikevon.proxy.dynamic;
import com.smikevon.proxy.annotations.FxMapper; /** * Created by fengxiao on 15-1-27. */ @FxMapper("fileMapper") public interface FileMapper { public String read(String fileDir); public void append(String fileDir , String message); }
第二个service类
package com.smikevon.proxy.dynamic; import com.smikevon.proxy.annotations.FxResource; import com.smikevon.proxy.annotations.FxService; /** * Created by fengxiao on 15-1-27. */ @FxService("fileService") public class FileService { String file = "rFile.txt"; @FxResource(value="fileMapper") public FileMapper fileMapper; public void read(){ System.out.println("hello world"); String txt = fileMapper.read(file); System.out.println("message:"+txt); } }
示例类已经贴完,下面贴出容器类。容器类通过使用一个hashmap来实现容器,这里没有考虑线程安全的问题,只是为了示例。初始化使用静态内部类来实现单例模式。
package com.smikevon.proxy.dynamic; import com.smikevon.proxy.Processors.FileMapperProcessor; import com.smikevon.proxy.Processors.FileServiceProcessor; import com.smikevon.proxy.annotations.FxMapper; import com.smikevon.proxy.annotations.FxService; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Created by fengxiao on 15-1-27. */ public class FxApplicationContext { private static final String JAVA_SOURCE_DIR = "src/main/java"; private static final String JAVA_PACKAGE_DIR = "com/smikevon/proxy/dynamic"; private static final String JAVA_PACKAGE_PATH = "com.smikevon.proxy.dynamic"; private Map<String,Object> context = new HashMap<String,Object>(); private FxApplicationContext(){} /** * 容器初始化 */ public FxApplicationContext init(){ try { File file1 = new File(JAVA_SOURCE_DIR+File.separator+JAVA_PACKAGE_DIR); File[] files = file1.listFiles(); for(File file : files){ if(file.getName().endsWith("java")){ String name = file.getName().substring(0,file.getName().indexOf(".")); Class<?> clazz = Class.forName(JAVA_PACKAGE_PATH+"."+name); if(clazz.getAnnotation(FxMapper.class)!=null){ String key = clazz.getAnnotation(FxMapper.class).value(); //实例化mapper的处理类 FileMapperProcessor processor = new FileMapperProcessor(); if(context.get(key) == null){ context.put(key,processor.bind(clazz)); } } if(clazz.getAnnotation(FxService.class)!=null){ String key = clazz.getAnnotation(FxService.class).value(); //实例化service的处理类 FileServiceProcessor processor = new FileServiceProcessor(); if(context.get(key) == null){ context.put(key,processor.bind(clazz)); } } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } return this; } public Object getBean(String beanId){ Iterator<Map.Entry<String, Object>> iterator = context.entrySet().iterator(); while(iterator.hasNext()){ Map.Entry<String, Object> entry = iterator.next(); String key = entry.getKey(); if(key.equals(beanId)){ return entry.getValue(); } } return null; } /** * 静态内部类方式实现单例 */ private static class FxApplicationContextHolder{ public static FxApplicationContext instance = new FxApplicationContext(); } public static FxApplicationContext getInstance(){ return FxApplicationContextHolder.instance; } }
动态代理引用了cglib包,在项目里要加上如下引用:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version>
</dependency>
代理类如下:
先是对有@FxMapper标记的类的动态代理方法
package com.smikevon.proxy.Processors; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.lang.reflect.Method; /** * Created by fengxiao on 15-1-27. */ public class FileMapperProcessor implements MethodInterceptor{ public Object bind(Class clazz) { Enhancer enhancer = new Enhancer(); enhancer.setCallback(this); enhancer.setSuperclass(clazz); Object obj = enhancer.create(); return obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { StringBuilder sb = new StringBuilder(); if(method.getName().toLowerCase().startsWith("read")){ if (objects.length > 0) { String fileDir = (String) objects[0]; File file = new File(fileDir); BufferedReader br = new BufferedReader(new FileReader(file)); String line = null; while((line = br.readLine())!=null){ sb.append(line); } } } return sb.toString(); } }
其次是对有@FxService标记的类的动态代理方法
package com.smikevon.proxy.Processors; import com.smikevon.proxy.annotations.FxResource; import com.smikevon.proxy.dynamic.FxApplicationContext; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Created by fengxiao on 15-1-27. */ public class FileServiceProcessor implements MethodInterceptor{ private Object target; public Object bind(Class clazz) { try { target = clazz.newInstance(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); Object obj = enhancer.create(); //将mapper的实例bean赋值给对应的引用 Field[] fields = clazz.getDeclaredFields(); for(Field field : fields){ if (field.getAnnotation(FxResource.class)!=null){ String value = field.getAnnotation(FxResource.class).value(); Object object = FxApplicationContext.getInstance().getBean(value); //看到没下面这行代码和注释掉的代码效果是一样的,反射的强大之处 field.set(target,object); //((FileService)target).fileMapper = (FileMapper)object; } } return obj; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { method.invoke(target,objects); return null; } }
其实,@FxService和@FxMapper可以合并成一个Annotation,对应的动态代理类也可以合并为一个(类似Spring的@Component ),但是那样的话,每个类的处理逻辑就会比较复杂,不易理解。
下面是测试方法类:
package com.smikevon.proxy.dynamic; import java.lang.reflect.InvocationTargetException; /** * Created by fengxiao on 15-1-27. */ public class FileDemo { public static void main(String[] args) throws InvocationTargetException { FxApplicationContext fxApplicationContext = FxApplicationContext.getInstance().init(); FileService fileService = (FileService)fxApplicationContext.getBean("fileService"); fileService.read(); } }
我的文件内容(文件名fFile.txt,位置就在项目根目录):
hello , my name is Mr Read! I love reading books!
输出结果:
hello world message:hello , my name is Mr Read!I love reading books!
看下有没有很熟悉,和spring使用bean的方式是不是很一致? 原理原来没有什么神秘的,就是这么简单。
这里那里用到了IOC?
容器方式初始化bean,将bean的生命周期交付给容器(hashmap),而不是调用者,示例里容器的生命周期比较简单,即调用init()方法后开始,程序执行完成结束。
这里那里用到了AOP?
通过动态代理读取接口参数,获取相应文件内容,这就是aop。