存档

‘开发’ 分类的存档

spring循环引用的问题

2010年10月27日 没有评论

很久没写技术贴了,这两天被spring的循环引用搞死了,发文记之。

前几天,项目结构做了调整,把我所在的项目代码嵌入另一个项目,然后就杯具了,症状如下:

Bean with name ‘xxxService’ has been injected into other beans [xxxService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching – consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.

用的spring版本是2.5.6,之前也有遇到过这种问题,确实是代码设计得比较耦合,将相关逻辑拆分后得到解决。这两天这个问题,不管我如何拆解都如影随形,已经不是简单的A->B,B->A的循环引用了,而是深层次的逻辑耦合,要解耦比较困难,说明在设计阶段有提高的余地。诡异的是,在融入另一个项目前是不会抛这个错误的,可见问题可能出在和新项目融合后的配置文件上,简单分析了一下还是不得要领,于是开始启用google了。Juergen Hoeller说:

This is probably a consequence of the bean initialization order having changed, in combination with auto-proxying and maybe a pointcut that is too broad.

然后他给出了解决方案,有一个参数setAllowRawInjectionDespiteWrapping,默认是false,将其设成true即可。代码如下:

public class MyWebApplicationContext extends XmlWebApplicationContext {

    @Override
    protected DefaultListableBeanFactory createBeanFactory() {
        DefaultListableBeanFactory beanFactory =  super.createBeanFactory();
        beanFactory.setAllowRawInjectionDespiteWrapping(true);
        return beanFactory;
    }

}

然后在web.xml配置启用此context,

<context-param>
        <param-name>contextClass</param-name>
        <param-value>xxx.MyWebApplicationContext</param-value>
    </context-param>

然后就可以了。

当然这只是治标的办法,治本还得从设计的角度解决问题,尽量设计解耦的代码,有待提高,写下这篇文章是因为我在网上搜了很多,发现都不是很清楚,根据本文前面的出错信息提示也找不到比较好的答案。另一方面,随着spring越来越庞大,各种参数纷繁复杂,只有开发人员才清楚每一个的含义和如何设置,已经不是当年追求简单的spring了。

参考:SPR-2415

分类: java 标签:

利用JAMon监控java应用

2010年7月5日 没有评论

JAMon,Java Application Monitor的缩写,主页上这样介绍:

is a free, simple, high performance, thread safe, Java API that allows developers to easily monitor production applications.

可以用于

  1. 监控服务器的响应时间
  2. 跟踪应用里的各种行为,比如数据库访问等等

入门级应用

JAMon提供了一种非侵入的方式,只要配置一个filter,将原有请求都先经过这个filter。第一步先下载相关的jar包,放到对应的目录,然后在web.xml里加一段filter的配置

<filter>

<filter-name>JAMonFilter</filter-name>

<filter-class>com.jamonapi.JAMonFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>JAMonFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

然后重启应用,这样JAMon就开始记录应用里各请求的响应情况了,JAMon还提供了页面展示监控到的记录,将下载下来的jamon.war里的文件放到对应目录,然后在web.xml配置jsp的访问路径,如

<!– jamon begin –>

<servlet>

<servlet-name>jamonadmin</servlet-name>

<jsp-file>/jamonadmin.jsp</jsp-file>

</servlet>

<servlet>

<servlet-name>menu</servlet-name>

<jsp-file>/menu.jsp</jsp-file>

</servlet>

<servlet>

<servlet-name>exceptions</servlet-name>

<jsp-file>/exceptions.jsp</jsp-file>

</servlet>

<servlet>

<servlet-name>sql</servlet-name>

<jsp-file>/sql.jsp</jsp-file>

</servlet>

<servlet>

<servlet-name>query</servlet-name>

<jsp-file>/query.jsp</jsp-file>

</servlet>

<!– jamon end –>

重启应用后,访问jamonadmin.jsp,就可看到展示页面,包含各请求的请求次数、响应时间(最大、最小、平均)等信息。对于刚上线的应用,使用此方法监控,可快速发现性能瓶颈。当然,也可以继承这个filter,在里面加入另外想做的事情。

监控每个方法的执行时间

在上面已经可以监控每个请求的情况,但是,有些时候需要粒度更小的监控,比如每个方法的执行时间和次数,找到执行次数最多和时间最长的方法,先优化,应该可以极大地提供整体的响应速度。幸运的是,Spring框架里已经提供了一个Interceptor:JamonPerformanceMonitorInterceptor,可以很好地和JAMon整合,配置也很简单,将想监控的service implementation加入到这个Interceptor,重启后在上面的展示页面就能看到各方法的执行次数和执行时间了。

JamonPerformanceMonitorInterceptor有个缺点,就是必须配置log4j里的level为trace,这点让人不爽,于是有牛人修改了下,具体见Jamon*Interceptor: decoupling monitoring from tracing,不知道最新的Spring有没有修改。

还有个问题是,我在使用中发现,如果需要监控的service implementation之间存在循环引用的话,应用是起不来的,当然,从工程的角度来讲,循环引用是不合理设计导致的,本就不应该出现,可有些时候难免会出现循环引用。后来采用引入第三个service implementation来将原两个循环引用的service解开,这个是治标不治本的办法,呵呵。

这个监控也只是在应用刚上线时比较好,当应用稳定后就不需要了,以免增加额外的开销。

统计系统行为

对于一个稍大点的应用,都需要数据给决策者提供决策的依据,比如一个游戏,那么整个系统里玩家的行为统计和分析将给游戏进一步的运营方向提供清晰的数据支撑,而不是乱无目的地猜测玩家可能喜欢什么。

在JAMon的future directions说要将统计数据持久化,目前JAMon的所有数据都在内存里,只要重启就消失了。这点不太好,历史数据可以用于对比,另外一个是如果应用部署在多个服务器上,那么只能看每台服务器的数据,却不能看总的数据。遗憾的是JAMon自从发布2.7版本后就停止更新了。

对于这个,我在JAMon的基础上做了持久化的工作,支持多节点,提供展示页面,原理很简单,每隔一段时间将统计数据回写到数据库即可。

参考

  1. JAMon
分类: java 标签:

htmlunit,不仅仅用来做单元测试的

2010年4月9日 没有评论

htmlunit,用于网站单元测试的,在它的主页,介绍说“是一款没有图形界面的浏览器”,可以做浏览器做的很多事情,比如浏览页面、填充表单、模拟点击按钮等等。优点有:

  1. 支持javascript
  2. 可模拟多种浏览器,如firefox、ie6/7/8
  3. 建立在httpclient之上,简化了很多,接口更清晰和直接

当然,它不是真正的浏览器,还是有一些缺陷,如对javascript的支持没有浏览器那么全面,这个项目一直在持续中,相信以后会有更好的支持。

htmlunit的初衷是用于验证网站开发中页面UI上的功能正确性,一直以来,页面UI的测试都脱离不了测试人员通过肉眼来验证是否正确,这样的效率很低,也很可能出错,利用htmlunit,一方面可以自动化测试,一方面减少了人力方面的资源,一举两得。

换个角度思考,既然htmlunit可以用来验证,当然也可以用于其他方面。很明显的一点,就是可以用来做网络爬虫,这几年web2.0概念如火如荼,很多网站都大量采用了AJAX技术,动态加载页面内容,导致传统的网络爬虫抓瞎,因为传统的网络爬虫不支持javascript,抓取的页面都是动态加载前的内容,而htmlunit通过javascript的支持,应该可以取得更好的结果。

另外一点,可以用来模拟很多行为。比如模拟登录邮箱,将邮件读取显示出来,这样不用打开浏览器或邮件客户端即可以读到最新的邮件,或者模拟登录开心农场,定时去收菜或偷菜,此类网页游戏的外挂,使用htmlunit写起来应该不是很困难的事情。

参考:

  1. htmlunit主页
分类: java 标签: ,

初探httpclient

2010年3月28日 没有评论

httpclient是由java实现用来模拟http请求的,开源的,现在已经出到4.1版本了,改动太大了,还是之前的3.1版本比较符合人的思维,主要讲下使用中遇到的问题。

一、编码

编码有好几块,一个是客户端发起请求时指定的编码,一个是服务器返回的编码,还有一个是返回页面的Content-Type里指定的编码,一般来说后两者是相同的。

对于发起请求时的编码,可以先查看网页的源码,拿到里面的编码作为请求时的编码即可。

为了获取正确的返回内容,可以采用如下:

System.out.println("response body: "+new String(method.getResponseBody(), method.getRequestCharSet()))

有些时候通过指定正确的编码还是返回乱码,有可能是由于服务端采用gzip压缩的原因,尝试在request header里去掉[Accept-Encoding,gzip,deflate]也许会返回正确的结果。
二、为get/post指定请求编码

分别继承各自的父类,然后重载getRequestCharSet,如get方法

public class EncodedGetMethod extends GetMethod {
	private String charSet = "utf-8";

	public EncodedGetMethod(String uri, String charset) {
		super(uri);
		this.charSet = charset;
	}

	@Override
	public String getRequestCharSet() {
		return this.charSet;
	}
}

三、post的两种传参数方式

对于表单提交,一般有两种,带有图片,form的属性里有enctype=”multipart/form-data”;还有一种是简单的纯文本提交,如输入用户名密码然后登录的表单。

httpclient针对这两种form使用了不同的传参数方式,

1. 对于有图片的form提交使用Part[],有两种,StringPart用于传简单的文本,FilePart用于传文件,典型的StringPart如下:

StringPart sp = new StringPart(name, value, charset);

2. 对于纯文本的form提交使用NameValuePair[]

四、关于cookie

httpclient已经相当聪明了,会自动管理cookie,对于一个client发出的一系列get/post请求都享有当前的cookie。

参考:

  1. httpclient主页
  2. 关于编码
  3. 关于multipart表单的post方法
分类: java 标签: ,

log4j日志不向父类传播的方法

2009年4月28日 没有评论

log4j的所有日志都有一个祖先,叫rootLogger,其他所有的logger都会继承他,其他的logger不仅会在自身设定的appender里打印,也会在从自身往前的所有继承链上传播,如果不想往继承链上传播,可设置additivity属性为false,默认是true:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L – %m%n

log4j.rootLogger=info,stdout

log4j.additivity.SubLogger=false
log4j.logger.SubLogger=info,YAppender

Appender Additivity
The output of a log statement of logger C will go to all the appenders in C and its ancestors. This is the meaning of the term “appender additivity”.

However, if an ancestor of logger C, say P, has the additivity flag set to false, then C‘s output will be directed to all the appenders in C and it’s ancestors upto and including P but not the appenders in any of the ancestors of P.

Loggers have their additivity flag set to true by default.

分类: java 标签: