使用Eclipse阅读JDK源码

研究JDK源代码,是从JAVA新手走向高手的必经之路。阅读JDK源码的方法如下:

  1. 在eclipse中新建一个JAVA工程
  1. 将JDK安装根目录里的src.zip解压到JAVA工程目录下
  1. 回到eclipse,右键点击工程,选择refresh
  1. 此时,除了一堆编译错误外,你应该可以开始阅读JDK所有源码

从无到有写一个后台注册模块(基于SSH2)

此次项目基于SSH2框架(物业报修系统),为了避免忘记框架使用的步骤,我稍微记录一下注册模块的编写过程。

框架分层

核心是Spring框架,它有IOC容器,可以方便地管理Struts2和Hibernate;Struts2因其MVC的架构,因而负责显示;Hibernate则是持久层,实现DAO。

步骤

下面是详细的步骤,根据时间先后一步一步来。

Tomcat容器的配置(WebRoot/WEB-INF/web.xml)

该文件告诉Tomcat,主页是哪个、Action交给谁处理、网站的编码格式、过滤拦截哪些URL、托管给Spring等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<!-- 所有请求都通过FilterDispatcher查找actionMapper的设置来决定请求对应的是哪个Action -->

<display-name>邱永臣配置</display-name>

<!-- 过滤器的配置 -->
<filter>
<!-- 名字 -->
<filter-name>filterDispatcher</filter-name>
<!-- 过滤器对应的名字 -->
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>

<filter-mapping>
<filter-name>filterDispatcher</filter-name>
<!-- 过滤匹配的URL -->
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 解决中文乱码问题 -->
<filter>
<filter-name>struts-clean</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-clean</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 进入WEB应用访问的默认文件 -->
<welcome-file-list>
<welcome-file>lookRemindRepairOrder.jsp</welcome-file>
</welcome-file-list>

<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 如果将该xml文件放在WEB-INF下面的话,就需要下面配置 <param-value>WEB-INF/applicationContext.xml</param-value> -->
<param-value>WEB-INF/applicationContext.xml</param-value>
<!-- 下面是默认配置 -->
<!-- <param-value>classpath:com/lzq/config/applicationContext-*.xml</param-value> -->
</context-param>

<!-- 采用Listener来初始化Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

编写用户实体(model层)

所谓用户实体,其实是java bean,它内部有用户数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
*
*/
package com.ilovecl.myproperty.model;

/**
* 学生实体
*
* @author 邱永臣
*
* 对应的数据库建表命令为 create table `Student` ( `userId` int not null primary
* key auto_increment, `userName` varchar(20) not null, `password`
* varchar(20) not null, `phoneNumber` long, `email` varchar(100),
* `sexual` varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
*/

public class Student {
private int userId;
private String userName;
private String password;
private long phoneNumber;
private String email;
private String sexual;

public Student() {
super();
// TODO Auto-generated constructor stub
}

public Student(String userName2, String password2, long phoneNumber2,
String email2, String sexual2) {
this.userName = userName2;
this.password = password2;
this.phoneNumber = phoneNumber2;
this.email = email2;
this.sexual = sexual2;
}

/**
* @return the userId
*/
public int getUserId() {
return userId;
}

/**
* @param userId
* the userId to set
*/
public void setUserId(int userId) {
this.userId = userId;
}

/**
* @return the userName
*/
public String getUserName() {
return userName;
}

/**
* @param userName
* the userName to set
*/
public void setUserName(String userName) {
this.userName = userName;
}

/**
* @return the password
*/
public String getPassword() {
return password;
}

/**
* @param password
* the password to set
*/
public void setPassword(String password) {
this.password = password;
}

/**
* @return the phoneNumber
*/
public long getPhoneNumber() {
return phoneNumber;
}

/**
* @param phoneNumber
* the phoneNumber to set
*/
public void setPhoneNumber(long phoneNumber) {
this.phoneNumber = phoneNumber;
}

/**
* @return the email
*/
public String getEmail() {
return email;
}

/**
* @param email
* the email to set
*/
public void setEmail(String email) {
this.email = email;
}

/**
* @return the sexual
*/
public String getSexual() {
return sexual;
}

/**
* @param sexual
* the sexual to set
*/
public void setSexual(String sexual) {
this.sexual = sexual;
}

}

