4.特性
4.1MVC
借鉴 Spring MVC 与 RESTful Web Service 的思路,打造了一款轻量级 MVC 框架。Model 由 Entity 或 JavaBean 充当,View 为 JSON 格式的数据(也可为 XML 格式),Controller 在框架中被命名为 Action。相比1.0版本,不再继承BaseAction,减少了依赖。
4.1.1MVC注解
- @Action:定义Action类,只有标记过的类才会被转发。
- @Request:定义URL映射规则。
4.1.2映射规则
在 @Request 注解的参数中,使用“请求方法 + 请求地址”的格式定义一个完整的请求。- 请求方法:
GET、POST、PUT、DELETE四种,不区分大小写(推荐大写)。 - 请求地址: 请求地址要以“/”开头,末尾无需“/”,同样不区分大小写(推荐小写)。在地址中,允许使用一个或多个带“{xxx}”格式的占位符,依次对应 Action 方法中的参数,类型一般为 long 或 String。可使用 Map<String, Object> 封装所有请求参数,key 为参数名,value 为参数值,可根据实际情况对 Object 类型的参数值进行转型。
@Action
public class ProductAction {
@Request("GET:/product")
public Page index() {
......
}
@Request("POST:/product/create")
public Result create(Map<String, Object> fieldMap, List<Multipart> multipartList) {
......
}
@Request("PUT:/product/update/{id}")
public Result update(long id, Map<String, Object> fieldMap) {
......
}
@Request("DELETE:/product/delete/{id}")
public Result delete(long id) {
......
}
}
4.1.3Action方法
- Action方法参数
允许定义空参数、long、Map 、List四种类型,以及它们之间的各种组合。例如:public Page index(); public Result delete(long id) public Page search(Map<String, Object> fieldMap) public Result update(long id, Map<String, Object> fieldMap) public Result create(List<Multipart> multipartList) public Result create(Map<String, Object> fieldMap, List<Multipart> multipartList) public Result uploadPicture(long id, Map<String, Object> fieldMap, List<Multipart> multipartList) ......
- Action方法返回值
Action方法返回值允许为void、Page、Result三种。
a. 以void为返回值的方法,处理完业务后,不做后续操作,例如下载等。
b. 以Page为返回值的方法,则转发或重定向到相应的页面中。
Page常用方法:
void setPath(String path);//设置路径 void setData(Map<String, Object> data);//设置参数
c. 以Result为返回值的方法,则返回包含基本信息的json字符串。
Result常用方法:
void setSuccess(boolean success);//设置成功标志 void setError(int error);//设置错误代码 void setData(Object data);//设置响应数据
4.2IOC
借鉴 Spring IOC 的思路,可将任意的类标注为@Bean,当应用启动时,框架会自动创建这些 Bean实例,并放入容器中。随后可使用 BeanHelper.getBean() 方法获取对应的 Bean实例。一般情况下,会将 Action类标注为@Action,Service实现类标注为 @Service,可在 Action类中使用 @Inject注解注入所需的 Service接口。
Action类:
@Action
public class ProductAction {
@Inject
private ProductService productService;
...
}
Service 实现类:
@Service
public class ProductServiceImpl implements ProductService {
...
}
若在项目中一个接口同时存在多个实现类,此时只能选择其中的一个实现类生效,可在接口上使用 @Impl指定具体的实现类。
4.3AOP
借鉴 Spring 与 AspectJ 的思路,去掉了复杂的切点表达式,简化为指定包名与指定类名进行横向拦截,需使用 @Aspect 注解定义一个 Aspect 类,并继承 AspectProxy方法,通过覆盖父类中的某些钩子方法,来实现编写对应的增强代码。包括以下几种增强类型,即钩子方法:
方法 |
说明 |
void begin() |
在进入方法时执行。 |
boolean intercept(Method method, Object[] args) |
设置过滤条件。默认返回 true,表示无过滤条件。 |
void before(Method method, Object[] args) |
在目标方法调用前执行。 |
void after(Method method, Object[] args) |
在目标方法调用后执行。 |
void error(Method method, Object[] args, Exception e) |
在抛出异常时执行。 |
void end() |
在方法执行完毕前执行。 |
4.3.1AOP注解
- @Aspect:定义需要拦截的类,只有满足条件的类或方法才会被拦截。
参数:pkg定义需要拦截的包名。
cls需要拦截的类名。如果为空,则拦截该包下的所有类;否则如果包名与类名均不为空,则添加指定类。 - @Order:多个拦截方法的执行顺序。
参数:value序号的值越小越靠前
以下编写了一个 Aspect 类,用于横切 com.smart.sample.action下所有类的方法,除了SystemAction类,代码如下:
@Aspect(pkg = "com.smart.sample.action")
@Order(0)
public class AccessAspect extends AspectProxy {
@Override
public boolean intercept(Class<?> cls, Method method, Object[] params) throws Exception {
boolean result = true;
if (cls == SystemAction.class) {
result = false;
}
return result;
}
@Override
public void before(Class<?> cls, Method method, Object[] params) throws Exception {
Long userId = DataContext.Session.get(Constant.USER_ID);
if (userId == null) {
WebUtil.setRedirectURL(DataContext.getRequest(), Constant.REDIRECT_URL);
throw new AccessException();
}
}
}
可在 intercept() 方法中,对目标方法(Method 对象)的名称、参数、返回值、注解等信息设置过滤条件。
4.4ORM
借鉴 JPA 的思路,对 ORM 映射规则进行了简化,尽量减少相关的注解配置。每个 Entity 类必须继承 BaseEntity,该父类中提供了实体主键字段,名称为 id,类型为 long,在实际项目中可对主键字段进行统一修改。通过一个简单的示例来说明 ORM 映射规则,代码如下:
public class Product extends BaseEntity {
private long productTypeId;
private String productName;
private String productCode;
private int price;
private String description;
... getter/setter 方法
}
映射规则如下:
Entity 类 |
数据库 |
类名(如:Product) |
表名(如:product) |
属性名(如:productTypeId) |
列名(如:product_type_id) |
可见,将 Entity 类的类名与属性名的“驼峰式”转为“下划线式”,即为数据库的表名与列名。若表名带有前缀(如:t_product),则此时需要借助 @Table 注解,将其标注在 Entity 类上,在该注解的参数中指定所对应的表名(如:@Table(“t_product”))。
同理,对于属性名与列名的不规则映射规则,可以借助 @Column 注解实现映射关系。当列名为 Java 关键字时(如:class),需要考虑此方案。
在本框架中使用“贫血式”模型,不使用 OneToMany、ManyToOne、ManyToMany 等“充血式”模型,也就是说,一个 Entity 与一个 Table 对应,一个 Property 与一个 Column 对应。建议使用 Java 原始类型,而不是封装类型,如:建议使用 int,而不是 Integer。
Entity 属性类型只能从以下类型中选择:
类型 |
说明 |
int |
对于长度较小的数值类型数据,推荐使用 int 类型,不要使用 Integer 类型。 |
long |
对于长度较长的数值类型数据,推荐使用 long 类型,不要使用 Long 类型。 |
double |
对于所有的浮点类型数据,推荐使用 double 类型,不要使用 Double、float/Float 等类型。 |
String |
对于字符类型数据,推荐使用 String 类型,不要使用 char 类型。 |
注意:对于日期或时间,推荐使用 String 或 long 类型,而不要使用 Date 类型。
4.5DAO
封装 Apache DbUtils 类库,提供 DataSet 工具类,可执行基于单表的 SQL 语句,详细的 DataSet API 请见附录。对于复杂的 SQL 语句,可编写在代码中,也可编写在 sql.properties 文件中,通过 SQLHelper.getSQL() 方法来获取具体的 SQL 语句,可配合使用 DBHelper 来执行 SQL 语句。
4.6事务控制
借鉴 Spring 声明式事务控制的思路,提供了一个 @Transaction 注解,将此注解标注在 Service 实现类中打算进行事务控制的方法上,该方法在运行时就会具备事务特性。事务使用默认的传播行为,本框架不考虑复杂的传播行为,也不考虑嵌套事务与跨库事务。相比1.0版本,新增@Service注解,用于区分其他类型的Bean,作用和@Bean注解一样。
4.7异常处理
采用 C/C++ 编码风格,采用错误代码取代异常处理机制,该方案仅用于 Action 方法中。通过一个简单的示例展示基于错误代码的异常处理机制,代码如下:@Action
public class ProductAction {
@Inject
private ProductService productService;
...
@Request("get:/product/{id}")
public Result getProductById(long productId) {
if (productId == 0) {
return new Result(false).error(ERROR_PARAM);
}
Product product = productService.getProduct(productId);
if (product != null) {
return new Result(true).data(product);
} else {
return new Result(false).error(ERROR_DATA);
}
}
...
}
当 productId 为 0 时,返回一个 Result 对象,成功标志为 false,错误代码 ERROR_PARAM(值为 10)。错误代码可统一定义。
同理,当 product 不为 null 时,返回一个 Result 对象,成功标志为 true,相关数据为 product,否则,也返回一个 Result 对象,成功标志为 false,错误代码为 ERROR_DATA(值为 20)。Action 方法的返回值,也就是 Result 对象,将会序列化为 JSON 格式的数据,通过 AJAX 的方式返回到回调函数中,可通过获取 result 对象的 success 属性来判断操作是否成功,可访问 error 属性获取相应的错误代码,并给出具体的提示信息。AJAX 代码示例如下:
...
$.ajax({
url: '/product/' + productId,
type: 'get',
success: function(result) {
if (result.success) {
var product = result.data;
$('#product_type_id').val(product.productTypeId);
$('#product_name').val(product.productName);
$('#product_code').val(product.productCode);
$('#price').val(product.price);
$('#description').val(product.description);
} else {
switch (result.error) {
case 10:
alert('The parameter is error!');
break;
case 20:
alert('The data is error!');
break;
}
}
}
});
...
4.8单元测试
使用 JUnit 作为单元测试框架,并对其进行了扩展,可使用 @Order 注解设置被测方法的执行顺序。以下是一个 Test 类,代码如下:public class ProductServiceTest extends BaseTest {
private ProductService productService = BeanHelper.getBean(ProductServiceImpl.class);
@BeforeClass
@AfterClass
public static void init() {
initSQL("sql/product.sql");
}
@Test
@Order(1)
public void getProductListTest() {
List<Product> productList = productService.getProductList();
Assert.assertNotNull(productList);
Assert.assertEquals(productList.size(), 7);
}
@Test
@Order(2)
public void getProductTest() {
long productId = 1;
Product product = productService.getProduct(productId);
Assert.assertNotNull(product);
}
...
}
Test 类必须继承 BaseTest 类。在类中可使用 BeanHelper.getBean() 方法初始化相关的 Bean 实例(这里是 Service 实现类实例),注意:此时不能使用 @Inject 实现依赖注入。可使用 JUnit 提供的 @Test、@BeforeClass、@AfterClass 等注解来标注被测方法。在 init() 方法上同时标注了 @BeforeClass 与 @AfterClass,表示该方法会在测试之前与测试之后被 JUnit 框架所调用,用于执行数据初始化脚本,该脚本在 test/resources/sql/ 目录下。
可在 test/resources/config.prperties 文件中单独配置单元测试所对应的数据源,一般情况下一个应用对应两个数据库,一个作为开发,另一个作为测试。 建议保证单元测试方法的顺序性,并合理使用 @Order 注解,让整个单元测试可反复测试。
4.9MVC配置文件
MVC中所有涉及到的参数配置都在根类路径下config.properties文件中。参数说明见:#应用名称
app.name=smart-sample
#Action类所在的包
app.package=com.smart.sample
#网站静态资源路径
app.www_path=/www/
#jsp路径
app.jsp_path=/WEB-INF/jsp/
#登录地址
app.home_page=/login
#上传文件尺寸大小(MB)
app.upload_limit=10
#数据库类型
jdbc.type=mysql
#驱动名
jdbc.driver=com.mysql.jdbc.Driver
#数据库链接地址
jdbc.url=jdbc:mysql://localhost:3306/sample
#登录名
jdbc.username=root
#密码
jdbc.password=root
#是否动态加载i18n文件
i18n.reloadable=true
#上传路径
sample.upload_path=/www/upload/
来源:oschina
链接:https://my.oschina.net/u/59256/blog/195401