宇凡's profileBreak the LoopPhotosBlogLists Tools Help

Blog


    June 23

    ORM单元测试技巧

    A variety of bugs can lurk in the O/R mapping, including the following:

    • Missing mapping for a field
    • References to nonexistent tables or columns
    • Database constraints that prevent objects from being inserted, updated, or deleted
    • Queries that are invalid or that return the wrong result
    • Incorrect repository implementation

    http://www.theserverside.com/tt/articles/article.tss?l=PersistentDomain

    May 22

    一段使用正则表达式提取中文的代码

      String f = "abakdia中文akakji ji 你好 釹號";
      Pattern p = Pattern.compile("[\\u4E00-\\u9FFF]+");
      Matcher matcher = p.matcher(f);
      while (matcher.find()) {
       System.out.println(matcher.group());
      }
    May 18

    使用AOP及Annotation实现半自动实质化Hibernate Entity

    熟悉Hibernate的朋友应该都知道,Hibernate支持对集合的懒加载(最新版也支持属性的懒加载),但这样的懒加载在一些分层的架构会带来一些问题,如果只是在Web中应用,可以通过使用OSIV来保持Hibernate Session直到view渲染完毕,但在分布式的应用中,这显然不是一个合理的方案,甚至在一些严禁的web应用中,我们也不应该使用OSIV(原因可参看Without EJB中相关章节),这时候,又要依赖懒加载提高性能,又想将Hibernate Entity传递到Service层之外的时候,就会出现很多代码来做一件事情:
    Hibernate.initialize(collection), 这样的与业务逻辑无关的代码实在是一种臭味。
    我想到的一个办法是结合使用AOP和Annotation来半自动化的完成这件工作:
    1、创建一个指导AOP拦截器工作的Annotation,主要用来告诉拦截器应该实质化哪一部分属性
    /**
     * @author Yufan Shi 
     */
    package yufan.annotation;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    /**
     * Use this Annotation to mark sevice methods which wanna its returned result to
     * be materialized(initialize lazy load hibernate collections).
     *
     * <p>
     * <b>e.g:</b>
     *
     * @Materialize(select = { "suburbs" }) <br >
     * @Materialize(select = { "#root.{suburbs}" })
     *                     </p>
     *
     * Place this annotation in service interfaces is recommended.
     *
     * @author Yufan Shi
     * @see yufan.aop.interceptor.MaterializeEntityInterceptor
     */
    @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target( { java.lang.annotation.ElementType.METHOD })
    public @interface Materialize {
     /**
      * <p>
      * OGNL expressions to select properties from target entity.<br>
      * For example:<br>
      * one method like this was marked by this annotation:<br>
      * <b>City getCity(String id);</b><br>
      * and City got a suburbs list which is lazy loaded. By using select
      * {"suburbs"}, you are telling
      * yufan.aop.interceptor.MaterializeEntityInterceptor to materialize this
      * collection after getCity method returned.
      *
      * another method like this:<br>
      * <b>List findAllCity();</b><br>
      * and City got a suburbs list which is lazy loaded. By using select
      * {"#root.{suburbs}"}, you are telling
      * yufan.aop.interceptor.MaterializeEntityInterceptor to materialize suburbs
      * collection of every city of the list returned by findAllCity method.
      * The same thing works works both on City[] and Map.
      * </p>
      */
     String[] value();
    }
     
    2、创建一个拦截器,来拦截业务层方法,这些方法应该返回Hibernate Entity,以及Entity的数组、List或者Map.拦截器拦截到方法后,会考察该方法是否有@Materialize注解,并且从中得到要实质化的属性OGNL表达式,根据该表达式结合OGNL,得到一批要实质化的对象,然后使用Hibernate.initialize一一实质化。
    /**
     * @author Yufan Shi
     */
    package yufan.aop.interceptor;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    import ognl.Ognl;
    import ognl.OgnlContext;
    import ognl.OgnlException;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.hibernate.Hibernate;
    import org.hibernate.SessionFactory;
    import yufan.annotation.Materialize;
    /**
     * MaterializeEntityInterceptor
     *
     * This interceptor will Materialize your hibernate entity's lazy loaded
     * collection properties returned by methods intercepted by it
     *
     * @author Yufan Shi (yufanshi@gmail.com)
     * @see yufan.annotation.Materialize
     */
    public class MaterializeEntityInterceptor implements MethodInterceptor {
     private SessionFactory sessionFactory;
     public SessionFactory getSessionFactory() {
      return sessionFactory;
     }
     public void setSessionFactory(SessionFactory sessionFactory) {
      this.sessionFactory = sessionFactory;
     }
     /*
      * (non-Javadoc)
      *
      * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
      */
     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
      Materialize materializeConfig = methodInvocation.getMethod()
        .getAnnotation(Materialize.class);
      Object r = methodInvocation.proceed();
      if (materializeConfig != null) {
       for (String expression : materializeConfig.value()) {
        for (Object o : selectObjects(r, expression)) {
         Hibernate.initialize(o);
        }
       }
      }
      return r;
     }
     private List selectObjects(Object result, String expression) {
      List<Object> list = new ArrayList<Object>();
      try {
       OgnlExpression ognl = new OgnlExpression(expression);
       Object value = ognl.getValue(new OgnlContext(), result);
       if (expression.endsWith("}")) {
        list.addAll((Collection<? extends Object>) value);
       } else {
        list.add(value);
       }
      } catch (OgnlException e) {
       e.printStackTrace(System.err);
      }
      return list;
     }
    }
    class OgnlExpression {
     private Object expression;
     public OgnlExpression(String expressionString) throws OgnlException {
      super();
      expression = Ognl.parseExpression(expressionString);
     }
     public Object getExpression() {
      return expression;
     }
     public Object getValue(OgnlContext context, Object rootObject)
       throws OgnlException {
      return Ognl.getValue(getExpression(), context, rootObject);
     }
     public void setValue(OgnlContext context, Object rootObject, Object value)
       throws OgnlException {
      Ognl.setValue(getExpression(), context, rootObject, value);
     }
    }
    3、在Service接口中需要实质化返回值的方法声明上放置@Materialize注解,示例如:
    package test.business.service;
    import java.util.List;
    import java.util.Map;
    import test.entity.City;
    import yufan.annotation.Materialize;
    public interface IEchoService {
     String echo(String msg);
     @Materialize( { "suburbs" })  // 实质化city的suburbs属性
     City getRandomCity();
     @Materialize( { "#root.{suburbs}" }) // 实质化city数组中每个city元素的suburbs属性
     City[] findCities();
     @Materialize( { "#root.{suburbs}" })// 实质化city列表中每个city元素的suburbs属性
     List findCityList();
     @Materialize( { "#root.{suburbs}" })// 实质化city图value集合中每个city元素的suburbs属性
     Map findCityMap();
    }
     
     
    4、在Spring中定义拦截器及其他
    <bean id="materializeIntercpetor"
      class="yufan.aop.interceptor.MaterializeEntityInterceptor"
      autowire="byName" />
     <bean name="echoServiceTarget"
      class="test.business.service.EchoServiceImpl" autowire="byName" />
     <bean id="echoService"
      class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="proxyInterfaces">
       <value>test.business.service.IEchoService</value>
      </property>
      <property name="interceptorNames">
       <value>transactionInterceptor,materializeIntercpetor,echoServiceTarget</value>
      </property>
     </bean>
    5、如果已经使用OSIV,去掉OSIV在web.xml中的定义,然后尝试在试图中遍历suburbs集合,就会发现不再出现LazyInitializeException了
     
    这个功能能减少很多的重复的业务无关的代码,但是还是由于一点不足,就是必须注解在接口中,这个感觉有点侵扰了接口的单纯性。但我尝试在实现类里加注解却发现无法在interceptor里的通过反射得到注解。
     

    从唯一字符串得到散列性较好的文件路径

    在一些大型系统中,会经常在硬盘上存储文件,比如存储用户的头像,如果将所有文件存在一个目录则可能造成文件系统性能低下,甚至崩溃。比较好的做法是利用一定的算法根据唯一的值得到一个散列性较好的文件路径,将文件存放在该路径。
    以下这个类使用md5得到一串32字节的hash字符串,用该字符串的第一个字符作为根目录,然后将字符串2个一组切分,最后两个字符串作为文件名。
    这样的情况下,可以将所有的文件分布在16个大目录下的最底层,每个目录最多只有256个子目录。
    可以得到类似这样的文件路径:
    e\e4\e6\9e\5b\1e\65\48\e7\49\61\51\9b\45\31\d2\1c.jpg
     
     
    /**
     * @author Yufan Shi
     */
    package com.jongo.account.service.impl;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import com.jongo.account.service.MassFilePathSelector;
    /**
     * 使用MD5算法得到散列性好的文件路径
     *
     * @author Yufan Shi
     *
     */
    public class MD5MassFilePathSelector implements MassFilePathSelector {
     private MessageDigest md5;
     public MD5MassFilePathSelector() {
      try {
       md5 = MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException e) {
       throw new RuntimeException(e);
      }
     }
     /*
      * (non-Javadoc)
      *
      * @see com.jongo.account.service.MassFilePathSelector#select(java.lang.String,
      *      java.lang.String, java.lang.String)
      */
     public String select(String uniqueName, String ext) {
      byte[] hex_digest = new byte[32];
      md5.update(uniqueName.getBytes());
      bytesToHex(md5.digest(), hex_digest);
      
      String digest = new String(hex_digest);
      StringBuffer sb = new StringBuffer();
      sb.append(digest.substring(0, 1));
      for (int i = 0; i < digest.length(); i += 2) {
       sb.append("/");
       sb.append(digest.substring(i, i + 2));
      }
      sb.append("." + ext);
      return sb.toString();
     }
     /**
      * Turn 16-byte stream into a human-readable 32-byte hex string
      */
     private static void bytesToHex(byte[] bytes, byte[] hex) {
      final char lookup[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
        '9', 'a', 'b', 'c', 'd', 'e', 'f' };
      int i, c, j, pos = 0;
      for (i = 0; i < 16; i++) {
       c = bytes[i] & 0xFF;
       j = c >> 4;
       hex[pos++] = (byte) lookup[j];
       j = (c & 0xF);
       hex[pos++] = (byte) lookup[j];
      }
     }
    }
     

    比较简单且质量较高的图像缩放算法

         // 构造Image对象
         BufferedImage src = javax.imageio.ImageIO.read(buddyIcon
           .getInputStream());
         // 得到缩小后的头像
         Image scaled = src.getScaledInstance(this.iconWidth,
           this.iconHeight, Image.SCALE_SMOOTH);

         // 绘制缩小/放大后的图
         BufferedImage thumbnail = new BufferedImage(this.iconWidth,
           this.iconHeight, BufferedImage.TYPE_INT_RGB);

         thumbnail.getGraphics().drawImage(scaled, 0, 0, null);

         File out = new File("thumbnail.jpg");

         // 使用JPEG压缩,质量为100%
         // ? 这里依赖了Sun的包,要不要使用第三方的包
         JPEGImageEncoder encoder = JPEGCodec
           .createJPEGEncoder(new FileOutputStream(out));
         JPEGEncodeParam param = encoder
           .getDefaultJPEGEncodeParam(thumbnail);
         param.setQuality(1.0f, false);

         // JPEG编码
         encoder.encode(thumbnail);