进入JavaEE阶段后,我们的实体类(Model)都是以一个JavaBean的形式出现的,在使用框架等可以实现属性值的自动注入。
先说下什么是JavaBean,其实就是一个普通的类,但他有一些规范约束:
- Ⅰ提供一个公有的无参构造函数.
- Ⅱ需要被序列化并且实现了java.io.Serializable接口.
- Ⅲ该类的属性一般是私有(private)修饰的.
- Ⅳ可能有一系列的公有的"getter"或"setter"的访问器方法.
- Ⅴ该类是一个公有类,并且用package语句声明属于某一个包.
划重点,要求属性值是被private修饰的,因此如果外界想要读写该属性,就必须通过getter/setter来操作,所以我们一般都会给JavaBean的属性生成对应的getter和setter。
那么今天这篇文章想讨论的是什么呢?
其实是关于属性命名的问题。是一个小细节,但也是一个新手坑。
我们知道,在Java中,遵循的变量命名规则是驼峰命名法,即变量名若由多个单词词根组成,那么变量名的首字母小写,遇到第二个单词词根时,该单词的首字母大写,之后小写…以此类推。
如:假设有个JavaBean的属性为name,那么它的getter的命名应该为:getName()。
别看这小小的命名规范没啥,到了JavaEE阶段,很多的框架对JavaBean的属性值的读写操作,都跟这有关。
举个栗子:
这几天在做项目的时候,在jsp页面用了EL表达式获取request域的page对象,想操纵下他的一个私有属性PageItems,如下:
- 先上JavaBean代码:
注意,划线部分的属性命名没有遵守驼峰命名法,第一个字母大写了。
然后在该类中还提供了对应的getter/setter,如下: - 在Servlet中将page对象实例存储进了request域中,并请求转发到了jsp页面
protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
Integer pageNo = WebUtils.parseStringToInteger(request.getParameter("pageNo"), 1);
Integer pageSize = WebUtils.parseStringToInteger(request.getParameter("pageSize"), Page.PAGE_SIZE);
// 调用BookService获取本页的Page对象
Page<Book> page = bookService.page(pageNo, pageSize);
page.setUrl("manager/bookServlet?action=page");
// 保存该page对象进request域
request.setAttribute("page", page);
// 请求转发到jsp中
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request, response);
}
以上代码,只需要关注有写注释的三条语句,分别是:获取page对象实例,存储进request域,将请求转发到jsp页面。
- 在jsp页面中用EL表达式,企图获取该page对象的PageItems属性
- 一运行,诶好家伙,挂掉了…
- 运行界面直接空白,查看下后台
- 后台打印了出错报告
- 运行报错的原因,竟然是因为,找不到PageItems属性!!!博主那时候懵了一会,不知道怎么的,第一反应就是把EL表达式中的属性名的首字母改成小写,然后重新运行,结果如下:
- 成功运行了…玄乎
并且,此时我并没有把JavaBean中的属性名也改成小写开头,也就是说,JavaBean中的属性名仍然是PageItems。
总结:
怎么说呢,就感觉还是要遵循Java的代码规范的。关于这个例子的解释,我个人理解如下:
首先,EL表达式获取Bean对象的属性,是通过调用该Bean对象的getXXX()来获取值的,其中的XXX是属性名。(ps:该属性值,甚至可以不存在!)
那么,对于一个规范的JavaBean的属性来说,如name,它所对应的getter是getName();如果不遵循规范,写成了Name,那么它对应的getter应该是什么呢?服务器不清楚。
接着,EL表达式中写bean对象.name,会去找该bean对象的getName();但是,如果写成了bean对象.Name,就无法找到对应的getter了,所以这也是服务器报错说找不到该属性的原因。
血的教训啊兄弟们!一定要遵守代码书写规范!
再来一个栗子:
做这个项目的时候呢,用了一下第三方的jar包,引入了一个类BeanUtils,其作用是自动将url请求中的参数列表自动封装进对应的bean对象实例中,就减少了我们自己从request对象中获取参数值后再调用bean对象的setter的繁琐操作。
/**
* 封装请求参数进模型Bean中
* @param obj 模型Bean对象
* @param map 请求参数的map集合
* @param <T> 待封装的Bean对象的具体类
* @return 返回一个已封装好请求参数的bean对象实例
*/
public static <T> T copyMapToBean(T obj, Map map){
try {
BeanUtils.populate(obj, map);
} catch (Exception e) {
e.printStackTrace();
}
// BeanUtils的注入,是在原有的bean对象中进行注入的
return obj;
}
根据前面提到的,无论是手动注入bean对象的属性值,还是依靠第三方框架帮我们自动注入,其底层都是调用了该bean对象的setter来对属性进行值的注入。
那么,开始实验:
- 首先,还是利用了上述的Page类,这次测试的属性值是PageTotal,其他的属性值遵守Java代码编写规则,如下:
仍然在该类中提供所有属性的getter和setter方法。
并且提供了重写后的toString():
- 接着,从浏览器请求一个Servlet,并在请求中携带参数列表发送到服务器:
- 在该Servlet中接收请求参数并打印,同时调用copyMapToBean()对bean对象的其他属性值进行自动注入,打印该bean对象:
/**
* 分页功能
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
// 省略了一些流程处理 ...
System.out.println("pageSize = " + request.getParameter("pageSize"));
System.out.println("PageTotal = " + request.getParameter("PageTotal"));
Page<Book> bookPage = WebUtils.copyMapToBean(new Page<Book>(), request.getParameterMap());
System.out.println("注入后的page对象:" + bookPage);
// 省略了一些流程处理 ...
}
结果如下:
从运行结果可以看到,首先,Servlet中确实是接收到了两个参数,分别是pageSize和PageTotal。但在调用第三方类进行Bean对象的自动注入后,却发现被注入值的对象的属性中,只有pageSize成功注入,而PageTotal并没有注入成功。而实际的Bean对象实例中确确实实存在了该PageTotal属性,并且也有对应的setter。
- 将浏览器地址栏中的请求参数PageTotal修改成pageTotal,其他因素不变,再次访问服务器,查看运行结果:
神奇的事情发生了,注入成功了!!!第二行的输出结果之所以为null,是因为我没有改Servlet中获取请求参数的键名,仍然是"PageTotal",所以获取到的是null。但是,看注入后的结果,可以看到已经成功将pageTotal注入到bean对象中了,并且,注意了!此时的bean对象中的属性值其实是PageTotal,而不是pageTotal。
好了,做下这个栗子的总结:
- 首先,如果是不遵循Java命名规范的变量,即使是有该变量值,也无法注入成功。
- 其次,以setPageTotal()为例,其所对应的变量名为pageTotal,而不是PageTotal,这也就是为什么第一次注入会失败,而第二次注入成功的原因。在服务器看来,一个bean对象中的所有setter,都有其对应的属性存在,而该属性的名字就是setXXX()脱去set后的XXX的第一个字母改为小写,即为该setter所对应操纵的属性的属性名。
最后,做下全文总结:
- 这次举的两个例子,第一个是通过调用JavaBean的getter来操作bean对象的属性;第二个例子是通过调用setter来操作bean对象的属性。想说明的点,其实只有一个,就是在现成的框架或jar包中,对于一个JavaBean而言,getXXX()和setXXX()对应所关联的属性呢,其实都是以<脱去get/set后的XXX的第一个字母改为小写>为命名的属性。所以,无论是调用bean对象的getter还是setter,都要求调用者指定该属性为<脱去get/set后的XXX的第一个字母改为小写>为命名的属性,即使该属性与bean对象中真实存在的属性名不一致也无妨。
- Java变量命名规范为:驼峰命名法,且第一个字母必为小写,生成对应的getter和setter遵循驼峰命名法。
- 一定要遵守Java的各种书写规范啊兄弟们,可以避开很多坑的呜呜呜!
==========全文仅为个人观点,如有不妥,还请评论区指出。==========