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引入


<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

 

3、定制统一配置的名称空间

步骤:

统一这种配置后,框架会在工程所在的磁盘根目录下创建opt/hjf/{namespace}子目录,用于存放配置文件、日志文件。

 

4、统一日志管理

 

5、统一服务管理

以实际例子说明应用,假如某项目含有如下服务:

WWW:主站服务,既是个Web工程

CMS:资源管理服务,重点是数据存储和操纵。普通Java工程

SNS:用户管理服务,并含有复杂社交关系应用。普通Java工程

如果WWW要调用CMS服务中接口,就涉及到跨服务,目前1.8版是跨Spring容器来实现的,具体使用如下。

 

(1)、配置

步骤:

  1. 新建一普通Java工程,例如howsun-sns;
  2. 设定howsun-config.properties中的namespace=howsun_sns
  3. 在/opt/hjf/${空间名}目录下手工创建一份Spring配置文件,文件名为${空间名}-sc.xml;即/opt/hjf/howsun_sns/sns-sc.xml
  4. 要将cms服务中接口纳入到框架统一服务中管理,需要在接口和实现类上标注:@ContractService,其中实现类上的注解需要指定bean的id

示例:

@ContractService
public interface PersonService{

}

@ContractService("personService")
public class PersonServiceImpl implements PersonService{

}

(2)、跨服务调用

现在www服务需要调用sns服务的PersonService,步骤如下:

  1. 新建一Java Web工程,例如howsun-www;
  2. 设定howsun-config.properties中的namespace=howsun_www
  3. 在/opt/hjf/${空间名}目录下手工创建一份Spring配置文件,文件名为${空间名}-sc.xml;即/opt/hjf/howsun_www/www-sc.xml
  4. 在配置文件中装配ContractServices Bean并注入modules属性,如:<entry key="sns" value="howsun_sns"/>
  5. 在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));
      }
}

 

6、统一数据库操纵

6.1、重点数据操纵方法

6.1.1、事务方法

■ 保存实体
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)
返回删除的记录个数

注意:

 

6.1.2、非事务方法

■ 根据主键值查询一条记录
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)以提高性能

 

6.2、数据分页

<%@taglib uri="http://www.howsun.org/tags/page" prefix="howsun"%>

    <howsun:pagination/>


6.3、创建主键

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<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
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。

 

配置文件:

<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)方法即可

 

 

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重新架构设计的,敬请期待。

 

作者:张纪豪([email protected])