wiki:youhua/2017_1

1.java类中EnvUtils.getEnv()使用优化

在servlet处理请求时,EnvFilter中会调用EnvUtils.getEnv()实例化Env,并对实例属性进行设置,这些都是必要步骤,在使用EnvUtils.getEnv()时如果跳过这些步骤,调用env中一些需要用到request、response、servletContext属性的方法时会出现NullPointerException错误。

//evn过滤器代码 org.gelivable.web.EnvFilter : 62
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        ......
        Env env = EnvUtils.getEnv();
        envMap.put(env, "");
        try {
            //设置了env的属性
            env.setRequest((HttpServletRequest) request);
            env.setResponse((HttpServletResponse) response);
            env.setServletContext(servletContext);

            request.setAttribute("env", env);

            chain.doFilter(request, response);

        } 
        ......
    }

在聚超值项目中,entity类(service偶尔也会出现)中经常会用EnvUtils.getEnv().getBean()获取service或dao类实例的代码。

public class User {
        ......
	public UserPurchasingInfo getInfo() {
		......
		UserPurchasingInfo info = EnvUtils.getEnv().getBean(GeliDao.class).find(UserPurchasingInfo.class, this.userId);
                //或者 UserPurchasingInfo info = EnvUtils.getEnv().getBean(UserPurchasingInfoService.class).find(this.userId);
		......
	}
        ......
}

这种代码至少在三种情况运行用会出现NullPointerException错误:
1.在单元测试中运行。
2.不依赖于servlet的定时任务中运行。
3.在处理servlet的请求时,通过代码发起的新线程中简单的调用EnvUtils.getEnv().getBean(xxx.class)获取某个类的实例会和1、2点一样出现错误。

原因
EnvUtils?.getEnv()用到了线程单例,当线程第一次调用时threadLocal会实例一个Env对象,之后这个线程再调用都只返回这个对象,而且只能被这个线程调用。
1、2点的错误原因比较明显,原因是在代码执行的时候没有经过过滤器,EnvUtils?.getEnv()只是把Env实例化了,env对象里的servletContext属性是空的,没有进行设置,调用的时候就报空指针异常了。
第3点报错的原因是因为当在处理请求过程中启用了新的线程,新线程调用EnvUtils.getEnv()时,threadLocal就会新实例一个Env对象,这个实例和1、2一样,没有设置env里的servletContext属性,调用env.getBean(xxx.class)的时候就会报空指针异常。

public class EnvUtils {
    public static Env getEnv() {
        return threadLocal.get();
    }

    public static void removeEnv() {
        threadLocal.remove();
    }

    //通过EnvUtils获取的Env实例是线程单例的,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    private static final ThreadLocal<Env> threadLocal = new ThreadLocal<Env>() {
        @Override
        protected Env initialValue() {
            return new Env();
        }
    };

}


优化方案
将非Controller的java类中的EnvUtils.getEnv().getBean(GeliDao?.class)改为GeliUtils.getDao(),
将EnvUtils.getEnv().getBean(xxxService.class)改为SpringCtxUtils.getBean(xxxService.class)。(SpringCtxUtils在cn.pconline.best.util目录下)

2.异常处理优化

项目中经常有异常被捕获后没有日志也没有打印,如果在程序执行过程中出现问题,因为看不到错误信息,就需要一步步来排查。

    private String getMoreTopicText(long topicId, int type) {
        try {
          ......
        } catch (Exception e) {
        }
        return "";
    }

还有些代码,捕获之后不仅用日志记录了异常而且还会用控制台打印异常,用日志记录异常后控制台就会打印异常信息,不需要调用printStackTrace()重复打印。

    private String getMoreTopicText(long topicId, int type) {
        try {
          ......
        } catch (Exception e) {
           e.printStackTrace();
           logger.error("xxx error :", e);
        }
        return "";
    }

优化方案

对于一些后续有判断处理不影响程序正确执行的异常,调用log4j的error(Object message)或者info(Object message)进行简单处理,
对于可能造成程序中断或数据错误的异常,用log4j的error(Object message, Throwable t)进行处理,
对于catch里即有日志记录又有控制台打印的异常,删掉控制台打印保留日志。

3.代码格式优化

部分实体类的常量、属性、方法混合在一起比较杂乱,使用的时候容易看漏。

优化方案

将代码按照公有常量、属性、方法的次序进行调整。有些实体中的方法不仅仅是属性的get、set方法,还有些方法无关实体属性是在页面展示数据时调用的,这些方法放到了最后,相似特征的方法会放在一起。

public class Topic {
   //公有的常量放在类的最前面,私有的可以放在使用的方法上面
   public static final int AUDIT_STATUS_WAIT = 0;//待审
   ......

   //属性
   private long topicId;
   ......

   //属性的get set方法
   public long getTopicId() {
        return topicId;
   }

   public void setTopicId(long topicId) {
       this.topicId = topicId;
   }
   ......

   //还有些get方法无关实体属性,是为了jsp里面方便el调用。这类方法放在最后,有相似特征的放在一起。
   public Mall getMall() {
       return ....
   }
}