编写Hibernate的映射文件(Student.hbm.xml)

Hibernate正是凭借它,才能将对象映射到数据库中去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
<class name="com.ilovecl.myproperty.model.Student" table="Student">
<id name="userId" type="java.lang.Integer">
<column name="userId" />
<generator class="increment" />
</id>
<property name="userName" type="java.lang.String">
<column name="userName" length="20" />
</property>
<property name="password" type="java.lang.String">
<column name="password" length="20" />
</property>
<property name="phoneNumber" type="java.lang.Long">
<column name="phoneNumber" />
</property>
<property name="email" type="java.lang.String">
<column name="email" length="100" />
</property>
<property name="sexual" type="java.lang.String">
<column name="sexual" length="10" />
</property>
</class>
</hibernate-mapping>

将Hibernate托管给Spring

在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码,告诉Hibernate数据库的信息,映射文件的位置,将Hibernate的sessionFactory托管给Spring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<!-- 设置字符集,防止插入到数据库里时出现乱码 -->
<value>jdbc:mysql://139.129.118.90:3306/myproperty?useUnicode=true&amp;characterEncoding=UTF-8</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>test</value>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/ilovecl/myproperty/model/Student.hbm.xml</value>
</list>
</property>
</bean>

<bean id="baseTransactionProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

编写DAO层代码(接口和实现)

DAO层专门操作实体,比如增删改查一个学生等。
接口代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
*
*/
package com.ilovecl.myproperty.DAO;

import java.util.List;

import com.ilovecl.myproperty.model.Student;

/**
* DAO层:学生DAO的接口
*
* @author 邱永臣
*
*/
public interface IStudentDAO {
public abstract void save(Student transientInstance);

public abstract void delete(Student persistentInstance);

public abstract Student findById(java.lang.Integer id);

public abstract List<Student> findByUserName(java.lang.String userName);

boolean isStudentExits(String userName);

}

实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
*
*/
package com.ilovecl.myproperty.DAO.impl;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.ilovecl.myproperty.DAO.IStudentDAO;
import com.ilovecl.myproperty.model.Student;

