研究JDK源代码,是从JAVA新手走向高手的必经之路。阅读JDK源码的方法如下:
- 在eclipse中新建一个JAVA工程
- 将JDK安装根目录里的src.zip解压到JAVA工程目录下
- 回到eclipse,右键点击工程,选择refresh
- 此时,除了一堆编译错误外,你应该可以开始阅读JDK所有源码
研究JDK源代码,是从JAVA新手走向高手的必经之路。阅读JDK源码的方法如下:
- 在eclipse中新建一个JAVA工程
- 将JDK安装根目录里的src.zip解压到JAVA工程目录下
- 回到eclipse,右键点击工程,选择refresh
- 此时,除了一堆编译错误外,你应该可以开始阅读JDK所有源码
此次项目基于SSH2框架(物业报修系统),为了避免忘记框架使用的步骤,我稍微记录一下注册模块的编写过程。
核心是Spring框架,它有IOC容器,可以方便地管理Struts2和Hibernate;Struts2因其MVC的架构,因而负责显示;Hibernate则是持久层,实现DAO。
下面是详细的步骤,根据时间先后一步一步来。
该文件告诉Tomcat,主页是哪个、Action交给谁处理、网站的编码格式、过滤拦截哪些URL、托管给Spring等。
1 |
|
所谓用户实体,其实是java bean,它内部有用户数据:
1 | /** |
Hibernate正是凭借它,才能将对象映射到数据库中去:
1 |
|
在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码,告诉Hibernate数据库的信息,映射文件的位置,将Hibernate的sessionFactory托管给Spring:
1 | <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> |
DAO层专门操作实体,比如增删改查一个学生等。
接口代码:
1 | /** |
实现的代码:
1 | /** |
在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:
1 | <!-- 将学生DAO托给Spring --> |
逻辑层原先放在Struts2控制层的Action中,但随着Action的日渐庞大,许多负责的逻辑代码需要分离出来单独作为逻辑层。
接口的代码:
1 | /** |
实现的代码:
1 | /** |
在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:
1 | <!-- 注册业务逻辑 --> |
到这里,我们就进入了Struts2的MVC世界。
- 控制层:Action属于MVC中的C;
- 模型层:Struts1中用java bean作为模型层,也就是MVC中的M,但该方案在Struts2中被废除,模型层的内容直接放入Action;
- 视图层:jsp的内容即是视图层;
Action最主要的方法是execute,在用户点击注册按钮时,会执行该方法:
1 | /** |
在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:
1 | <!-- 学生注册界面的Action --> |
由于Spring管理着Action,Struts2需要Action的时候,只能找Spring要。
在struts.xml中增加下面的代码代码:
1 | <action name="register" class="studentRegisterAction"> |
1 | <%@ page contentType="text/html;charset=utf-8"%> |
上面是就是一个完整的SSH2实现的注册模块,在运行之前必须先建立数据库,数据库的信息和建表命令都在上面。
写项目时,有一个方法需要返回两种类型的对象,所以我将两个空白的对象作为参数传给方法,在方法里修改。修改时,我没有一条条地填上空白对象的属性,而是贪图方便,将一个新对象赋值给空白对象,结果原来的那个空白对象仍旧是空白的。
java不同于C++,在参数传递中只有值传递,没有引用传递。在下面的方法声明里,repairOrder是一个RepairOrder对象的引用,student是一个Student对象的引用:
1 | public boolean getDetailOfRepairOrder(int repairOrderId, |
我将一个r和一个s传给上面的函数:
1 | RepairOrder r = new RepairOrder(); |
程序的结果是,r的name属性是test而不是newR test,为什么?
“物业报修系统”项目里有个图片上传
的功能模块,我花了近一个晚上理解原理和写出代码。项目框架是SSH2,而上传图片模块主要涉及到Struts2
,所以我剥离Spring和Hibernate的内容,只放出Struts2的代码。
(N : 在2015年的时候,我在Android端写过一个类似的模块,但当时我的做法是调用
Restful API
将图片直接上传到服务器,写的跑在客户端上,而现在写的代码跑在服务器上,两者有较大的区别)
主要步骤分别有Struts2配置文件的修改
、编写JSP代码
、编写后台Action处理
。
首先是web.xml
,它是TomCat服务器的配置文件,为了使得网站的默认页面是上传照片页面(default_upload_pic.jsp
),必须修改它:
1 |
|
接着是struts.xml,它配置了Struts2。在一个页面被提交到服务器后,如何进行处理、由谁处理和处理之后给浏览者返回什么内容
等都由它决定。负责处理图片上传的Action是com.ilovecl.test
包中的UploadPicAction
,它决定浏览者跳转到page1.jsp
或是page2.jsp
。此外,其中还定义了文件过滤器,不允许浏览者上传过大的文件:
1 |
|
JSP文件负责给浏览者显示内容,它提供一个文件选择框,浏览者选择图片后点击按钮便可上传(JSP是嵌入在HTML中的脚本,和javascript类似)。
近几周,出于课程要求,我在忙于写“物业保修系统”,该系统的MySQL数据库放在Aliyun上,而我写代码和调试的地点是不固定的,每次连接数据库时总会跳BUG:
(Access denied for user ‘root‘@’112.96.109.106’ (using password: YES))
112.96.109.106这个IP是不固定的,尤其是当我用手机当热点时(在前女友学校的图书馆里只能这么办),每次都会换一个IP,所以每次都要登录服务器去新增一个白名单。
具体的步骤是这样的:
1 | mysql -u root -p |
1 | GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' BY 'test' WITH GRANT OPTION; |
这样一来,以后我就可以随便地在任何一个喜欢的地方访问数据库了。
Java的Object对象有9个方法,其中的equals()
和hashCode()
在hashMap的实现里面起着比较重要的作用,我在研究hashMap的源码时就遇到了它们俩,此篇博文主要是为了记录它们之间的相爱相杀。
为了说明它们的关系,我们需要HashMap的背景知识。
HashMap的实现方式是数组链,不同的对象根据其哈希码(hashCode方法的返回值)找到对应的数组下标,然后存入数组。不同的对象有相同的哈希码时怎么办?这就由数组链中的链来解决了,相同哈希码的对象都放在同一条链上,该链的链头指向数组,进而形成数组链。
当第一个对象已经存入HashMap,第二个对象准备存入HashMap时,系统在查找到数组下标后若发现它们的hashCode相同(也就是冲突),会调用equals()来检查它们之间的关系,会有相应有以下两种处理方法:
如果它们的hashCode不相同,直接存入第二个对象。
现在假设有两个对象,它们的equals()相匹配,但hashCode()却不同,让我们好好分析一下当它们存入HashMap时会发生什么。
假设StringA和StringB是两个不同的对象,内容都是”hello,world”,equals()返回true,但hashCode()返回值不一样。我们把StringA和StringB当作Key,分别对应着ValueA和ValueB。
在StringA和ValueA已经存入HashMap后,我们尝试存入StringB和ValueB,因为hashCode不同,StringB和ValueB顺利地进入HashMap.
我们写一个查询:HashMap.get(“hello,world”),此时会发生什么呢?我们取回的究竟是ValueA还是ValueB?只有天知道。
或者换一下,我们写一个查询:HashMap.get(StringA),此时会发生什么呢?我们取回的究竟是ValueA还是ValueB?只有天知道。
再换一下,我们写一个查询:HashMap.get(StringB),此时会发生什么呢?我们取回的究竟是ValueA还是ValueB?只有天知道。
相信聪明的你已经看出了问题所在。所以我们常说,如果equals匹配,hashCode()一定要相同
,不然就有神奇的事情发生。
我在使用接口注入时,犯了个错误,项目运行时出现“**The requested resource (Servlet action is not available) is not available.”的提示。当我不经意地往控制栏里那么一瞅时,我发现了一条重要线索:“Failed to convert property value of type [com.sun.proxy.$Proxy1] to required type [com.ilovecl.news.service.impl.UsersManager] for property ‘usersManager’**”。
我之所以知道有这么一条线索,还得从Spring的注入方式说起。在很久很久以前,传说中著名的java开源框架Spring有三种不同的注入类型:接口注入
、属性(Setter)注入
和构造函数注入
。 当中的接口注入正是导致这个错误的罪恶之源,为何?请客官听我慢慢道来。
在项目里,我编写了个IUsersManager接口,里边的函数负责管理用户,我还编写了个UsersManager类(它实现了IUsersManager接口)。除了这两位,还存在着关键的第三位兄弟UsersAction类。
UsersAction类内部有一个UsersManage变量,正如代码中所示:
public class UsersAction extends DispatchAction {
// 注入的目标是实现了接口的类
private UsersManager usersManager;
public UsersManager getUsersManager() {
return usersManager;
}
public void setUsersManager(UsersManager usersManager) {
this.usersManager = usersManager;
}
当我放心地利用Spring的IOC特性,在配置文件中将UsersManager注入到UsersAction中时(内容如下),一开头提出的问题却出现了。
1 | <!-- usersManager将会被注入UsersAction --> |
我试图将一个实现了某个接口的类A直接注入到另一个类B中,这在Spring中是不允许的。我必须将B中的A换成接口,将UsersAction的UsersManager换成IUsersManager接口,更改后的代码如下:
public class UsersAction extends DispatchAction {
// 注入的目标是接口
private IUsersManager usersManager;
public IUsersManager getUsersManager() {
return usersManager;
}
public void setUsersManager(IUsersManager usersManager) {
this.usersManager = usersManager;
}
也就是说,一个实现了接口的类A,必须以接口的形式注入到目标B中,这样一来,目标B只需调用接口即可操作类A,这是接口注入的核心(遗憾的是,接口注入和属性注入的功能差不多,但接口使得项目的类数量变多,现如今不推崇使用接口注入)。
本文涉及2个表,券主表Receipt和发券记录表ReceiptIssueRecord,1个订单可以拥有多张券,1张券只会对应1个订单。
1 | <-- 券主表 --> |
1 | <-- 发券记录表 --> |
券主表
发券记录表
为了查出分布在2个表里的数据,我们会在sql语句的from子句里写上2个表名:
1 | select * from Receipt, ReceiptIssueRecord where Receipt.OrderId = ReceiptIssueRecord.OrderId |
结果:
为什么会这样的呢?我们对上面的查询语句做下修改,去掉where子句,看看有什么变化,拭目以待:
1 | select * from Receipt, ReceiptIssueRecord |
结果:
来自UC小编的震惊!去掉where子句,我们得到的是2个表的乘积(5 * 4 = 20),也就是笛卡尔乘积,总20条记录!此时,我们不难明白为何上面的sql查询结果只有3条,因为它限定了左边的orderId=右边的orderId。
开大脑洞想一想,假若线上券主表有1w记录,发券记录表有1K记录,我们这个sql语句,随随便便跑出来了1千万条结果!
所谓的笛卡尔乘积,实力过于强悍~我军无法与之抗衡,迅速撤退!
结论:在from子句里,多个表用逗号连接起来,结果=笛卡尔乘积。
(注:后面吃瓜群众会发现,这种多表查询,本质上是交叉连接cross join)
Join,根据多个表的列,从左到右,将多个表拼接起来。
Join分为几种,cross join,inner join,left join,right join,full join和join。
cross join即是第一节里多表查询的方式,查询语句:
1 | select * from Receipt cross join ReceiptIssueRecord; |
其结果是笛卡尔乘积,左表的每一条记录和右表的所有记录都做了连接,结果是极其可怕的。
(注:cross join 不能像其它join一样使用on关键字)
inner join相当于join,带上on关键字,可以在多表连接时过滤不符合条件的记录,而不必等到连接完成后再用where子句筛选,查询语句:
1 | select * |
结果:
inner join带上on关键字,左表和右表都必须符合on的限定条件,才会拿出来做拼接。
比如,上面的查询会从Receipt表中挑出orderId为5114049055891395的记录,再从ReceiptIssueRecord表中挑出orderId为5114049055891395的记录,2者拼接。
left join,也被称作left outer join。相比于inner join,它不要求左表和右表都符合on限定条件,而是把左表的行记录全部记录出来,然后按照on限定到右表查找,若找到则连接,若没有则用null拼接。
1 | select * |
结果:
right join,和left join相反,全部列出右表所有记录,再从左表查找。
1 | select * |
full join,是left join和right join的综合,全部列出左表和右表的记录,再针对左表的记录,到右表查,有则拼接无则null,接着针对右边记录,到左表查。
1 | select * |
注:理论上,full join的结果是left join和right join的和,不过我在实际操作的时候,sql报错了,有待进一步研究
on相对于join,就像是where相对于select。
在join的时候加上on限定,不会对表中所有数据进行连接,仅仅会挑选出符合限定条件的记录,这样就大大减少了查询成本,从千万级降到百千级,所以有人建议join查询时,尽量把条件都放在on里面。
如果同时使用on和where,则会在查询时根据on条件选择性连接,再根据where条件对连接结果进行筛选。
f8a7d13f8794a4c27a284ab50ef41bd5ac6e39d7
闲暇之余,读几本好书,不仅可以修心养性、陶冶情操,更可以开阔视野学到丰富的知识。所谓足不出户,便知天下事、读书百 遍,其义自见,说的也正是这个道理。回首往昔,我读过的书也不少,只可惜那时年少,不知所谓“积累”,如今既知,深感两者为云泥之别,故此,开此篇,记录 阅读书目。倘若临时有忆,亦会翻出记忆深处被掩盖的想法。革命尚未成功,同志仍需努力,谨此。
写的比较好的一本书,书中传主专注学术、追求科学的精神让人印象深刻。
再回首现代,一心向钱的人们仿佛只是专注着路边的水井盖,流行文化里充斥着所谓“男女神”、“壕”、“TDK”等。
两者相比较,就似精石美玉和污泥顽垢的差别。
这是一本值得现代人于午夜静静反思的书。
读完之后,我才发现,我并不知晓’我’的名字,反倒对雪说的”傻气”二字印象深刻。
把一部动漫(未来日记)中的日记软件搬到现实!
2015年2月,出于个人需求,我以“未来日记”为初始原型,编了一个日记APP。现在它也有了不少用户,得到了许多的反馈,也是时候重启更新进程了…
新版本的APP会保持开源状态,直至加入云同步功能。