【小结】类 WebInfo 站各方面
1、Java 程序开发方面
1.1、使用统一的JavaWeb 模板项目: webtemplate
1、使用 Eclipse mars 及以上版本,将自带 Maven 插件
2、模板项目导入:
- 先修改模板项目中
pom.xml
的<artifactId>webtemplate</artifactId>
<finalName>webtemplate</finalName>
节点的项目名字 - 在 Eclipse 中 Import->Existing Maven Projects 选择项目文件夹
提示:首次开发 maven 将下载需要的 jar 包,需要等待几分钟
- 项目导入后:如果有红叉可以在项目上右键 maven->update project;如果是因为 jsp 验证的报错,这种报错可以忽略,如果看的不爽可以关闭 Eclipse 检测 jsp 的功能
1.2、项目版本控制 git
- 你的电脑要已安装 git,否则
sudo apt-get install git
- 在项目文件夹目录使用
git init 项目文件夹的名字
,这样就创建了本地 git 库 - 项目文件中使用
git remote add origin 远程仓库GitURL
可添加远程仓库 - 添加
.gitignore
文件,忽略管理不需要的文件
/target/
/.settings/
.classpath
.project
- 在 Eclipse 中打开 git 管理面板:Windows -> Show view -> other -> 搜索 git -> 添加 Git Repository 、Git Staging
- Git Repository 是 Git 仓库管理,用与添加 Git 库
- Git Staging 是 常用的阶段管理
- 在 Eclipse :Windows -> Perspective -> Customize Perspective -> git 中可以添加 git push、pull 的快捷按钮
警告:在做镜像站或者别的新项目时,一定不要直接拷贝整个项目文件夹,因为项目在项目文件夹中有 .git .settings 的隐藏文件,直接拷贝可能会产生 Git 和项目的混乱。建立镜像站的正确做法是:建了新文件夹,只拷贝 src 文件夹
和 pom.xml
,然后修改 pom.xml
项目名,类似步1、1
,init 新 git 库
1.3、在需求分析方面(待讨论)
首先看设计原型图->分析页面需要展示什么数据->这些数据存放在哪些数据表中->各数据表是如何关联的
一定想好页面上每一部分数据的如何获取在进行实际开发
1.4、在DAO方面(待讨论)
1、一张数据表对应一个 DAO,如果有连表查询,将接口定义在主表 DAO 中
2、在设计 DAO 方法时,参数应该尽可能明确,比如:
Domain queryBySlug(String slug);
而不要设计为:
Domain queryBySlug(Domain domain);
下面的方法参数是一个 Domain 实体,可是 Domain 实体中到底有什么是完全不知道的,别人只有查看 mapper 才只有到底涉及到了什么变量,这时很不方便的,也很难复用。所以 DAO 接口的设计要尽量明确、细致
3、每个 DAO 都对应一个 mapper 文件,在 resource/mapper
中,名字和 DAO 名字一样,文件中<mapper namespace="com.moma.adsense.dao.DomainDao">
很重要,这决定了映射关系
4、mapper 中的 查询 sql 应当拒绝 select *
select
domain_nid,domain_name,domain_country,domain_slug,domain_createtime,domain_adsense_status,domain_includetime,domain_modifytime
from
ads_domain
where
domain_nid = #{nid}
这中分行写法会让 sql 更加清晰
!容易出错的地方 1!
DAO 的接口肯定会有多参数的情况,但是在与 mapper 映射时,会有参数对应不上的问题,这时需要 @Param 来解决
List<LogicAdsDmn> queryLogicAdsDmnList(@Param("maxNid") long maxNid, @Param("star") int star, @Param("step") int step);
// 如果写成,是无法对应上参数的
List<LogicAdsDmn> queryLogicAdsDmnList(long maxNid,int star,int step);
mapper:
<select id="queryLogicAdsDmnList" resultType="ads_logic_ads_dmn">
<![CDATA[
SELECT
lad_nid,domain_nid,adsense_nid,lad_number,lad_modifytime
from
ads_logic_ads_dmn
where
lad_nid > #{maxNid}
order by lad_nid limit #{star},#{step}
]]>
</select>
多参数时,在 mapper 中不用定义传入参数的类型;如果返回是 list 那定义的返回值类型应该是 list 的泛型
!容易出错的地方 2!
在 mapper 中 \$ 与 # :
#{} 会将参数编译,有点像使用 JDBC 的 PreparedStatement
\${} 将直接将参数插入 sql 中,可用于 order by \${}
!容易出错的地方 3!
如果要插入 List,DAO:
int insertDomainList(List<Domain> domainList);
mapper:
<insert id="insertDomainList" parameterType="java.util.List">
<if test="list != null and list.size() > 0 ">
insert into ads_domain
(
domain_nid,domain_name,domain_slug,domain_country,domain_createtime,
domain_adsense_status,domain_includetime,domain_modifytime
)
VALUES
<foreach collection="list" item="domain" index="index"
separator=",">
(
#{domain.domainNid},#{domain.domainName},#{domain.domainSlug},
#{domain.domainCountry},#{domain.domainCreatetime},#{domain.domainAdsenseStatus},
#{domain.domainIncludetime},#{domain.domainModifytime}
)
</foreach>
</if>
</insert>
如果参数只有一个 list parameterType="java.util.List"
固定写法,collection="list"
也是固定写法,而不是 collection="domainList"
,这时 Mybatis 的映射机制,会将你的参数封装成 map,map.put("list",domainList)
<if test="list != null and list.size() > 0 ">
先验证下 list,可减少 sql 出错
separator=","
是迭代时的连接符
!容易出错的地方 4!
如果 DAO 的参数是对象,DAO:
List<Adsense> queryByNewId(AdQuery adQuery);
AdQuery
public class AdQuery {
private String newId;
private Integer offset;
private Integer length;
setter/getter
}
mapper
<select id="queryByNewId" parameterType="AdQuery" resultType="ads_adsense">
<![CDATA[
select
adsense_nid,adsense_id,adsense_type,adsense_new_id,adsense_modifytime
from
ads_adsense
where
adsense_new_id like CONCAT('%',#{newId}) and adsense_type != 'h' limit #{offset}, #{length}
]]>
</select>
mapper 直接写对象中的域 #{newId}
而不是写成 #{adQuery.newId}
!容易出错的地方 5!
mapper 中 parameterType 和 resultType 对象命名的规则
resultType="ads_adsense"
映射的 javabean 是 com.moma.adsense.entity.Adsense.java
。但是为什么不是 resultType="Adsense"
? 这是因为在 Adsense.java 中使用了 @Alias("ads_adsense")
,给他起了别名;但是什么的不是resultType="com.moma.adsense.entity.Adsense"
? 这是因为在 spring-dao.xml
对 com.moma.adsense.entity
包进行了扫描注入。所以 parameterType 或者 resultType 使用的对象不在 com.moma.adsense.entity
中时,就要写类的全称
!容易出错的地方 6!
entity
的域类型应当使用包装类型,例如 int
用 Integer
、long
用 Long
,这样可以兼容 null
精确的小数值,我(Yifan)建议使用 BigDecimal,数据库也是 BigDecimal,double float 在某些时候会有精度损失
DAO 的重点:DAO 不要考虑业务,不要写入业务逻辑
1.5、在Service方面(待讨论)
1、Service 的接口参数应该使用对象类型,提高方法的聚合性和弹性,例如
接口定义
/* 获取domain */
Domain getDomain(Domain domain);
接口实现
@Override
public Domain getDomain(Domain domain) {
// TODO Auto-generated method stub
Long nid = domain.getDomainNid();
String name = domain.getDomainName();
String slug = domain.getDomainSlug();
if (nid != null) {
domain = domainDao.queryByNid(nid);
} else if (slug != null) {
domain = domainDao.queryBySlug(slug);
} else if (name != null) {
domain = domainDao.queryByName(name);
}
return domain;
}
这样写就聚合了 domainDao 中的三个方法,使得 DAO 中的 接口和 Service 接口不是一一对应对应,这样在程序发生变动时可以减少程序变化
就算只有 slug 的查询
@Override
public Domain getDomain(Domain domain) {
// TODO Auto-generated method stub
String slug = domain.getDomainSlug();
domain = domainDao.queryBySlug(slug);
return domain;
}
// 也比下面的好
public Domain getDomain(String slug) {
...
}
因为下面的Service 接口设计与 DAO 接口数据依赖关系太重,DAO 的调整一定会导致 Service 的调整
2、Service 应当不直接调用不直接相关的 DAO,应当调用其直接相关的 Service(保证 Service 与 DAO 之间关系不混乱)
意思就是 DomainServier
DAO 只注入 DomainDAO
,并不注入 AdsenseDAO
用到 adsense 时注入 AdsenseService
,为得也是解除 Service 和 dao 依赖
Service 的重点:Service 的方法应该以业务导向,返回的数据是包装过的
1.6、在Controller方面(待讨论)
1、Controller 主要负责请求转发,不要在其中处理业务
2、Controller 中每个方法返回的 model 中都添加,为jsp 区分 controller 来源使用:controllerName
当前 Controller【名字】controllerMethod
当前 Controller 调用的【方法名字】originalURL
【来源】URL
model.addAttribute("controllerName", "domain");
model.addAttribute("controllerMethod", "index");
model.addAttribute("originalURL", "/");
3、想使用任何的 servlet 对象将它定义成方法的参数就可以使用,这是 SpringMVC 很 cool 的地方
// 想使用 HttpServletRequest
public String index(Model model, HttpServletRequest request) {...}
!容易出错的地方 1!.../domain/baidu.com
这中 slug,baidu.com
应当称为参数,但是 SpringMVC 会将 .
看成分割符而值接收到 baidu
解决方法:value = "/{id-number:.+}"
@RequestMapping(value = "/{domain-name:.+}", method = RequestMethod.GET)
public String index(@PathVariable("domain-name") String dmnName, Model model, HttpServletRequest request,
HttpServletResponse response) {...}
!容易出错的地方 2!
404 页面的状态码为:200
设置统一404返回
@RequestMapping(value = "/404page", method = RequestMethod.GET,HttpServletResponse response)
public String index404(Model model) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 设置状态码
logger.info("/index404");
return "404";
}
其他地方这样调用
return "redirect:/404page";
1.6、在 JSP 方面
1、基本结构已经给出,不要破坏
2、没有集成 struts2,可使用 jstl 标签
3、在 header.jsp 已经定义了 jspPath
用于项目路径,不包含最后\\
,jspPath
可在所有引入 header.jsp 的文件了使用,可无视报错提示,不要再重复定义 eg:http://localhost:8080/(project-name)
1.7、在开发的其他方面
1、com.moma.adsense.dto
存放数据传输对象,用于装饰数据库实体对象,给实体对象扩展字段
2、com.moma.dmv.util
存放通用的工具类
1.8、推荐的命名方式
DAO
DES | DES | DES |
---|---|---|
查询 | queryBy | queryById 根据 本身 的 id
|
查询多条件 | queryByIdAndTest | 根据 本身 的 id 和 Test 的 主键
|
查询所有 | queryAll | 返回 List
|
插入 | insertObject | insertVote 返回 Boolean ;将插入主键
|
更新 | updateObject | updateVote 返回 影响的条数 Long
|
计数 | countBy | countByTest 根据 Test 的 主键
|
删除 | deleteBy | deleteByUtId |
service
DES | DES | DES |
---|---|---|
获取对象 | getObject | getTest |
保存对象 | saveObject | saveLogicTestVote 有则插入,没有则更新 |
插入对象 | addObject | addVote 插入对象 |
删除对象 | removeObject | removeLogicUserMistake |
2、Java 自动更新程序开发方面
2.1 自动化更新核心
核心使用的 RMI 技术
2.2 Service 端的注意事项
Spring 配置,见模板项目的 spring-remote-server.xml
<!-- RMI server -->
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="update_data" />
<!-- 服务端接口实现类 -->
<property name="service" ref="serverRMIService" />
<!-- 服务端接口 -->
<property name="serviceInterface" value="com.moma.adsense.rmi.server.ServerRMIService" />
<!-- RMI服务注册端口,服务器要开放 -->
<property name="registryPort" value="9090" />
<property name="servicePort" value="8899" />
</bean>
接口应该继承 Remote
interface ServerRMIService extends Remote
接口中的方法应当都抛出 java.rmi.RemoteException
int addDomainList(List<Domain> domainList) throws java.rmi.RemoteException;
接口实现类 要在 Spring 自动扫描的包中,否则需要配置
@Service("serverRMIService")
public class ServerRMIServiceImpl implements ServerRMIService {...}
Spring 包扫描
<context:component-scan base-package="com.moma.adsense.rmi.server" />
2.3 Client 端的注意事项
Spring 配置,见模板项目的 spring-remote-client.xml
<!-- RMI client -->
<bean id="clientService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<!-- 服务端URL -->
<property name="serviceUrl" value="rmi://52.10.35.214:9090/update_data" />
<!-- 服务接口 -->
<property name="serviceInterface" value="com.moma.adsense.rmi.server.ServerRMIService" />
<property name="lookupStubOnStartup" value="false" />
<property name="refreshStubOnConnectFailure" value="true"></property>
</bean>
服务端类,使用 BeanFactory 注入对象
@Component
public class AutoUpdateClient implements BeanFactoryAware{
ServerRMIService updateService = (ServerRMIService) bea.getBean("clientService");
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// TODO Auto-generated method stub
AutoUpdateClient.bea = beanFactory;
}
}
定时任务,在需要定时执行的方法上配置
@Scheduled(cron = "59 59 23 0/2 * ? ")
定时任务,Spring 配置 spring-task.xml
<task:annotation-driven />
<context:component-scan base-package="com.moma.adsense.rmi.client" />
2.4 RMI部署线上的注意事项
1、无法连接向上服务
aws 的 host 被默认为 aws 的内网 ip,在启动 RMI 服务端后,无法与线下连接,查看 hostname ,修改服务器 hosts 文件:添加 hostname 和对外网 ip。可以解决