分享

动手开发自己的mvc

 邵飞翔 2017-03-16
     注解注入
我们知道,Spring只有一个角色:工厂。这个工厂可以生产出任何你想要的对象或依赖,并且在出厂前后可以无限制的增强功能。
Spring最基础的功能就是注入,其中注解注入的方式消除了文件配置的繁琐,让人爱不释手。我们做的这个小框架虽然没法完全依照
它的实现,但是我们可以发挥小而精的特色,做好同样的功能。
首先新建注入注解Autowired
Java代码  收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.FIELD )  
  3. public @interface Autowired {  
  4.      /** 
  5.       * 注入依赖的类 必须为可以实例化的类 
  6.       * 不能是抽象类或者接口 
  7.       * @return 
  8.       */  
  9.      public Class iocClass();  
  10.        
  11. }  

只有一个参数,提供实现类。

接着创建容器BeanContainer,这个容器需要实现读取目标类被注解@Autowired注释的元素,提供注入功能,并且能缓存所有注入类。
处理Autowired注解的过程比较简单:读取Autowire,提取iocClass,实例化一个iocClass对象,通过反射调用setter方法,完成注入
Java代码  收藏代码
  1. for (Field f : fields) {  
  2.                     // 循环判断是否有Autowired的自动注入field  
  3.                     Autowired au = f.getAnnotation(Autowired.class );  
  4.                     //System.out.println("========================="+f.getName()+" : "+au+"======================\n");  
  5.                     if (au != null) {  
  6.                          // 假如此属性被注解为Autowired  
  7.                          // 得到需要注入的实例class  
  8.                         Class ioc = au.iocClass();  
  9.                          // 构造set方法  
  10.                         String setName = "set"  
  11.                                   + f.getName().substring(0, 1).toUpperCase()  
  12.                                   + f.getName().substring(1);  
  13.   
  14.                          // 得到action set的方法以及它的参数类型,注意此时不能是参数子类的类型。  
  15.                         Method setMethod = cls.getMethod(setName, new Class[] { f  
  16.                                   .getType() });  
  17.   
  18.                         Object iocObj = ioc.newInstance();  
  19.                         System. out.println( "处理" + ioc.getName());  
  20.                         AutowiredSet(iocObj);  
  21.                         Object setObject = iocObj;  
  22.                          setMethod.invoke(obj, setObject);  
  23.                    }  
  24.   
  25.               }  

AutowiredSet方法是此代码体的最外围方法体,里面进行了递归调用,实现了被注入类以下所有Autowired属性的注入。
单例注解
我们往往用单例模式来保证每个应用程序只产生一个某类的对象,但是用容器做单例不同于编程式单例模式,前者依赖与容器的缓存功能,后者通过约束构造方法为私有来实现,不要弄混淆哦。
首先创建单例注解:
Java代码  收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.TYPE )  
  3. public @interface SingleTon {  
  4.   
  5. }  

没有任何参数属性,仅仅用来标识此类是否单例。
在容器类BeanContainer创建Map
Java代码  收藏代码
  1. private static Map< String, Object> autoObjMap = new ConcurrentHashMap<String, Object>();  

BeanContainer设计成单例,ConcurrentHashMap可以保证多线程下的线程安全问题,当然 用HashTable也可以。
改造上面代码,加入两句话:

Java代码  收藏代码
  1. if (autoObjMap .containsKey(ioc.getName())) {  
  2.                              System. out.println( "已在缓存中找到" + ioc.getName());  
  3.                              iocObj=getBean(ioc.getName());  
  4.                              setMethod.invoke(obj, iocObj);  
  5.                               continue;  
  6. }  
  7.   
  8. if (checkSingle(iocObj)) {  
  9.                               autoObjMap.put(iocObj.getClass().getName(),  
  10.                                       setObject);  
  11. }  


checkSingle用来判断是否用了单例注解
Java代码  收藏代码
  1. public boolean checkSingle(Object obj) {  
  2.            SingleTon single = obj.getClass().getAnnotation(SingleTon .class);  
  3.            if (single == null) {  
  4.                return false;  
  5.           }  
  6.            return true;  
  7. }  

