core-src
log包:
Log4jMBean: 基于JMX动态配置Logger日志等级, 获取Logger Appender的MBean.
//获取日志级别
@ManagedAttribute(description = "Logging level of the root logger")
public String getRootLoggerLevel() {
Logger root = Logger.getRootLogger();
return root.getLevel().toString();
}
// 设置Root Logger的日志级别.
@ManagedAttribute(description = "Logging level of the root logger")
public void setRootLoggerLevel(String newLevel) {
Logger root = Logger.getRootLogger();
Level level = Level.toLevel(newLevel);
root.setLevel(level);
mbeanLogger.info("设置Root Logger级别到{}", newLevel);
}
//最后看一下如何获取一个Logger的所有Appender, 加深理解一下Log4j的原理:
private List<String> getLoggerAppenders(Logger logger) {
List<String> appenderNameList = new ArrayList<String>();
//循环加载logger及其parent的appenders
// 下面一段代码也就是Log4j中标准的代码
// 一个logger的所有appender, 不光是它自己的appender, 还包括所有祖先logger的
//appender
for (Category c = logger; c != null; c = c.getParent()) {
Enumeration e = c.getAllAppenders();
while (e.hasMoreElements()) {
Appender appender = (Appender) e.nextElement();
String appenderName = appender.getName();
if (c != logger) {//NOSONAR
appenderName += "(parent)";
}
appenderNameList.add(appenderName);
}
//如果logger不继承parent的appender,则停止向上循环.
if (!c.getAdditivity()) {
break;
}
}
return appenderNameList;
}
MockLog4jAppender类: 之前已经分析过了, 就是一个模拟的appender, 把所有logger的event记录在一个list中.
orm包:
Page类: 与具体ORM实现无关的分页参数及查询结果封装.
值得注意的是, 这是一个泛型类. 不过, 我不太明白的是, Calvin难道要把非常大的结果集放到这个对象中, 然后靠index来在
内存中检查分页数据?
这个等待miniweb时候再看看吧.
PropertyFilter类: 感觉很笨重, 没看明白, 不分析了...还是使用Hibernate的Restrictions和Criterion
SqlBuilder类: 使用Velocity生成sql的工具类.
calvin这个类不是用来解析模板的, 注意, getSql(String sqlTemplate, Map<String, ?> model)方法中的
sqlTemplate参数是指具体的模板内容, Calvin只是用velocity来做字符串解析用的
附velocity模板语法:
"$"打头是VTL变量,"#"打头表示这个是一个VTL指令
VTL语言中存在三种引用:变量、属性和方法。模板中使用的这三种引用Velocity都会转换为相应的JAVA方法。Velocity是通过调用JAVA对象的tostring()方法来实现替换的。
$user.name属性Velocity将转换为java语言的User.getName()
普通引用$my_first-name 哑引用$!my_first-name 正式引用:${my_first-name}
#set--变量定义和赋值指令
#if--条件判断指令
#foreach--数组循环遍历指令
Velocity提供一个循环计数器$velocityCount供使用
#include--文件包含指令
#parse--模板解析指令
#stop--停止指令
#marco--自定义宏(函数)
现在来看hibernate包:
SimpleHibernateDao类: 封装Hibernate原生API的DAO泛型基类.可在Service层直接使用,也可以扩展泛型DAO子类使用.
看一下类的定义:
class SimpleHibernateDao<T, PK extends Serializable>
T, PK extends Serializable 这句话说明, T, PK都是继承自Serializable
看一下如何使用这个dao类:
dao = new SimpleHibernateDao<User, Long>(sessionFactory, User.class);
看看构造函数, 很有意思:
/**
* 用于Dao层子类使用的构造函数.
* 通过子类的泛型定义取得对象类型Class.
* eg.
* public class UserDao extends SimpleHibernateDao<User, Long>
*/
//明白了吧? 根据反射得到了单签dao负责的对象
public SimpleHibernateDao() {
this.entityClass = ReflectionUtils.getSuperClassGenricType(getClass());
}
/**
* 用于用于省略Dao层, 在Service层直接使用通用SimpleHibernateDao的构造函数.
* 在构造函数中定义对象类型Class.
* eg.
* SimpleHibernateDao<User, Long> userDao = new SimpleHibernateDao<User, Long>(sessionFactory, User.class);
*/
//在service中直接使用
public SimpleHibernateDao(final SessionFactory sessionFactory, final Class<T> entityClass) {
this.sessionFactory = sessionFactory;
this.entityClass = entityClass;
}
//保存新增或修改的对象.
public void save(final T entity) {
Assert.notNull(entity, "entity不能为空");
getSession().saveOrUpdate(entity);
logger.debug("save entity: {}", entity);
}
//按id获取对象.
public T get(final PK id) {
Assert.notNull(id, "id不能为空");
return (T) getSession().load(entityClass, id);
}
//获取全部对象,支持排序.
public List<T> getAll(String orderBy, boolean isAsc) {
Criteria c = createCriteria();
if (isAsc) {
c.addOrder(Order.asc(orderBy));
} else {
c.addOrder(Order.desc(orderBy));
}
return c.list();
}
//按属性查找对象列表,匹配方式为相等.
public List<T> findBy(final String propertyName, final Object value) {
Assert.hasText(propertyName, "propertyName不能为空");
Criterion criterion = Restrictions.eq(propertyName, value);
return find(criterion);
}
//执行hql:
public int batchExecute(final String hql, final Object... values) {
return createQuery(hql, values).executeUpdate();
}
public Query createQuery(final String queryString, final Object... values) {
Assert.hasText(queryString, "queryString不能为空");
Query query = getSession().createQuery(queryString);
if (values != null) {
for (int i = 0; i < values.length; i++) {
query.setParameter(i, values[i]);
}
}
return query;
}
关于lazy:
/**
* 初始化对象.
* 使用load()方法得到的仅是对象Proxy, 在传到View层前需要进行初始化.
* 只初始化entity的直接属性,但不会初始化延迟加载的关联集合的元素和属性.
* 如需初始化关联属性,可实现新的函数,执行:
* Hibernate.initialize(user.getRoles()),初始化User的直接属性和关联集合.
* Hibernate.initialize(user.getDescription()),初始化User的直接属性和延迟加载的Description属性.
*/
public void initProxyProperty(Object proxyProperty) {
Hibernate.initialize(proxyProperty);
}
HibernateDao类:
继承了SimpleHibernateDao, 并且做了一些功能上的扩展:
扩展功能包括分页查询
//按Criteria分页查询.
//首先查询能够获得的对象总数
//然后把总数设置到Page对象中
//然后开始设置Page的其他参数
//最后执行查询,把总的查询结果放入到Page中
//思考: 如果返回结果很大, 那么都放在内存中不太好
//我感觉解决方法: 要么使用memcached, 要么使用还是在sql级别分页
//要么使用count获得了总数, 然后再Page中持有一个Iterator也是一个不错的选择
public Page<T> findPage(final Page<T> page, final Criterion... criterions) {
Assert.notNull(page, "page不能为空");
Criteria c = createCriteria(criterions);
if (page.isAutoCount()) {
int totalCount = countCriteriaResult(c);
page.setTotalCount(totalCount);
}
setPageParameter(c, page);
List result = c.list();
page.setResult(result);
return page;
}
其他的没有什么看的价值, 不看也罢.
分页Page这个对象做的很精巧, 可以适当修改一下, 改进一下性能问题.
再看security包:
SpringSecurityUtils类:SpringSecurity的工具类.
//取得当前用户, 返回值为SpringSecurity的User类或其子类, 如果当前用户未登录则返回null.
public static <T extends User> T getCurrentUser() {
Authentication authentication = getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof User) {
return (T) principal;
}
}
return null;
}
//取得Authentication, 如当前SecurityContext为空时返回null.
private static Authentication getAuthentication() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
return context.getAuthentication();
}
return null;
}
//取得当前用户的登录名, 如果当前用户未登录则返回空字符串.
public static String getCurrentUserName() {
Authentication authentication = getAuthentication();
if (authentication != null && authentication.getPrincipal() != null) {
return authentication.getName();
}
return "";
}
//取得当前用户登录IP, 如果当前用户未登录则返回空字符串.
public static String getCurrentUserIp() {
Authentication authentication = getAuthentication();
if (authentication != null) {
Object details = authentication.getDetails();
if (details instanceof WebAuthenticationDetails) {
WebAuthenticationDetails webDetails = (WebAuthenticationDetails) details;
return webDetails.getRemoteAddress();
}
}
return "";
}
//判断用户是否拥有角色, 如果用户拥有参数中的任意一个角色则返回true.
public static boolean hasAnyRole(String[] roles) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<GrantedAuthority> granteds = authentication.getAuthorities();
for (String role : roles) {
for (GrantedAuthority authority : granteds) {
if (role.equals(authority.getAuthority())) {
return true;
}
}
}
return false;
}
//将UserDetails保存到Security Context.
public static void saveUserDetailsToContext(UserDetails userDetails, HttpServletRequest request) {
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(userDetails,
userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
没有用过spring security, 所以就是简单的理解了一下.
再看一下Test包, 这个包主要都是一些测试的工具类:
groups包:
先看一个有趣的定义: @interface
这就是定义了一个annotation
//实现TestNG Groups分组执行用例功能的annotation.
public @interface Groups {
/**
* 执行所有组别的测试.
*/
final String ALL = "all";
/**
* 组别定义,默认为ALL.
*/
String value() default ALL;
}
GroupsTestRunner类:
实现TestNG Groups分组执行用例功能的TestRunner.
比如可以在白天持续执行速度较快的测试方法, 到了晚上再执行较慢的测试方法.
Runner会只执行测试类的@Groups定义, 与在-Dtest.groups=xxx,xxx相吻合的测试类及测试方法.
/** 在Properties文件或JVM参数-D中定义执行分组的变量名称. */
public static final String PROPERTY_NAME = "test.groups";
从环境变量读取test.groups定义, 多个group用逗号分隔.
String groupsDefine = System.getProperty(PROPERTY_NAME);
//判断一个方法是否需要运行
//取得方法上的Groups annotation, 如果无Groups注解或注解符合分组要求则返回true.
Groups groupsAnnotation = testMethod.getAnnotation(Groups.class);
if ((groupsAnnotation == null) || groups.contains(groupsAnnotation.value())) {
return true;
}
DataUtils类: 数据生成工具类.
很简单, 就是生成一些随机名字,随机数字等.
SeleniumUtils类: 简单封装的自动化工具Selenium的工具类.
buildDriver方法: 根据不同的浏览器类型, 创建WebDriver
剩下的就是包装一下Selenium的api. 具体参考Selenium的api
JettyUtils类:
public static Server buildDebugServer(int port, String contextPath) {
Server server = new Server(port);
WebAppContext webContext = new WebAppContext("D:\\privatePj\\springsideold\\WebRoot",
contextPath);
webContext.setClassLoader(Thread.currentThread()
.getContextClassLoader());
server.setHandler(webContext);
server.setStopAtShutdown(true);
return server;
}
这里面关键是理解WebAppContext, 其构造函数第一个参数是一个Web项目的位置
第二个参数是contextPath, 即使上下文路径
测试:
public static void main(String[] args) throws Exception {
Server server = JettyUtils.buildDebugServer(9988, "/");
server.start();
}
访问:
http://localhost:9988/
就可以看到index.html页面的内容了
DbUnitUtils类: dbunit的包装类
//在ClassPath中查找XML数据文件并执行DBUnit Operation.
private static void execute(DatabaseOperation operation, DataSource h2DataSource, String... xmlFilePaths)
throws DatabaseUnitException, SQLException {
IDatabaseConnection connection = new H2Connection(h2DataSource.getConnection(), "");
for (String xmlPath : xmlFilePaths) {
try {
InputStream input = resourceLoader.getResource(xmlPath).getInputStream();
IDataSet dataSet = new FlatXmlDataSetBuilder().setColumnSensing(true).build(input);
operation.execute(connection, dataSet);
} catch (IOException e) {
logger.warn(xmlPath + " file not found", e);
}
}
}
帖一下xml文件的格式:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<SS_USER ID="1" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="admin@springside.org.cn" LOGIN_NAME="admin" NAME="Admin" PLAIN_PASSWORD="admin" STATUS="enabled"/>
<SS_USER ID="2" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="user@springside.org.cn" LOGIN_NAME="user" NAME="Calvin" PLAIN_PASSWORD="user" STATUS="enabled"/>
<SS_USER ID="3" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="jack@springside.org.cn" LOGIN_NAME="user2" NAME="Jack" PLAIN_PASSWORD="user2" STATUS="enabled"/>
<SS_USER ID="4" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="kate@springside.org.cn" LOGIN_NAME="user3" NAME="Kate" PLAIN_PASSWORD="user3" STATUS="enabled"/>
<SS_USER ID="5" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="sawyer@springside.org.cn" LOGIN_NAME="user4" NAME="Sawyer" PLAIN_PASSWORD="user4" STATUS="enabled"/>
<SS_USER ID="6" CREATE_TIME="2009-07-11 08:52:25.805" EMAIL="ben@springside.org.cn" LOGIN_NAME="user5" NAME="Ben" PLAIN_PASSWORD="user5" STATUS="enabled"/>
<SS_ROLE ID="1" NAME="Admin"/>
<SS_ROLE ID="2" NAME="User"/>
<SS_USER_ROLE USER_ID="1" ROLE_ID="1"/>
<SS_USER_ROLE USER_ID="1" ROLE_ID="2"/>
<SS_USER_ROLE USER_ID="2" ROLE_ID="2"/>
<SS_USER_ROLE USER_ID="3" ROLE_ID="2"/>
<SS_USER_ROLE USER_ID="4" ROLE_ID="2"/>
<SS_USER_ROLE USER_ID="5" ROLE_ID="2"/>
<SS_USER_ROLE USER_ID="6" ROLE_ID="2"/>
</dataset>
应该<SS_USER> 表名
属性名都是列名
属性值都是列值
WebTestUtils类:
* 1.Spring WebApplicationContext初始化到ServletContext.
* 2.将MockRequest/Response放入Struts2的ServletActionContext.
再来就是utils包了:
PropertiesUtils 类: 载入多个properties文件, 相同的属性最后载入的文件将会覆盖之前的载入. 实现很简单, 不做分析.
ReflectionUtils类: 做了很多反射封装, 主要看一下如何使用apache的beanutils工具:
主要看两个主要的方法, 循环向上找所有的方法, 和所有的字段(因为可能存在继承)
public static Field getAccessibleField(final Object obj,
final String fieldName) {
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {// NOSONAR
// Field不在当前类定义,继续向上转型
}
}
return null;
}
public static Method getAccessibleMethod(final Object obj,
final String methodName, final Class<?>... parameterTypes) {
Assert.notNull(obj, "object不能为空");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
Method method = superClass.getDeclaredMethod(methodName,
parameterTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {// NOSONAR
// Method不在当前类定义,继续向上转型
}
}
return null;
}
设置字段属性:
Field field ;
field.set(obj, value);
读取字段属性:
result = field.get(obj);
触发方法:
Method method;
method.invoke(obj, args);
最后看一下web包:
ServletUtils类: 还是有很多实用的方法的, 以后直接当做工具类来使用! 比如:
a) 对Http Basic验证的 Header进行编码.
b) 设置让浏览器弹出下载对话框的Header.
c) 根据浏览器 If-None-Match Header, 计算Etag是否已无效.
d) 根据浏览器If-Modified-Since Header, 计算文件是否已被修改.
Struts2Utils类:
实现获取Request/Response/Session与绕过jsp/freemaker直接输出文本的简化函数.
真是一个好类.
//取得HttpSession的简化函数.
public static HttpSession getSession() {
return ServletActionContext.getRequest().getSession();
}
//-- 绕过jsp/freemaker直接输出文本的函数 --//
/ **
* 直接输出内容的简便函数.
* eg.
* render("text/plain", "hello", "encoding:GBK");
* render("text/plain", "hello", "no-cache:false");
* render("text/plain", "hello", "encoding:GBK", "no-cache:false");
*
* @param headers 可变的header数组,目前接受的值为"encoding:"或"no-cache:",默认值分别为UTF-8和true.
*/
public static void render(final String contentType, final String content, final String... headers) {
HttpServletResponse response = initResponseHeader(contentType, headers);
try {
response.getWriter().write(content);
response.getWriter().flush();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
//直接输出支持跨域Mashup的JSONP.
public static void renderJsonp(final String callbackName, final Object object, final String... headers) {
String jsonString = null;
try {
jsonString = mapper.writeValueAsString(object);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
String result = new StringBuilder().append(callbackName).append("(").append(jsonString).append(");").toString();
//渲染Content-Type为javascript的返回内容,输出结果为javascript语句, 如callback197("{html:'Hello World!!!'}");
render(ServletUtils.JS_TYPE, result, headers);
}
什么是跨域ajax?
由于XMLHttprequest不能在firefox下跨域. 什么是跨域? 就是ajax脚本所在域是www.aaa.com, 去访问www.bbb.com的连接.
出于安全考虑, 不允许.