/**
* @author 邱永臣
*
*/
public class StudentDAO extends HibernateDaoSupport implements IStudentDAO {

private static final Log log = LogFactory.getLog(StudentDAO.class);

/*
* (non-Javadoc)
*
* @see
* com.ilovecl.myproperty.DAO.IStudentDAO#save(com.ilovecl.myproperty.model
* .Student)
*/
@Override
public void save(Student transientInstance) {
log.debug("save(Student transientInstance) in StudentDAO");
try {
this.getHibernateTemplate().saveOrUpdate(transientInstance);
log.debug("save(Student transientInstance) in StudentDAO successful");
} catch (RuntimeException re) {
log.error("save(Student transientInstance) in StudentDAO failed",
re);
throw re;
}

}

/*
* (non-Javadoc)
*
* @see
* com.ilovecl.myproperty.DAO.IStudentDAO#delete(com.ilovecl.myproperty.
* model.Student)
*/
@Override
public void delete(Student persistentInstance) {
log.debug("delete(Student persistentInstance)");
try {
getHibernateTemplate().delete(persistentInstance);
log.debug("delete(Student persistentInstance) successful");
} catch (RuntimeException re) {
log.error("delete(Student persistentInstance) failed", re);
throw re;
}

}

/*
* (non-Javadoc)
*
* @see com.ilovecl.myproperty.DAO.IStudentDAO#findById(java.lang.Integer)
*/
@Override
public Student findById(Integer id) {
log.debug("findById(Integer id) : " + id);
try {
Student instance = (Student) getHibernateTemplate().get(
"com.ilovecl.myproperty.model.Student", id);
return instance;
} catch (RuntimeException re) {
log.error("findById(Integer id) failed", re);
throw re;
}
}

/*
* (non-Javadoc)
*
* @see
* com.ilovecl.myproperty.DAO.IStudentDAO#findByUsername(java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public List<Student> findByUserName(String userName) {
List<Student> students;
try {
String queryString = "from Student as model where model."
+ "userName" + "= ?";
students = getHibernateTemplate().find(queryString, userName);
} catch (RuntimeException re) {
log.error("findByUserName failed", re);
throw re;
}

return students;
}

@Override
public boolean isStudentExits(String userName) {
return findByUserName(userName).size() == 1;
}
}

将DAO托管给Spring

在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:

1
2
3
4
5
6
7
<!-- 将学生DAO托给Spring -->
<bean id="studentDAO" class="com.ilovecl.myproperty.DAO.impl.StudentDAO">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>

编写逻辑层代码(接口与实现)

逻辑层原先放在Struts2控制层的Action中,但随着Action的日渐庞大,许多负责的逻辑代码需要分离出来单独作为逻辑层。

接口的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
*
*/
package com.ilovecl.myproperty.service;

import com.ilovecl.myproperty.DAO.IStudentDAO;
import com.ilovecl.myproperty.model.Student;

/**
* @author 邱永臣
*
*/
public interface IRegisterService {

public IStudentDAO getStudentDAO();

public void setStudentDAO(IStudentDAO studentDAO);

public boolean register(Student student);

boolean isStudentExits(String userName);
}

实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
*
*/
package com.ilovecl.myproperty.service.impl;

import com.ilovecl.myproperty.DAO.IStudentDAO;
import com.ilovecl.myproperty.model.Student;
import com.ilovecl.myproperty.service.IRegisterService;

/**
* @author 邱永臣
*
*/
public class RegisterService implements IRegisterService {

private IStudentDAO studentDAO;

/**
* @return the studentDAO
*/
public IStudentDAO getStudentDAO() {
return studentDAO;
}

/**
* @param studentDAO
* the studentDAO to set
*/
public void setStudentDAO(IStudentDAO studentDAO) {
this.studentDAO = studentDAO;
}

/*
* (non-Javadoc)
*
* @see
* com.ilovecl.myproperty.service.IRegisterService#isStudentExits(java.lang
* .String)
*/
@Override
public boolean isStudentExits(String userName) {
return studentDAO.findByUserName(userName).size() == 1;
}

/*
* (non-Javadoc)
*
* @see
* com.ilovecl.myproperty.service.IRegisterService#register(java.lang.String
* , java.lang.String)
*/
@Override
public boolean register(Student student) {
if (isStudentExits(student.getUserName())) {
return false;
}

studentDAO.save(student);

return true;
}

}

将逻辑层托管给Spring

在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:

1
2
3
4
5
6
7
8
<!-- 注册业务逻辑 -->
<bean id="registerService" parent="baseTransactionProxy">
<property name="target">
<bean class="com.ilovecl.myproperty.service.impl.RegisterService">
<property name="studentDAO" ref="studentDAO" />
</bean>
</property>
</bean>

编写Struts2的Action

到这里,我们就进入了Struts2的MVC世界。