OK,就这么简单。
上面的注解注入仅仅完成了最基本的功能,还不能无缝的增强原有功能,比如现在有个需求:为特定方法做日志处理,怎么办?每个方法都加上logger?显然不是,最理想的就是
可以直接为这个事务处理类提供功能扩展,并且不影响原有代码。代理模式正是为此类问题而生的。

代理模式
伟大的Spring是设计模式运用的最佳实践,包含工厂模式,代理模式,策略模式,单例模式等等。
而代理模式的运用更是让spring框架充满活力,
毫不夸张的说,正是代理模式的运用让spring看起来无比强大。spring提供两种方式实现代理:jdk动态代理,CGLIB包。
代理模式有这样几个小概念:抽象接口,代理实现类,真实实现类。前者是后面两个的公共接口,是暴露给最终调用者的唯一接口。
首先我们模拟一下代理模式的简单例子。
1,创建抽象接口:
Java代码  收藏代码
  1. public interface UserService {  
  2.     public void save ();  
  3. }  

2,创建真实实现类,实现UserService接口:
Java代码  收藏代码
  1. public class UserServiceImpl implements UserService {  
  2.   
  3.      @Override  
  4.      public void save() {  
  5.           System. out.println( "插入用户");  
  6.      }  
  7.   
  8. }  

3,创建代理实现类,实现UserService接口:
Java代码  收藏代码
  1. public class UserServiceProxy implements UserService {  
  2.   
  3.     private UserService userService;  
  4.     public UserServiceProxy(){  
  5.       //创建真实实现类   
  6.      userService= new UserServiceImpl();  
  7.     }  
  8.      @Override  
  9.      public void save() {  
  10.           System. out.println( "事务开始");  
  11.            userService.save();  
  12.           System. out.println( "事务提交");  
  13.      }  
  14.   
  15. }  

4,创建客户端测试代码:
Java代码  收藏代码
  1. public class ClientMain {  
  2.   
  3.        
  4.      public static void main(String[] args) {  
  5.        UserService userService= new UserServiceProxy();  
  6.        userService.save();  
  7.      }  
  8.   
  9. }  

最终输出:
事务开始
插入用户
事务提交。

当调用save方法时,会调用最终实现类的save方法,并且让你有机会在方法前后进行代码增强。
想做出通用点的代理实现 显然上面的用法是不够的。为了重现代码改造的过程,我先稍微改动一下上面的代码,
UserServiceProxy类添加一个构造方法:
Java代码  收藏代码
  1. public UserServiceProxy(String realclass){  
  2.      try {  
  3.                 userService=(UserService)Class. forName(realclass).newInstance();  
  4.           } catch (Exception e) {  
  5.               e.printStackTrace();  
  6.           }  
  7.     }  

调用代码改为:
Java代码  收藏代码
  1. UserService userService= new UserServiceProxy("com.javapatterns.proxy.mytest.UserServiceImpl" );  
  2. userService.save();  

新加的构造方法看起来像一个工厂,外面传入不同实现类,都可以做代理。但是这样有两个明显的缺点:
1,代理工厂需要实现所有接口方法,当方法很多时,你会感到很毛的。
2,当你需要为另一个接口做代理时,需要重新写一个Proxy,并且工厂代码一句也不能少。
你要知道我们面对的是刁钻的程序员们,他们总是不能忍受编写冗余的代码。

从JDK1.3开始,java提供了InvocationHandler接口和Proxy类,用于创建动态代理。
jdk动态代理的强制条件:实现类必须实现一个或多个接口(CGLIB包就没有此限制)
InvocationHandler只提供一个方法
public Object invoke (Object proxy, Method method, Object[] args)。
当抽象接口调用方法时,会自动调用invoke里面的方法,给我们机会diy任何附加功能。
为了把工厂代码隐藏起来,并且能够很方便的扩展,我们需要设计一个代理工厂,这个工厂的具体设计目标是:
1,提供统一的工厂方法生成代理对象,比如通过传入原始类,或者其他参数,让工厂自动创造出代理类。
2,扩展不同的代理工厂时,可以很方便的提供加工代码。
一般做法是:
1,定义工厂公共接口,继承InvocationHandler接口
Java代码  收藏代码
  1. /** 
  2.  * 代理工厂总接口 
  3.  * 所有自定义代理工厂必须实现此接口 
  4.  * @author 杜云飞 
  5.  * 
  6.  */  
  7. public interface ProxyFactory extends InvocationHandler {  
  8.      public Object factory(Object targetObject,Object params);  
  9.      public Object getTargetObject();  
  10.      public Object getParams();  
  11. }  

