howsun-javaee-framework
Java应用层框架
版本:1.0.8
1、项目介绍
这是一款居于Spring容器之上特别适用于中小企业应用的JavaEE快速开发框架,具有如下特性:
1、跨服务调用(跨Spring容器,也可以使用类似Netty的通信中间件来实现)
2、封装DAO操作,大大简化了数据库操纵业务,统一的查询参数接口,统一的分页对象,可创建单机可集群环境的数据唯一ID。支持Hibernate,JPA和MongoDB操纵
3、统一配置管理,配置文件不随工程一起发布,可以有效地避免线上线下配置文件混乱问
4、统一日志管理,不需要做太多的日志配置,自动生成日志配置模板,并将日志记录到指定的文件夹中
5、封装了Redis客户端,取代Memcache
6、简化了Json操作
7、提供了分页、工具类封装的JSP标签库
8、大量工具包:如安全、Web、断言、编码等40多种
2、引入框架构件
Maven引入
org.howsun
howsun-jee-framework
1.0.8
需要注意,必须保证内部仓库中已经存在构件。
非Maven工程,请在build目录下载howsun-jee-framework-1.0.8.jar,并手工将其和依赖构件全部加入工程的ClassPath
3、定制统一配置的名称空间
步骤:
在工程Source下创建META-INF目录
目录中创建howsun-config.properties配置文件
文件中只需一行:namespace=空间名
统一这种配置后,框架会在工程所在的磁盘根目录下创建opt/hjf/{namespace}子目录,用于存放配置文件、日志文件。
4、统一日志管理
框架运行时,第一次会在项目同分区中创建/opt/hjf/${空间名}目录,并自动写一份log4j.properties的配置文件
日志级别默认为info
日志分别往控制台和文件中传送
日志内容文件在/opt/hjf/logs/${空间名}.log
按天切分存储
5、统一服务管理
以实际例子说明应用,假如某项目含有如下服务:
WWW:主站服务,既是个Web工程
CMS:资源管理服务,重点是数据存储和操纵。普通Java工程
SNS:用户管理服务,并含有复杂社交关系应用。普通Java工程
如果WWW要调用CMS服务中接口,就涉及到跨服务,目前1.8版是跨Spring容器来实现的,具体使用如下。
(1)、配置
步骤:
新建一普通Java工程,例如howsun-sns;
设定howsun-config.properties中的namespace=howsun_sns
在/opt/hjf/${空间名}目录下手工创建一份Spring配置文件,文件名为${空间名}-sc.xml;即/opt/hjf/howsun_sns/sns-sc.xml
要将cms服务中接口纳入到框架统一服务中管理,需要在接口和实现类上标注:@ContractService,其中实现类上的注解需要指定bean的id
示例:
@ContractService
public interface PersonService{
}
@ContractService("personService")
public class PersonServiceImpl implements PersonService{
}
(2)、跨服务调用
现在www服务需要调用sns服务的PersonService,步骤如下:
新建一Java Web工程,例如howsun-www;
设定howsun-config.properties中的namespace=howsun_www
在/opt/hjf/${空间名}目录下手工创建一份Spring配置文件,文件名为${空间名}-sc.xml;即/opt/hjf/howsun_www/www-sc.xml
在配置文件中装配ContractServices Bean并注入modules属性,如:
在Controller类中注入ContractServices,ContractServices来调用PersonService接口
示例:
//配置文件
//Java文件
@Controller
public class HomeController{
@Resource
private ContractServices contractServices;
@RequestMapping("/{nickname}/index")
public String index(@PathVariable String nickname, Model model){
PersonService personService = contractServices.getContractService(PersonService.class);
model.addAttribute("person",personService.getPerson(nickname));
}
}
6、统一数据库操纵
框架中封装了三种持久层框架,分别是Hibernate、JPA、Mongo4J。其中前两者是针对RDBMS,后者是针对NoSQL MongoDB
使用时只需在配置中增加id为genericDao的bean,实现类可以为上述三种的任意一种
实现类为Hibernate、JPA时,需要注入相应的Session工厂和数据源;为Mongo4j时,需要MongoDB连接池。这些都可以在各自的软件厂商找到使用说明
6.1、重点数据操纵方法
6.1.1、事务方法
■ 保存实体
void save(Object object)
■ 更新对象
void update(Object object)
■ 根据条件更新记录
public int update(Class entityName, String[] fields, Object[] values, Serializable id)
返回更新的结果数
■ 批量更新记录
public int updateByBatch(Class entityName, String fields, String condition, Object[] values)
返回批量更新的记录个数
■ 根据主键值删除记录
public int delete(Class entityClass, Serializable... entityids)
返回删除的记录个数
■ 删除一个已知的记录
public int delete(Object object)
返回删除的记录个数
■ 根据条件和参数删除记录
public int delete(Class entityClass, String condition, Object[] params)
返回删除的记录个数
注意:
这些方法都需要事务,Service层标上@Transactional
MongoDB目前还不支持事务
6.1.2、非事务方法
■ 根据主键值查询一条记录
public T find(Class entityClass, Serializable entityid)
返回与实体名一致的单个实体对象
■ 根据条件查询多条记录,可以设分页、排序
public List finds(Class entityClass, String fields, Page page,String condition, Object[] params, OrderBean order)
返回与实体名一致的多个实体集合对象
■ 统计某表中的总记录数
long getCount(Class entityClass);
返回总记录数
■ 根据条件统计某表中的总记录数
long getCount(Class entityClass, String condition, Object[] params);
返回记录数
■ 模拟自增长主键,不支持同步锁,适合数据频繁低的应用中
Long nextId(Class> entityClass);
返回下一个主键值
注意:
这些方法都不需要事务,Service层标上@Transactional,还需要调用方法上加上@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)以提高性能
6.2、数据分页
构建一个Page对象:new Page(当前页码,每页大小,页码上的URL地址)
将对象送到分页查询方法中去
方法执行完后,将对象放在HttpServletRequest作用域中,Key为Page. SCOPE_NAME
JSP页面上引入框架的标签库:
JSP页面上利用分页标签显示分页结果:
效果示例:
6.3、创建主键
支持大规模数据库集群环境中无重复主键
主键类型:java.lang.Long,因此表中预先要设计为bigint型主键类型
org.howsun.dao.IDGenerator .getUniqueID ()
6.4、增强的Seeker参数查询
只需编写一个自定义的查询参数类实现Seeker接口,或者继承GeneralSeeker类
下面以实现在MongoDB中存储的Person记录查询为例具体说明,首页新建一个PersonSeeker类:
public class PersonSeeker extends GeneralSeeker implements Seeker {
private static final long serialVersionUID = 9183107407293358219L;
/**最大时间间隔查询为90天**/
public static final long TIME_SLICES = 90L * 24 * 3600 * 1000;
static{
ORDER_FIELDS.put("created", "创建时间");
ORDER_FIELDS.put("updated", "修改时间");
ORDER_FIELDS.put("followers", "粉丝数量");
ORDER_FIELDS.put("friends", "好友数量");
ORDER_FIELDS.put("ffs", "互相关注数量");
ORDER_FIELDS.put("birthday", "生日");
ORDER_FIELDS.put("auth.logonWithMonth", "月访问量");
ORDER_FIELDS.put("auth.logonWithWeek", "周访问量");
ORDER_FIELDS.put("auth.logonWithDay", "日访问量");
ORDER_FIELDS.put("auth.logonTotal", "总访问量");
ORDER_FIELDS.put("auth.lastLogonTime", "最后访问时间");
}
/**按用户ID查寻**/
protected Set personIds = new HashSet(0);
/**按昵称查**/
protected String nickname;
/**按真实姓名查**/
protected String realname;
/**按用户名查**/
protected String username;
/**是否锁定账号**/
protected Boolean locked;
/**按授了权的App查**/
protected Set appIds = new HashSet(0);
/**是否存在的字段**/
protected Map existsFields = new HashMap(1,1);
/**按起始创建时间查**/
protected Date startCreated;
/**按结束创建时间查**/
protected Date endCreated;
/**关注我的人**/
protected int followers;
/**好友数量:我关注的人数**/
protected int friends;
/**互相关注数量**/
protected int ffs;
/* (non-Javadoc)
* @see com.chinaot.core.service.Seeker#buildCriteria()
*/
@Override
public Criteria buildCriteria(){
Criteria criteria = new Criteria();
List criteras = new ArrayList(1);
if(Collections.notEmpty(personIds)){
if(personIds.size() == 1){
for(Long id : personIds){
criteras.add(Criteria.where("_id").is(id));
break;
}
}else{
criteras.add(Criteria.where("_id").in(personIds));
}
}
if(Strings.hasLength(username)){
criteras.add(Criteria.where("auth.accounts.username").is(username));
}
//昵称是唯一值,应精确查找
if(Strings.hasLength(nickname)){
criteras.add(Criteria.where("nickname").is(nickname));
}
if(Strings.hasLength(realname)){
criteras.add(Criteria.where("realname").regex(String.format(MongoGenericDaoUtil.REGEX_LIKE, realname)));
}
if(followers > 0){
criteras.add(Criteria.where("followers").gte(followers));
}
if(friends > 0){
criteras.add(Criteria.where("friends").gte(friends));
}
if(ffs > 0){
criteras.add(Criteria.where("ffs").gte(ffs));
}
if(Collections.notEmpty(appIds)){
criteras.add(Criteria.where("apps.appId").in(appIds));
}
if(locked != null){
List temps = new ArrayList();
temps.add(Criteria.where("auth.locked").exists(false));
temps.add(Criteria.where("auth.locked").is(locked));
criteras.add(new Criteria().orOperator(temps.toArray(new Criteria[]{})));
}
if(existsFields != null && existsFields.size() > 0){
for(Map.Entry exist : existsFields.entrySet()){
criteras.add(Criteria.where(exist.getKey()).exists(exist.getValue()));
}
}
if(criteras.size() > 0){
criteria.andOperator(criteras.toArray(new Criteria[]{}));
}
return criteria;
}
@Override
public RQLConditionbind buildRQL() {
return throw new RuntimeException("不支持RDBMS数据库查询");
}
//Setter&Getter...
}
public class PersonServiceImpl implements PersonService{
//在Service类实现查询的方法,仅如下几行代码就够了
public List<Person> getPersons(Seeker seeker, Set<String> fields, Page page){
//创建MongoDB查询对象
Query query = Query.query(seeker.buildCriteria());
//需要查询哪些字段
MongoGenericDaoUtil.bindFields(query, PersonUtil.checkFields(fields));
//排序
MongoGenericDaoUtil.bindOrders(query, seeker.getOrderBean());
//分页
MongoGenericDaoUtil.bindPaging(Person.class, dao.getMongoTemplate(), query, page);
//查询结果
List<Person> persons = dao.getMongoTemplate().find(query, Person.class);
return personj;
}
}
可见使用Seeker接口的参数查询,业务层代码更加简洁。
7、Redis缓存客户端封装
注意:Redis客户端使用的是jedis。
配置文件:
注意:由于jedis内部使用了线程池,客户没必须使用多例对象,这里做了个CacheHelper静态工具类,当客户端对象在实例化时,同步为CacheHelper赋值,所以在所有要访问缓存的地方直接调用CacheHelper.get(xxx)、CacheHelper.set(xxx,xx)方法即可
8、工具包
■安全工具
org.howsun.util.security.Codings
org.howsun.util.security.AES
■字符串工具
org.howsun.util.Strings
■日期工具
org.howsun.util.Dates
■断言工具
org.howsun.util. Asserts
■JavaBean工具
org.howsun.util. Beans
■中文拼音工具
org.howsun.util.ChinesePinyin
■文件工具
org.howsun.util. Files
■文件流工具
org.howsun.util.Streams
■IO工具
org.howsun.util.IOs
■数值工具
org.howsun.util.Numbers
■随机数工具
org.howsun.util.Randoms
■IP工具
org.howsun.util.Ips
■Web工具
org.howsun.util.Webs
其它未列的请查看JavaDoc说明
敬告:howsun2.0将不再兼容1.x版本,新版是居于Spring4.0重新架构设计的,敬请期待。
作者:张纪豪(zhangjihao@sohu.com)