  1. 控制层:Action属于MVC中的C;
  2. 模型层:Struts1中用java bean作为模型层,也就是MVC中的M,但该方案在Struts2中被废除,模型层的内容直接放入Action;
  3. 视图层:jsp的内容即是视图层;

Action最主要的方法是execute,在用户点击注册按钮时,会执行该方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/**
*
*/
package com.ilovecl.myproperty.struts.action;

import com.ilovecl.myproperty.model.Student;
import com.ilovecl.myproperty.service.IRegisterService;
import com.opensymphony.xwork2.ActionSupport;

/**
* 用户注册功能的Action类
*
* @author 邱永臣
*
*/
@SuppressWarnings("serial")
public class StudentRegisterAction extends ActionSupport {
private int userId;
private String userName;
private String password;
private String passwordConfirm;
private long phoneNumber;
private String email;
private String sexual;

String message;

private IRegisterService iRegisterService;

/**
*
*/
public StudentRegisterAction() {
phoneNumber = 0;
email = "";
sexual = "";
}

/*
* (non-Javadoc)
*
* @see com.opensymphony.xwork2.ActionSupport#execute()
*/
@Override
public String execute() throws Exception {
this.message = "";

if (this.userName.equals("")) {
this.message += "用户名为空!\n";
return INPUT;
} else if (this.password.equals("") || this.passwordConfirm.equals("")) {
this.message += "密码为空!\n";
return INPUT;
} else if (!this.password.equals(this.passwordConfirm)) {
this.message += "两次密码不相同!\n";
return INPUT;
}

if (iRegisterService.isStudentExits(userName)) {
this.message += "账户已存在!";
return INPUT;
}
if (iRegisterService.register(new Student(userName, password,
phoneNumber, email, sexual))) {
this.message = "注册成功";
return SUCCESS;
} else {
this.message = "注册失败";
return INPUT;
}
}

/**
* @return the userId
*/
public int getUserId() {
return userId;
}

/**
* @param userId
* the userId to set
*/
public void setUserId(int userId) {
this.userId = userId;
}

/**
* @return the userName
*/
public String getUserName() {
return userName;
}

/**
* @param userName
* the userName to set
*/
public void setUserName(String userName) {
this.userName = userName;
}

/**
* @return the password
*/
public String getPassword() {
return password;
}

/**
* @param password
* the password to set
*/
public void setPassword(String password) {
this.password = password;
}

/**
* @return the passwordConfirm
*/
public String getPasswordConfirm() {
return passwordConfirm;
}

/**
* @param passwordConfirm
* the passwordConfirm to set
*/
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}

/**
* @return the phoneNumber
*/
public long getPhoneNumber() {
return phoneNumber;
}

/**
* @param phoneNumber
* the phoneNumber to set
*/
public void setPhoneNumber(long phoneNumber) {
this.phoneNumber = phoneNumber;
}

/**
* @return the email
*/
public String getEmail() {
return email;
}

/**
* @param email
* the email to set
*/
public void setEmail(String email) {
this.email = email;
}

/**
* @return the sexual
*/
public String getSexual() {
return sexual;
}

/**
* @param sexual
* the sexual to set
*/
public void setSexual(String sexual) {
this.sexual = sexual;
}

/**
* @return the iRegisterService
*/
public IRegisterService getiRegisterService() {
return iRegisterService;
}

/**
* @param iRegisterService
* the iRegisterService to set
*/
public void setiRegisterService(IRegisterService iRegisterService) {
this.iRegisterService = iRegisterService;
}

/**
* @return the message
*/
public String getMessage() {
return message;
}

/**
* @param message
* the message to set
*/
public void setMessage(String message) {
this.message = message;
}

}

将Action托管给Spring

在WebRoot/WEB-INF/applicationContext.xml中增加下面的代码代码:

1
2
3
4
5
<!-- 学生注册界面的Action -->
<bean id="studentRegisterAction"
class="com.ilovecl.myproperty.struts.action.StudentRegisterAction">
<property name="iRegisterService" ref="registerService" />
</bean>

让Struts2找到从Spring那里找到Action

由于Spring管理着Action,Struts2需要Action的时候,只能找Spring要。

在struts.xml中增加下面的代码代码:

1
2
3
4
<action name="register" class="studentRegisterAction">
<result name="success">/register.jsp</result>
<result name="input">/register.jsp</result>
</action>

