异常的处理是每个java程序员时常面对的问题,但是很多人没有原则,遇到异常也不知道如何去处理,于是遇到检查异常就胡乱try...catch...一把,然后e.printstacktrace()一下了事,这种做法通常除了调试排错有点作用外,没任何价值。对于运行时异常,则干脆置之不理。
原因是很多开发者缺乏对异常的认识和分析,首先应该明白java异常体系结构,一种分层继承的关系,你必须对层次结构熟烂于心:
throwable(必须检查)
error(非必须检查)
exception(必须检查)
runtimeexception(非必须检查)
一般把exception异常及其直接子类(除了runtimeexception之外)的异常称之为检查异常。把runtimeexception以及其子类的异常称之为非检查异常,也叫运行时异常。
对于throwable和error,则用的很少,一般会用在一些基础框架中,这里不做讨论。
下面针对j2ee的分层架构:dao层、业务层、控制层、展示层的异常处理做个分析,并给出一般处理准则。
一、dao层异常处理
如果你用了spring的dao模板来实现,则dao层没有检查异常抛出,代码非常的优雅。但是,如果你的dao采用了原始的jdbc来写,这时候,你不能不对异常做处理了,因为难以避免的sqlexception会如影随形的跟着你。对已这种dao级别的异常,异常了你又能如何呢?与其这样胡乱try...catch...,囫囵吞枣消灭了异常不如让异常以另外一种非检查的方式向外传递。这样做好处有二:
1)、dao的接口不被异常所污染,假设你抛出了sqlexception,以后要是换了spring dao模板,那dao接口就不再抛出了sqlexception,这样,你的接口抛出异常就是对接口的污染。
2)、dao异常向外传播给更高层处理,以便异常的错误原因不丢失,便于排查错误或进行捕获处理。
这里还有一个设计上常常令人困扰的问题:很多人会问,那定义一个什么样的异常抛出呢,或者是直接抛出一个throw runtimeexception(e)? 对于这个问题,需要分场合,如果系统小,你可以直接抛出一个throw runtimeexception(e),但对于一个庞大的多模块系统来说,不要抛这种原生的非检查异常,而要抛出自定义的非检查异常,这样不但利于排错,而且有利于系统异常的处理,通常针对每一个模块,粗粒度的定义一个运行时dao异常。比如:throw new modelxxxdaoruntimeexception(".....",e),对于msg信息,你可写也可不写,根据需要灵活抛出。
这里常见一个很愚昧的处理方式,为每个dao定义一个异常,呵呵,这样累不累啊,有多大意义,在service层中调用时候,如果要捕获,还要捕获出一堆异常。这样致命的问题是代码混乱,维护困难,阅读也困难,dao的异常应该是粗粒度的。
二、业务层异常处理
习惯上把业务层称之为service层或者服务层,service层的代表的是业务逻辑,不要迷信分太多太多层有多大好处,除非需要,否则别盲目划分不必要的层,层越多,效率越差,根据需要够用就行了。
service接口中的每个方法代表一个特定的业务,而这个业务一定是一个完整的业务,通常会看到一些傻x的做法,数据库事务配置在service层,而service的实现就是dao的直接调用,然后在控制层(action)中,调用了好多service去完成一个业务,你气得已经无语了,低头找砖头去!!!
搞明白以上两个问题后再回过头看异常怎么处理,service层通常依赖dao,而service层的通常也会因为调用别的非检查异常方法而必须面对异常处理的问题,这里和dao层又有所不同,彼一时,此一时嘛!
一般来说一个小模块对应一个service,当然也许有两个或多个,针对这个模块的service定义一个非检查异常,以应付那些不可避免的异常检查,这个自定义异常可以简单的命名为xxxserviceruntimeexception,将捕获到的异常顺势转译为非检查异常后抛出。我喜欢这么做,因为前台是j2ee应用,前台是web页面,它们的struts2等框架会自动捕获所有service层的异常,并把异常交给开发者去自由处理。
但是还有一种情况,由于一些特殊的限制,如果某个异常一旦发生,必须做什么什么处理,而这种处理时硬性要求,或者调用某个service方法,必须检查处理什么异常,也可以抛出非检查的自定义异常,往往出现这种情况的是政治原因。不推崇这种做法,但也不排斥。
总之,对于接口,尽可能不去用异常污染她!
三、控制层异常
控制层说的简单些就是常见的action层,主要是控制页面请求的处理。控制层通常都依赖于service层,现在比较流行的框架对控制层做得都相当的到位,比如struts2、springmvc等等,他们的控制层框架会捕获业务层的所有异常,并在控制层中声明可能抛出exception,因此控制层一般不处理什么异常。
如果是控制层中因为调用了一些非检查异常的方法,比如io操作等,可以简单处理下异常,保证流的安全,这才是目的。
四、显示层异常处理
对于页面异常,处理的方式多种多样,一是不处理异常,一旦异常了,页面就报错。二是定义出错页面,根据异常的类型以及所在的模块,导航到出错页面。
一般来说,出错页面是更友好的做法。
另外还有特殊的处理方式,展示页面的模板可以捕获异常,并根据情况将异常信息铺到相应的位置,这样就更友好了,不过复杂度较高。
怎么处理,就看需要了。
五、总结
1)、对于异常处理,应该从设计、需要、维护等多个角度综合考虑,有一个通用准则:千万别捕获了异常什么事情都不干,这样一旦出现异常了,你没法依据异常信息来排错。
2)、对于j2ee多层架构系统来说,尽可能避免(因抛出异常带来的)接口污染。
总的来看 java ee 5 的标注开发方式开来是得到了大家的认可了.
@service 相当于定义 bean, 自动根据 bean 的类名生成一个首字母小写的 bean
@autowired 则是自动注入依赖的类, 它会在类路径中找成员对应的类/接口的实现类, 如果找到多个, 需要用 @qualifier("chineseman") 来指定对应的 bean 的 id.
一定程度上大大简化了代码的编写, 例如一对一的 bean 映射现在完全不需要写任何额外的 bean 定义了.
下面是代码的运行结果:
man.sayhello()=抽你丫的
simpleman said: hi
org.example.englishman@12bcd4b said: fuck you!
代码:
beans.xml
xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
<context:component-scan base-package="org.example"/>
beans>
测试类:
import org.example.iman;
import org.example.simpleman;
import org.springframework.context.applicationcontext;
import org.springframework.context.support.classpathxmlapplicationcontext;
public class springtest {
public static void main(string[] args) {
applicationcontext ctx = new classpathxmlapplicationcontext("beans.xml");
simpleman dao = (simpleman) ctx.getbean("simpleman");
system.out.println(dao.hello());
iman man = (iman) ctx.getbean("usman");
system.out.println(man.sayhello());
}
}
自动探测和注入bean的类:
package org.example;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.qualifier;
import org.springframework.stereotype.service;
@service
public class simpleman {
// 自动注入名称为 man 的 bean
@autowired(required = false)
@qualifier("chineseman")
//@qualifier("usman")
private iman man;
/**
* @return the man
*/
public iman getman() {
return man;
}
/**
* @param man the man to set
*/
public void setman(iman man) {
this.man = man;
}
public string hello() {
system.out.println("man.sayhello()=" man.sayhello());
return "simpleman said: hi";
}
}
一个接口和两个实现类:
package org.example;
/**
* 抽象的人接口.
* @author beansoft
* @version 1.0
*/
public interface iman {
/**
* 打招呼的抽象定义.
* @return 招呼的内容字符串
*/
public string sayhello();
}
package org.example;
import org.springframework.stereotype.service;
/**
* 中国人的实现.
* @author beansoft
*/
@service
public class chineseman implements iman {
public string sayhello() {
return "抽你丫的";
}
}
package org.example;
import org.springframework.stereotype.service;
/**
* @author beansoft
* 美国大兵
*/
@service("usman")
// 这里定义了一个 id 为 usman 的 bean, 标注里面的属性是 bean 的 id
public class englishman implements iman {
public string sayhello() {
return this " said: fuck you!";
}
}
default-autowire="x"
x有4个选择:byname,bytype,constructor和autodetect 我感觉byname和bytype用的多点 1. byname: service.java
applicationcontext.xml
cn.hh.spring.dbcpsource实现了source接口
结果相同。 2. bytype: service.java同上 applicationcontext.xml
同样没有配置setsource,autowire改成 "bytype",配置文件会找实现了source接口的bean,这里 cn.hh.spring.dbcpsource 实现了source接口,所以自动装配,如果没找到则不装配。 3. constructor: 试图在容器中寻找与需要自动装配的bean的构造函数参数一致的一个或多个bean,如果没找到则抛出异常。 4. autodetect: 首先尝试使用constructor来自动装配,然后再使用bytype方式。 |
(一)新建一个java bean(hellobean.java)java 代码
- package chb.demo.vo;
- public class hellobean {
- private string helloworld;
- public string gethelloworld() {
- return helloworld;
- }
- public void sethelloworld(string helloworld) {
- this.helloworld = helloworld;
- }
- }
(二)构造一个配置文件(beanconfig.xml)
xml 代码
- xml version="1.0" encoding="utf-8"?>
- >
- <beans>
- <bean id="hellobean" class="chb.demo.vo.hellobean">
- <property name="helloworld">
- <value>hello!chb!value>
- property>
- bean>
- beans>
(三)读取xml文件
1.利用classpathxmlapplicationcontextjava 代码
- applicationcontext context = new classpathxmlapplicationcontext("beanconfig.xml");
- hellobean hellobean = (hellobean)context.getbean("hellobean");
- system.out.println(hellobean.gethelloworld());
2.利用filesystemresource读取java 代码
- resource rs = new filesystemresource("d:/software/tomcat/webapps/springwebdemo/web-inf/classes/beanconfig.xml");
- beanfactory factory = new xmlbeanfactory(rs);
- hellobean hellobean = (hellobean)factory.getbean("hellobean");\
- system.out.println(hellobean.gethelloworld());
值得注意的是:利用filesystemresource,则配置文件必须放在project直接目录下,或者写明绝对路径,否则就会抛出找不到文件的异常
这里介绍两种技术:利用spring读取properties 文件和利用java.util.properties读取(一)利用spring读取properties 文件我们还利用上面的hellobean.java文件,构造如下beanconfig.properties文件:properties 代码
- hellobean.class=chb.demo.vo.hellobean
- hellobean.helloworld=hello!chb!
属性文件中的"hellobean"名称即是bean的别名设定,.class用于指定类来源。然后利用org.springframework.beans.factory.support.propertiesbeandefinitionreader来读取属性文件java 代码
- beandefinitionregistry reg = new defaultlistablebeanfactory();
- propertiesbeandefinitionreader reader = new propertiesbeandefinitionreader(reg);
- reader.loadbeandefinitions(new classpathresource("beanconfig.properties"));
- beanfactory factory = (beanfactory)reg;
- hellobean hellobean = (hellobean)factory.getbean("hellobean");
- system.out.println(hellobean.gethelloworld());
(二)利用java.util.properties读取属性文件比如,我们构造一个ipconfig.properties来保存服务器ip地址和端口,如:properties 代码
- ip=192.168.0.1
- port=8080
则,我们可以用如下程序来获得服务器配置信息:java 代码
- inputstream inputstream = this.getclass().getclassloader().getresourceasstream("ipconfig.properties");
- properties p = new properties();
- try {
- p.load(inputstream);
- } catch (ioexception e1) {
- e1.printstacktrace();
- }
- system.out.println("ip:" p.getproperty("ip") ",port:" p.getproperty("port"));