ProxyFactory提供工厂方法factory,也继承了调用方法invoke。表明实现此接口可以完成创造代理类,并添加代理功能,
params是为了满足有些特殊的传参需求。
2,提供一个默认的代理工厂实现,最好满足大部分需求,再次看到常用的缺省模式。
Java代码  收藏代码
  1. /** 
  2.  * 默认的代理工厂,假如可以扩展,继承之,实现自己的invoke方法即可 
  3.  * @author duyunfei 
  4.  * 
  5.  */  
  6. public abstract class DefaultProxyFactory implements ProxyFactory {  
  7.   
  8.      private Object params;  
  9.        
  10.      private Object targetObject;  
  11.        
  12.      public Object factory(Object targetObject,Object params) {  
  13.            this. targetObject=targetObject;  
  14.            this. params=params;  
  15.             
  16.           Class cls = targetObject.getClass();  
  17.           //用set的原因是,接口列表不能重复   
  18.           Set<Class> listInterfaces =new HashSet<Class>();  
  19.           Class[] selfInterfaces=cls.getInterfaces();  
  20.            for(Class inter:selfInterfaces){  
  21.               listInterfaces.add(inter);  
  22.           }  
  23.           Class superClass=cls.getSuperclass();  
  24.            while(!superClass.getName().equals( "java.lang.Object")){  
  25.               selfInterfaces=superClass.getInterfaces();  
  26.                for(Class inter:selfInterfaces){  
  27.                    listInterfaces.add(inter);  
  28.               }      
  29.               superClass=superClass.getSuperclass();  
  30.           }  
  31.             
  32.           selfInterfaces=listInterfaces.toArray(selfInterfaces);  
  33.   
  34.            return Proxy.newProxyInstance(cls.getClassLoader(),  
  35.                    selfInterfaces, this);  
  36.      }  
  37.   
  38.   
  39.      public Object getTargetObject() {  
  40.            return targetObject;  
  41.      }  
  42.   
  43.   
  44.      public Object getParams() {  
  45.            return params;  
  46.      }  
  47.   
  48. }  

这里有几点值得注意,首先把此类设成abstract是因为,工厂方法factory是所有工厂的共通代码,再没有抽象的必要,所以实现之,而从ProxyFactory中获得的
invoke方法是对每个工厂都需要有不同的实现,所以继续抽象之。
另外,Factory方法中的代码按常理说,可以直接用下面的几句话搞定
Java代码  收藏代码
  1. Class cls = targetObject.getClass();  
  2. Proxy.newProxyInstance(cls.getClassLoader(),  
  3. cls.getInterfaces(), this);  

但是我这为了保证 即使出现一些类的层级比较复杂的情况下也能很好的生成代理类,把类的父类以上接口都纳入进来。
3,通过继承DefaultProxyFactory,实现自己的代理工厂。接下来我们一起实现一个日志处理的代理。
这个代理实现的功能是,在被代理方法前执行相应的日志输出,通过一个参数配置受控方法。
首先创建DaoLogProxy继承DefaultProxyFactory:
Java代码  收藏代码
  1. public class DaoLogProxy extends DefaultProxyFactory {  
  2.      private static Logger log = Logger. getLogger("");  
  3.        
  4.      public Object invoke(Object proxy, Method method, Object[] args)  
  5.                throws Throwable {  
  6.         String params=(String) this.getParams();  
  7.         String[] param=params.split( ":");  
  8.         String[] includeMethods=param[1].split( ",");  
  9.         boolean isInclude= false;  
  10.         for(String inmethod:includeMethods){  
  11.            if(method.getName().equals(inmethod)){  
  12.               isInclude= true;  
  13.           }  
  14.         }  
  15.         Object returnValue= null;  
  16.         if(isInclude){  
  17.            //是属于包含的方法  
  18.           String showargs= "";  
  19.            for(Object arg:args){  
  20.               showargs=showargs+arg+ "  ";  
  21.           }  
  22.          log .info("执行"+ this.getTargetObject().getClass()+ "的"+method.getName()+"方法,参数为" +showargs);  
  23.              returnValue=method.invoke( this.getTargetObject(), args);         
  24.         } else{  
  25.              returnValue=method.invoke( this.getTargetObject(), args);  
  26.         }  
  27.             
  28.            return returnValue;  
  29.      }  
  30.   
  31. }  