编写视图层页面(jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>

<title>用户注册</title>

</head>

<body><br><br><br><br>
<div align="center">
${requestScope.message}
<s:form action="register" method="POST">
<s:textfield name="userName" label="user name" />
<s:password name="password" size="21" label="password" />
<s:password name="passwordConfirm" size="21" label="passwordConfirm" />
<s:textfield name="phoneNumber" label="phoneNumber" />
<s:textfield name="email" label="email" />
<s:textfield name="sexual" label="sexual" />
<s:submit value="OK" />
</s:form>
</div>
</body>
</html>

其它

上面是就是一个完整的SSH2实现的注册模块,在运行之前必须先建立数据库,数据库的信息和建表命令都在上面。

JAVA值传递

问题的来源

写项目时,有一个方法需要返回两种类型的对象,所以我将两个空白的对象作为参数传给方法,在方法里修改。修改时,我没有一条条地填上空白对象的属性,而是贪图方便,将一个新对象赋值给空白对象,结果原来的那个空白对象仍旧是空白的。

问题详情

java不同于C++,在参数传递中只有值传递,没有引用传递。在下面的方法声明里,repairOrder是一个RepairOrder对象的引用,student是一个Student对象的引用:

1
2
3
4
5
6
7
8
9
10
public boolean getDetailOfRepairOrder(int repairOrderId,
RepairOrder repairOrder, Student student) {
repairOrder.setName("test");
student.setPhone(18819473231);

RepairOrder newR = new RepairOrder();
newR.setName("newR test");

repairOrder = newR;
}

我将一个r和一个s传给上面的函数:

1
2
3
RepairOrder r = new RepairOrder();
Student s = new Student();
getDetailOfRepairOrder(3234234, r, s)

程序的结果是,r的name属性是test而不是newR test,为什么?

Read More

Struts2实现图片上传(物业报修系统)

介绍(S & T)

“物业报修系统”项目里有个图片上传的功能模块,我花了近一个晚上理解原理和写出代码。项目框架是SSH2,而上传图片模块主要涉及到Struts2,所以我剥离Spring和Hibernate的内容,只放出Struts2的代码。

(N : 在2015年的时候,我在Android端写过一个类似的模块,但当时我的做法是调用Restful API将图片直接上传到服务器,写的跑在客户端上,而现在写的代码跑在服务器上,两者有较大的区别)

详细步骤(A)

主要步骤分别有Struts2配置文件的修改编写JSP代码编写后台Action处理

Struts2配置文件的修改

首先是web.xml,它是TomCat服务器的配置文件,为了使得网站的默认页面是上传照片页面(default_upload_pic.jsp),必须修改它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<!-- 所有请求都通过FilterDispatcher查找actionMapper的设置来决定请求对应的是哪个Action -->

<display-name>邱永臣配置</display-name>

<!-- 过滤器的配置 -->
<filter>
<!-- 名字 -->
<filter-name>filterDispatcher</filter-name>
<!-- 过滤器对应的名字 -->
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>

<filter-mapping>
<filter-name>filterDispatcher</filter-name>
<!-- 过滤匹配的URL -->
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 解决中文乱码问题 -->
<filter>
<filter-name>struts-clean</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-clean</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 进入WEB应用访问的默认文件 -->
<welcome-file-list>
<welcome-file>default_upload_pic.jsp</welcome-file>
</welcome-file-list>

<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 如果将该xml文件放在WEB-INF下面的话,就需要下面配置 <param-value>WEB-INF/applicationContext.xml</param-value> -->
<param-value>WEB-INF/applicationContext.xml</param-value>
<!-- 下面是默认配置 -->
<!-- <param-value>classpath:com/lzq/config/applicationContext-*.xml</param-value> -->
</context-param>

<!-- 采用Listener来初始化Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

接着是struts.xml,它配置了Struts2。在一个页面被提交到服务器后,如何进行处理、由谁处理和处理之后给浏览者返回什么内容
等都由它决定。负责处理图片上传的Action是com.ilovecl.test包中的UploadPicAction,它决定浏览者跳转到page1.jsp或是page2.jsp。此外,其中还定义了文件过滤器,不允许浏览者上传过大的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="qiuyongchen" extends="struts-default">

<action name="uploadPic"
class="com.ilovecl.test.UploadPicAction">
<result name="success">/page1.jsp</result>
<result name="input">/page2.jsp</result>

<!-- 定义文件上传拦截器 -->
<interceptor-ref name="fileUpload">

<!-- 设置文件上传大小 -->
<param name="maximumSize">4096000</param>
</interceptor-ref>

<!-- 自定义了拦截器后必手动定义默认的拦截器,否则默认的拦截器不会被执行 -->
<interceptor-ref name="defaultStack"></interceptor-ref>

</action>

</package>
</struts>

JSP代码

JSP文件负责给浏览者显示内容,它提供一个文件选择框,浏览者选择图片后点击按钮便可上传(JSP是嵌入在HTML中的脚本,和javascript类似)。

Read More

允许MySQL远程访问

近几周,出于课程要求,我在忙于写“物业保修系统”,该系统的MySQL数据库放在Aliyun上,而我写代码和调试的地点是不固定的,每次连接数据库时总会跳BUG:

(Access denied for user ‘root‘@’112.96.109.106’ (using password: YES))

112.96.109.106这个IP是不固定的,尤其是当我用手机当热点时(在前女友学校的图书馆里只能这么办),每次都会换一个IP,所以每次都要登录服务器去新增一个白名单。

具体的步骤是这样的:

  1. 使用以下命令登录MySQL
    1
    mysql -u root -p
  2. 使用以下命令,使得从任意一个IP地址都能用root访问mysql(密码为test)
    1
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' BY 'test' WITH GRANT OPTION;

这样一来,以后我就可以随便地在任何一个喜欢的地方访问数据库了。

HashMap中的equals和hashCode

Java的Object对象有9个方法,其中的equals()hashCode()在hashMap的实现里面起着比较重要的作用,我在研究hashMap的源码时就遇到了它们俩,此篇博文主要是为了记录它们之间的相爱相杀。

为了说明它们的关系,我们需要HashMap的背景知识。

HashMap的存储方式

HashMap的实现方式是数组链,不同的对象根据其哈希码(hashCode方法的返回值)找到对应的数组下标,然后存入数组。不同的对象有相同的哈希码时怎么办?这就由数组链中的链来解决了,相同哈希码的对象都放在同一条链上,该链的链头指向数组,进而形成数组链。

当第一个对象已经存入HashMap,第二个对象准备存入HashMap时,系统在查找到数组下标后若发现它们的hashCode相同(也就是冲突),会调用equals()来检查它们之间的关系,会有相应有以下两种处理方法:

  1. 如果相等,系统就不再存入第二个对象;
  2. 如果不等,系统视它们为纯粹的下标冲突,将它们放在同一条链上;

如果它们的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()一定要相同,不然就有神奇的事情发生。

Spring的接口注入

我在使用接口注入时,犯了个错误,项目运行时出现“**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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- usersManager将会被注入UsersAction -->
<bean name="/users" class="com.ilovecl.news.struts.action.UsersAction">
<property name="usersManager">
<ref local="usersManager" />
</property>
</bean>

<!-- UsersManager -->
<bean id="usersManager" parent="baseTransactionProxy">
<property name="target">
<bean class="com.ilovecl.news.service.impl.UsersManager">
<property name="usersDao">
<ref local="usersDao" />
</property>
</bean>
</property>
</bean>

我试图将一个实现了某个接口的类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,这是接口注入的核心(遗憾的是,接口注入和属性注入的功能差不多,但接口使得项目的类数量变多,现如今不推崇使用接口注入)。

MySql的Join深入(未完待续)

零、背景

本文涉及2个表,券主表Receipt和发券记录表ReceiptIssueRecord,1个订单可以拥有多张券,1张券只会对应1个订单。

建表语句

1
2
3
4
5
6
7
8
9
10
11
12
<-- 券主表 -->
CREATE TABLE `Receipt` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`OrderId` bigint(20) NOT NULL DEFAULT '0' COMMENT '订单编号',
`ReceiptNumber` varchar(30) NOT NULL DEFAULT '"' COMMENT '券码',
`AddTime` datetime NOT NULL DEFAULT '1980-01-01 00:00:00' COMMENT '添加时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`ID`),
UNIQUE KEY `IX_ReceiptNumber` (`ReceiptNumber`),
KEY `IX_OrderId` (`OrderId`),
KEY `IX_UpdateTime` (`UpdateTime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
8
9
10
11
12
13
<-- 发券记录表 -->
CREATE TABLE `ReceiptIssueRecord` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键,自增id',
`OrderId` bigint(20) NOT NULL DEFAULT '-1' COMMENT '订单ID',
`BeginDate` datetime NOT NULL COMMENT '有效期开始时间',
`EndDate` datetime NOT NULL COMMENT '有效期结束时间',
`AddTime` datetime NOT NULL COMMENT '插入时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`ID`),
UNIQUE KEY `UK_OrderId` (`OrderId`),
KEY `IX_IssueStatus` (`IssueStatus`),
KEY `IX_UpdateTime` (`UpdateTime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMPRESSION='lz4' COMMENT='发券记录表';

表内容

券主表

发券记录表

一、多表查询

为了查出分布在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,根据多个表的列,从左到右,将多个表拼接起来。

Join分为几种,cross join,inner join,left join,right join,full join和join。

cross join(交叉连接)

cross join即是第一节里多表查询的方式,查询语句:

1
2
select * from Receipt cross join ReceiptIssueRecord;
<-- 相当于 select * from Receipt, ReceiptIssueRecord -->

其结果是笛卡尔乘积,左表的每一条记录和右表的所有记录都做了连接,结果是极其可怕的。
(注:cross join 不能像其它join一样使用on关键字)

inner join(内连接)

inner join相当于join,带上on关键字,可以在多表连接时过滤不符合条件的记录,而不必等到连接完成后再用where子句筛选,查询语句:

1
2
3
select * 
from Receipt inner join ReceiptIssueRecord
on Receipt.`OrderId` = ReceiptIssueRecord.`OrderId` and Receipt.`OrderId` = 5114049055891395;

结果:

inner join带上on关键字,左表和右表都必须符合on的限定条件,才会拿出来做拼接。
比如,上面的查询会从Receipt表中挑出orderId为5114049055891395的记录,再从ReceiptIssueRecord表中挑出orderId为5114049055891395的记录,2者拼接。

left join(左连接)

left join,也被称作left outer join。相比于inner join,它不要求左表和右表都符合on限定条件,而是把左表的行记录全部记录出来,然后按照on限定到右表查找,若找到则连接,若没有则用null拼接。

1
2
3
select * 
from ReceiptIssueRecord left join Receipt
on ReceiptIssueRecord.`OrderId` = Receipt.`OrderId`;

结果:

right join(右连接)

right join,和left join相反,全部列出右表所有记录,再从左表查找。

1
2
3
select * 
from ReceiptIssueRecord right join Receipt
on ReceiptIssueRecord.`OrderId` = Receipt.`OrderId`;

full join(全连接)

full join,是left join和right join的综合,全部列出左表和右表的记录,再针对左表的记录,到右表查,有则拼接无则null,接着针对右边记录,到左表查。

1
2
3
select * 
from ReceiptIssueRecord full join Receipt
on ReceiptIssueRecord.`OrderId` = Receipt.`OrderId`;

注:理论上,full join的结果是left join和right join的和,不过我在实际操作的时候,sql报错了,有待进一步研究

on VS where

on相对于join,就像是where相对于select。

在join的时候加上on限定,不会对表中所有数据进行连接,仅仅会挑选出符合限定条件的记录,这样就大大减少了查询成本,从千万级降到百千级,所以有人建议join查询时,尽量把条件都放在on里面。

如果同时使用on和where,则会在查询时根据on条件选择性连接,再根据where条件对连接结果进行筛选。

读万卷书行万里路

f8a7d13f8794a4c27a284ab50ef41bd5ac6e39d7

闲暇之余,读几本好书,不仅可以修心养性、陶冶情操,更可以开阔视野学到丰富的知识。所谓足不出户,便知天下事、读书百 遍,其义自见,说的也正是这个道理。回首往昔,我读过的书也不少,只可惜那时年少,不知所谓“积累”,如今既知,深感两者为云泥之别,故此,开此篇,记录 阅读书目。倘若临时有忆,亦会翻出记忆深处被掩盖的想法。革命尚未成功,同志仍需努力,谨此。

《钱伟长传》(祁淑英) 七月 31, 2015

写的比较好的一本书,书中传主专注学术、追求科学的精神让人印象深刻。

再回首现代,一心向钱的人们仿佛只是专注着路边的水井盖,流行文化里充斥着所谓“男女神”、“壕”、“TDK”等。

两者相比较,就似精石美玉和污泥顽垢的差别。

这是一本值得现代人于午夜静静反思的书。

《舞舞舞》(村上春树) 八月 3, 2015

读完之后,我才发现,我并不知晓’我’的名字,反倒对雪说的”傻气”二字印象深刻。

Read More

从无到有写一个日记APP

把一部动漫(未来日记)中的日记软件搬到现实!

2015年2月,出于个人需求,我以“未来日记”为初始原型,编了一个日记APP。现在它也有了不少用户,得到了许多的反馈,也是时候重启更新进程了…

新版本的APP会保持开源状态,直至加入云同步功能。

Read More