Java应用层框架
版本:1.0.8
这是一款居于Spring容器之上特别适用于中小企业应用的JavaEE快速开发框架,具有如下特性:
1、跨服务调用(跨Spring容器,也可以使用类似Netty的通信中间件来实现)
2、封装DAO操作,大大简化了数据库操纵业务,统一的查询参数接口,统一的分页对象,可创建单机可集群环境的数据唯一ID。支持Hibernate,JPA和MongoDB操纵
3、统一配置管理,配置文件不随工程一起发布,可以有效地避免线上线下配置文件混乱问
4、统一日志管理,不需要做太多的日志配置,自动生成日志配置模板,并将日志记录到指定的文件夹中
5、封装了Redis客户端,取代Memcache
6、简化了Json操作
7、提供了分页、工具类封装的JSP标签库
8、大量工具包:如安全、Web、断言、编码等40多种
Maven引入
<dependency> <groupId>org.howsun</groupId> <artifactId>howsun-jee-framework</artifactId> <version>1.0.8</version> </dependency> |
需要注意,必须保证内部仓库中已经存在构件。
非Maven工程,请在build目录下载howsun-jee-framework-1.0.8.jar,并手工将其和依赖构件全部加入工程的ClassPath
步骤:
- 在工程Source下创建META-INF目录
- 目录中创建howsun-config.properties配置文件
- 文件中只需一行:namespace=空间名
统一这种配置后,框架会在工程所在的磁盘根目录下创建opt/hjf/{namespace}子目录,用于存放配置文件、日志文件。
- 框架运行时,第一次会在项目同分区中创建/opt/hjf/${空间名}目录,并自动写一份log4j.properties的配置文件
- 日志级别默认为info
- 日志分别往控制台和文件中传送
- 日志内容文件在/opt/hjf/logs/${空间名}.log
- 按天切分存储
以实际例子说明应用,假如某项目含有如下服务:
WWW:主站服务,既是个Web工程
CMS:资源管理服务,重点是数据存储和操纵。普通Java工程
SNS:用户管理服务,并含有复杂社交关系应用。普通Java工程
如果WWW要调用CMS服务中接口,就涉及到跨服务,目前1.8版是跨Spring容器来实现的,具体使用如下。
步骤:
- 新建一普通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{}
现在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属性,如:<entry key="sns" value="howsun_sns"/>
- 在Controller类中注入ContractServices,ContractServices来调用PersonService接口
示例:
//配置文件 <bean id="contractServices" class="org.howsun.core.ContractServices" >
<property name="modules">
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="sns" value="howsun_sns"/>
</map>
</property>
</bean> //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));
}
}
- 框架中封装了三种持久层框架,分别是Hibernate、JPA、Mongo4J。其中前两者是针对RDBMS,后者是针对NoSQL MongoDB
- 使用时只需在配置中增加id为genericDao的bean,实现类可以为上述三种的任意一种
- 实现类为Hibernate、JPA时,需要注入相应的Session工厂和数据源;为Mongo4j时,需要MongoDB连接池。这些都可以在各自的软件厂商找到使用说明
■ 保存实体
void save(Object object)
■ 更新对象
void update(Object object)
■ 根据条件更新记录
public <T> int update(Class<T> entityName, String[] fields, Object[] values, Serializable id)
返回更新的结果数
■ 批量更新记录
public <T> int updateByBatch(Class<T> entityName, String fields, String condition, Object[] values)
返回批量更新的记录个数
■ 根据主键值删除记录
public <T> int delete(Class<T> entityClass, Serializable... entityids)
返回删除的记录个数
■ 删除一个已知的记录
public int delete(Object object)
返回删除的记录个数
■ 根据条件和参数删除记录
public <T> int delete(Class<T> entityClass, String condition, Object[] params)
返回删除的记录个数
注意:
- 这些方法都需要事务,Service层标上@Transactional
- MongoDB目前还不支持事务
■ 根据主键值查询一条记录
public <T> T find(Class<T> entityClass, Serializable entityid)
返回与实体名一致的单个实体对象
■ 根据条件查询多条记录,可以设分页、排序
public <T> List<T> finds(Class<T> entityClass, String fields, Page page,String condition, Object[] params, OrderBean order)
返回与实体名一致的多个实体集合对象
■ 统计某表中的总记录数
<T> long getCount(Class<T> entityClass);
返回总记录数
■ 根据条件统计某表中的总记录数
<T> long getCount(Class<T> entityClass, String condition, Object[] params);
返回记录数
■ 模拟自增长主键,不支持同步锁,适合数据频繁低的应用中
Long nextId(Class<?> entityClass);
返回下一个主键值
注意:
这些方法都不需要事务,Service层标上@Transactional,还需要调用方法上加上@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)以提高性能
- 构建一个Page对象:new Page(当前页码,每页大小,页码上的URL地址)
- 将对象送到分页查询方法中去
- 方法执行完后,将对象放在HttpServletRequest作用域中,Key为Page. SCOPE_NAME
- JSP页面上引入框架的标签库:
<%@taglib uri="http://www.howsun.org/tags/page" prefix="howsun"%> |
- JSP页面上利用分页标签显示分页结果:
<howsun:pagination/> |
- 效果示例:
- 支持大规模数据库集群环境中无重复主键
- 主键类型:java.lang.Long,因此表中预先要设计为bigint型主键类型
org.howsun.dao.IDGenerator .getUniqueID () |
只需编写一个自定义的查询参数类实现Seeker接口,或者继承GeneralSeeker类
下面以实现在MongoDB中存储的Person记录查询为例具体说明,首页新建一个PersonSeeker类:
可见使用Seeker接口的参数查询,业务层代码更加简洁。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<Long> personIds = new HashSet<Long>(0);/**按昵称查**/
protected String nickname;/**按真实姓名查**/
protected String realname;/**按用户名查**/
protected String username;/**是否锁定账号**/
protected Boolean locked;/**按授了权的App查**/
protected Set<Long> appIds = new HashSet<Long>(0);/**是否存在的字段**/
protected Map<String,Boolean> existsFields = new HashMap<String, Boolean>(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<Criteria> criteras = new ArrayList<Criteria>(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<Criteria> temps = new ArrayList<Criteria>();
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<String, Boolean> 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
//Setter&Getter... }
public RQLConditionbind buildRQL() {
return throw new RuntimeException("不支持RDBMS数据库查询");
}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;
}
}
注意:Redis客户端使用的是jedis。
配置文件:
<bean id="server1" class="org.howsun.redis.ShardedServer" >
<constructor-arg index="0" value="IP地址" />
<constructor-arg index="1" value="6379"/>
<constructor-arg index="2" value="local"/>
<property name="password" value="密码"/>
<property name="timeout" value="1000"/><!-- 小于connectionFactoryConfig.maxWait -->
</bean>
<bean id="cacheFactoryConfig" class="org.howsun.redis.CacheFactoryConfig" >
<property name="maxActive" value="5" />
<property name="maxIdle" value="10" />
<property name="minIdle" value="3" />
<property name="maxWait" value="1200" /><!--1.2秒之内必须返回-->
<property name="whenExhaustedAction" value="1" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<property name="numTestsPerEvictionRun" value="-1" />
<property name="minEvictableIdleTimeMillis" value="60000" />
<property name="softMinEvictableIdleTimeMillis" value="-1" />
<property name="servers">
<list>
<ref bean="server1" />
</list>
</property>
</bean>
<bean id="cacheFactory" class="org.howsun.redis.ShardedCacheFactory" >
<constructor-arg index="0" ref="cacheFactoryConfig" />
</bean>
<bean id="cacheService" class="org.howsun.redis.JedisCacheService" destroy-method="destroy">
<property name="cacheFactory" ref="cacheFactory" />
<property name="serializer" >
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<property name="report" value="false" />
<property name="callback">
<bean class="com.chinaot.core.cache.CacheHelper"/>
</property>
</bean>
注意:由于jedis内部使用了线程池,客户没必须使用多例对象,这里做了个CacheHelper静态工具类,当客户端对象在实例化时,同步为CacheHelper赋值,所以在所有要访问缓存的地方直接调用CacheHelper.get(xxx)、CacheHelper.set(xxx,xx)方法即可
■安全工具
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)