,这里我们通过自己的逻辑判断拦截方法。
然后写测试代码:
Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.          List<String> list=(List<String>) new DaoLogProxy().factory(new ArrayList<String>(),"includeMethods:add,get" );  
  3.          list.add( "test0");  
  4.          list.get(0);  
  5.          list.remove(0);  
  6.     }  

非常方便,控制台显示如下信息表示成功:
2012-8-21 16:44:45 java.util.logging.LogManager$RootLogger log
信息: 执行class java.util.ArrayList的add方法,参数为test0
2012-8-21 16:44:45 java.util.logging.LogManager$RootLogger log
信息: 执行class java.util.ArrayList的get方法,参数为0

假如你是客户端程序员,你期望怎样的代理配置?
我的期望是:本框架为注入类提供一个注解,配置代理工厂,让容器读取注解信息,调用工厂代码生成代理类,然后注入,这样我的客户端接口调用
的任何方法都会被自己扩展的代理类拦截。
好吧,新建Proxy注解,
Java代码  收藏代码
  1. /** 
  2.  * 通过此注解 可以为任意类创建jdk代理 
  3.  * @author duyf 
  4.  * 
  5.  */  
  6. @Retention(RetentionPolicy.RUNTIME)  
  7. @Target(ElementType.TYPE )  
  8. public @interface Proxy {  
  9.      /* 
  10.       * 必须指定自定义代理工厂 并且此工厂必须是ProxyFactory的子类 
  11.       */     
  12.      public Class proxyFactoryClass ();  
  13.             
  14.      /* 
  15.       * 传递任意参数 让自定义代理工厂自行处理 
  16.       */  
  17.       public String params() default "";  
  18.        
  19. }  

注解的目标“客户”是类(Type)。proxyFactoryClass强制规定提供的代理工厂实现ProxyFactory接口,这里我们可以继承DefaultProxyFactory就ok了。
打开容器类BeanContainer,加上Proxy处理代码
Java代码  收藏代码
  1. Proxy proxy = iocObj.getClass().getAnnotation(Proxy .class);  
  2. if (proxy != null ) {  
  3.  Object proxyFactoryClass = proxy.proxyFactoryClass().newInstance();  
  4.  String params=proxy.params();  
  5.  setObject = setProxyObject(proxyFactoryClass, setObject,params);  
  6. }  
  7.    
  8. public Object setProxyObject(Object proxyFactoryClass, Object obj,Object params) {  
  9.           checkProxy(proxyFactoryClass);  
  10.           ProxyFactory pf = (ProxyFactory) proxyFactoryClass;  
  11.            return pf.factory(obj,params);  
  12. }  
  13.   
  14. public void checkProxy(Object proxyFactoryClass) {  
  15.            if (!(proxyFactoryClass instanceof ProxyFactory)) {  
  16.                throw new RuntimeException( "所提供的代理类[" + proxyFactoryClass  
  17.                         + "]必须实现ProxyFactory接口" );  
  18.           }  
  19. }  

最后直接注入setObject到目标对象就行了。
测试一下:
1,创建一个测试接口UserService,UserServiceImpl实现接口,并用@Proxy注释
Java代码  收藏代码
  1. @Proxy(proxyFactoryClass=DaoLogProxy.class,params= "includeMethods:save,update" )  

2,在action里面配置自动注入:
Java代码  收藏代码
  1. @Autowired(iocClass = UserServiceImpl.class)  
  2. private UserService userService;  

并给出setter方法。
3,配置Action,调用save方法,会打印出
执行class $Proxy9的save方法。
有了Proxy注解,以后任何人想扩展自己的代理将易如反掌
具体实现代码请参考源码。

By 阿飞哥 转载请说明
腾讯微博:http://t.qq.com/duyunfeiRoom
新浪微博:http://weibo.com/u/1766094735
原文地址:http://duyunfei./blog/1773721

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多