#
目标:让log4j.xml配置文件中允许使用占位符(${key}).
使用场景:
在运行期决定一些动态的配置内容.
比如在我们项目中,希望一台物理机同一个应用跑多个实例.
因为多进程操作同一份log文件存在并发问题(打印,DailyRolling等),所以我希望配置如下:${loggingRoot}/${instance}/project.log
在运行脚本中,通过加入-Dinstance=instance1参数,来动态指定实例名.让同一份应用在不同的运行实例下,日志打印到不同的路径
Log4j分析:
我以为,Log4j天生就支持占位符的.请见:org.apache.log4j.helpers.OptionConverter.substVars(String val, Properties props)就有对占位符的处理.
org.apache.log4j.PropertyConfigurator (log4j.properties文件解析).默认就支持对占位符的处理.
org.apache.log4j.xml.DOMConfigurator挺怪异的.明明也有对占位符的处理.但是我们就是无法对其属性props进行赋值.
(当然,有可能是我误解了其props的用法--还没有完整读过他的源码)
处理方案:
继承org.apache.log4j.xml.DOMConfigurator,实现自己的DOMConfigurator.
public class PlaceHolderDOMConfigurator extends org.apache.log4j.xml.DOMConfigurator {
private Properties props;
public PlaceHolderDOMConfigurator(Properties props){
this.props = props;
}
public static void configure(String filename, Properties props) {
new PlaceHolderDOMConfigurator(props).doConfigure(filename, LogManager.getLoggerRepository());
}
//主要是覆写这个方案.传入properties对象
protected String subst(String value) {
try {
return OptionConverter.substVars(value, props);
} catch (IllegalArgumentException e) {
LogLog.warn("Could not perform variable substitution.", e);
return value;
}
}
}
测试代码:
log4j.xml片段:
<appender name="PROJECT" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file" value="${loggingRoot}/${instance}/project.log"/>
<param name="append" value="false"/>
<param name="encoding" value="GB2312"/>
<param name="threshold" value="info"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
</layout>
</appender>
Run.java:
public static void main(String[] args) {
Properties props = new Properties();
props.setProperty("loggingRoot", "d:/tmp");
props.setProperty("instance", "instance1");
PlaceHolderDOMConfigurator.configure(LOG4J_PATH, props);
Logger rootLogger = LogManager.getRootLogger();
FileAppender fileAppender = (FileAppender) rootLogger.getAppender("PROJECT");
System.out.println(fileAppender.getFile());
}
输出结果:
d:/tmp/instance1/project.log
当然,你也可以通过在启动参数中加 -DloggingRoot=xxxx -Dinstance=yyyy动态指定内容.
特别说明:
本文:log4j版本为1.2.14
log4j 1.2.15测试不通过,原因见:
https://issues.apache.org/bugzilla/show_bug.cgi?id=43325
背景:
1.团队成员对质量意识逐渐提升;单元测试意识提升;
2.性能意识不足,往往到最后提交性能测试的时候,才发现性能问题;在开发阶段忽视对性能的考虑.
尤其在做对外服务的需求中,危害特别明显.
基于这两个原因,希望有一个在单元测试下的性能测试工具.提供最简单的性能指标报表.在开发阶段让开发对性能情况有个感性的认识.
设计思路:
概念说明:
类名 |
方法
|
说明
|
Statistics
说明:性能统计信息 |
tps() |
提供tps |
|
average() |
提供平均响应时间,单位毫秒 |
|
total() |
提供总耗时,单位毫秒 |
Job
说明:测试单元逻辑 |
execute() |
性能测试逻辑 |
Warn
说明:性能未达标警告 |
|
|
PerformanceTester (核心)
说明:性能测试工具,根据制定的并发数和单个并发循环次数,进行性能测试;根据提供的平均响应时间,分析是否达标 |
test(Job job) |
性能测试,打印性能报表,分析是否达标 |
JTesterxPerformance
说明:基于JTester的性能测试基类,统一执行性能测试计划
备注:
JTester是我们公司同事编写的一套单元测试框架.我们同样可以提供基于JUnit的实现,比如JUnitPerformance
|
performance() |
根据提供的性能策略,指标 和 测试逻辑,进行性能测试 |
|
job() |
需要子类覆写,提供测试逻辑 |
|
testers() |
需要子类覆写,提供性能测试策略和指标要求 |
User Guide:
创建一个性能测试类,继承com.alibaba.tpsc.common.test.jtesterx.JTesterxPerformance
在类名标注@Test (org.testng.annotations.Test),表明需要进行TestNG的单元测试
备注:如果是在其他单元测试框架下,请自行扩展类似JUnitPerformacne实现
覆写public Job job()方法.提供 性能测试名 和 性能测试逻辑
@Override
public Job job() {
return new Job("SampleService.hello") {
@Override
public void execute() {
SampleService.hello();
}
};
}
覆写public Collection<PerformanceTester> testers().提供一组性能测试策略(并发数,单个并发循环次数) 和 性 能测试指标(平均响应时间)
性能测试工具会根据提供策略和指标,依次进行性能测试.
public Collection<PerformanceTester> testers() {
Collection<PerformanceTester> testers = new ArrayList<PerformanceTester>();
// 20个并发,单个并发循环1000次,平均响应时间阀值10ms
testers.add(new PerformanceTester(20, 1000, 10));
// 10个并发,单个并发循环1000次,平均响应时间阀值5ms
testers.add(new PerformanceTester(10, 1000, 5));
return testers;
}
右键点击Eclipse->Run As->TestNG Test.
如果测试通过,则显示Green Bar
如果测试未通过,则在Red Bar中显示:java.lang.AssertionError: performance expected is 1ms,but actual is 2.938ms.
工具代码和演示代码如下:
Demo下载
Tags:
Spring LazyInit DocumentDefaultsDefinition ReaderEventListener AbstractXmlApplicationContext
背景:
工程单元测试希望和生产环境应用共用一份Spring配置文件.
生产环境应用为了客户体验使用非LazyInit模式,但是单元测试下为了当前测试提高响应时间,希望设置LazyInit.
分析源代码,得知,Spring在解析XML时,会将Bean默认配置,放入到DocumentDefaultsDefinition对象中,其中包含lazyInit.
DocumentDefaultsDefinition注释如下:
Simple JavaBean that holds the defaults specified at the <beans> level in a standard Spring XML bean definition document: default-lazy-init, default-autowire, etc
Spring是否提供了入口点,进行DocumentDefaultsDefinition的修改呢?
详见:ReaderEventListener,注释如下:
Interface that receives callbacks for component, alias and import registrations during a bean definition reading process
在BeanDefinition分析过程中,对component,alias,import registrations,defaults registrations提供一组callbacks.
接口代码如下:
1 public interface ReaderEventListener extends EventListener {
2
3 /**
4 * Notification that the given defaults has been registered.
5 * @param defaultsDefinition a descriptor for the defaults
6 * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition
7 */
8 void defaultsRegistered(DefaultsDefinition defaultsDefinition);
9
10 /**
11 * Notification that the given component has been registered.
12 * @param componentDefinition a descriptor for the new component
13 * @see BeanComponentDefinition
14 */
15 void componentRegistered(ComponentDefinition componentDefinition);
16
17 /**
18 * Notification that the given alias has been registered.
19 * @param aliasDefinition a descriptor for the new alias
20 */
21 void aliasRegistered(AliasDefinition aliasDefinition);
22
23 /**
24 * Notification that the given import has been processed.
25 * @param importDefinition a descriptor for the import
26 */
27 void importProcessed(ImportDefinition importDefinition);
28
29 }
接下去分析,ReaderEventListener是在哪个入口点,提供了回调.答案是XmlBeanDefinitionReader.
在AbstractXmlApplicationContext,创建了XmlBeanDefinitionReader对象,见:
1 public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
2
3 /**
4 * Loads the bean definitions via an XmlBeanDefinitionReader.
5 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
6 * @see #initBeanDefinitionReader
7 * @see #loadBeanDefinitions
8 */
9 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
10 // Create a new XmlBeanDefinitionReader for the given BeanFactory.
11 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
12
13 // Configure the bean definition reader with this context's
14 // resource loading environment.
15 beanDefinitionReader.setResourceLoader(this);
16 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
17
18 // Allow a subclass to provide custom initialization of the reader,
19 // then proceed with actually loading the bean definitions.
20 initBeanDefinitionReader(beanDefinitionReader);
21 loadBeanDefinitions(beanDefinitionReader);
22 }
23
24 }
我们只需要去复写这个方法,在创建XmlBeanDefinitionReader的时候,去注入EventListener即可.
扩展代码如下:
LazyInitListener.java (不管配置文件如何配置,设置默认的LazyInit为true)
public class LazyInitListener implements ReaderEventListener {
private static final String LAZY_INIT = "true";
@Override
public void defaultsRegistered(DefaultsDefinition defaultsDefinition) {
//set lazy init true
if (defaultsDefinition instanceof DocumentDefaultsDefinition) {
DocumentDefaultsDefinition defaults = (DocumentDefaultsDefinition) defaultsDefinition;
defaults.setLazyInit(LAZY_INIT);
}
}
@Override
public void aliasRegistered(AliasDefinition aliasDefinition) {
//no-op
}
@Override
public void componentRegistered(ComponentDefinition componentDefinition) {
//no-op
}
@Override
public void importProcessed(ImportDefinition importDefinition) {
//no-op
}
}
LazyInitClasspathXmlApplicationContext.java (复写AbstractXmlApplicationContext,创建XmlBeanDefinitionReader的时候注入LazyInitListener)
1 public class LazyInitClasspathXmlApplicationContext extends ClassPathXmlApplicationContext {
2
3 public LazyInitClasspathXmlApplicationContext(String location) {
4 super(location);
5 }
6
7 @Override
8 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
9 // Create a new XmlBeanDefinitionReader for the given BeanFactory.
10 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
11
12 // Configure the bean definition reader with this context's
13 // resource loading environment.
14 beanDefinitionReader.setResourceLoader(this);
15 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
16
17 // 添加的代码,设置LazyInitListener
18 beanDefinitionReader.setEventListener(new LazyInitListener());
19
20 // Allow a subclass to provide custom initialization of the reader,
21 // then proceed with actually loading the bean definitions.
22 initBeanDefinitionReader(beanDefinitionReader);
23 loadBeanDefinitions(beanDefinitionReader);
24 }
25
26 }
演示代码如下:
TestBean:一个Spring Bean对象
public class TestBean {
public void init() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
//ignore
System.out.println(e);
}
System.out.println("TestBean Init");
}
}
Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
5
6 <bean id="testBean" class="com.alibaba.javalab.spring.lazy.TestBean" init-method="init" />
7 </beans>
测试代码:
1 public class Run {
2
3 private static final String CONFIG = "classpath:spring/bean.xml";
4
5 public static void main(String[] args) {
6 testInit();
7 System.out.println("===============================");
8 testLazyInit();
9 }
10
11 public static void testInit() {
12 new ClassPathXmlApplicationContext(CONFIG);
13 }
14
15 public static void testLazyInit() {
16 new LazyInitClasspathXmlApplicationContext(CONFIG);
17 }
18 }
大功告成.收工. :)
背景:
公司的一个web应用,提交给测试部门做压力测试(由于不是我负责的,不清楚如何做的压力测试,以及测试指标),结果没压多久,就出现OutOfMemory.
接手协助原因查找,通过监控工具,发现
StandardSession(org.apache.catalina.session.
StandardSession)对象不断增长,毫无疑问,肯定是在不断创建Session对象.
备注:
一般做压力测试,每次请求都不会指定JESSESIONID值,导致Web容器认为每次请求都是新的请求,于是创建Session对象.
同事负责代码Review,发现应用没有任何一个地方存放Session内容.困惑之...
问题:Tomcat容器何时创建Session对象?
想当然认为,只有动态存放Session内容的时候,才会创建Session对象.但是事实真得如此吗?
先看Servlet协议描述:
请看:
getSession(boolean create)方法:
javax.servlet.http.HttpServletRequest.getSession(boolean create)
Returns the current HttpSession associated with this request or, if if there is no current session and create is true, returns a new session.
If create is false and the request has no valid HttpSession, this method returns null.
To make sure the session is properly maintained, you must call this method before the response is committed.
简单地说:当create变量为true时,如果当前Session不存在,创建一个新的Session并且返回.
getSession()方法:
javax.servlet.http.HttpSession getSession();
Returns the current session associated with this request, or if the request does not have a session, creates one.
简单的说:当当前Session不存在,创建并且返回.
所以说,协议规定,在调用getSession方法的时候,就会创建Session对象.
既然协议这么定了,我们再来看看Tomcat是如何实现的:(下面的描述,是基于Tomcat6.0.14版本源码)
先看一张简单的类图:
ApplicationContext:Servlet规范中ServletContext的实现
StandardContext:Tomcat定义的Context默认实现.维护了一份SessionManager对象,管理Session对象.所有的Session对象都存放在Manager定义的Map<String,Session>容器中.
StanardManager:标准的Session管理,将Session存放在内容,Web容器关闭的时候,持久化到本地文件
PersistentManager:持久化实现的Session管理,默认有两种实现方式:
--持久化到本地文件
--持久化到数据库
了解了大概的概念后,回头再来看看org.apache.catalina.connector.
Request.getSession()是如何实现的.
最终调用的是doGetSession(boolean create)方法,请看:
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session);
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
session.access();
return (session);
}
}
// Create a new session if requested and the response is not committed
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getCookies()) {
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
response.addCookieInternal(cookie, context.getUseHttpOnly());
}
if (session != null) {
session.access();
return (session);
} else {
return (null);
}
}
至此,简单地描述了Tomcat Session创建的机制,有兴趣的同学要深入了解,不妨看看Tomcat源码实现.
补充说明,顺便提一下Session的过期策略.
过期方法在:
org.apache.catalina.session.
ManagerBase(StandardManager基类) processExpires方法:
public void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
int expireHere = 0 ;
if(log.isDebugEnabled())
log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
for (int i = 0; i < sessions.length; i++) {
if (sessions[i]!=null && !sessions[i].isValid()) {
expireHere++;
}
}
long timeEnd = System.currentTimeMillis();
if(log.isDebugEnabled())
log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
processingTime += ( timeEnd - timeNow );
}
其中,Session.isValid()方法会做Session的清除工作.
在org.apache.catalina.core.ContainerBase中,会启动一个后台线程,跑一些后台任务,Session过期任务是其中之一:
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
OVER :)
资料:
wikipedia--bloom filter
使用场景,原理简介之中文资料:
数学之美系列二十一 - 布隆过滤器(Bloom Filter)
核心内容(摘自Google黑板报文章内容):
BloomFilter简易实现:
public class SimpleBloomFilter {
private static final int DEFAULT_SIZE = 2 << 24;
private static final int[] seeds = new int[] { 7, 11, 13, 31, 37, 61, };
private BitSet bits = new BitSet(DEFAULT_SIZE);
private SimpleHash[] func = new SimpleHash[seeds.length];
public static void main(String[] args) {
String value = "stone2083@yahoo.cn";
SimpleBloomFilter filter = new SimpleBloomFilter();
System.out.println(filter.contains(value));
filter.add(value);
System.out.println(filter.contains(value));
}
public SimpleBloomFilter() {
for (int i = 0; i < seeds.length; i++) {
func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i);
}
return (cap - 1) & result;
}
}
}
手头上一些工作,需要经常访问公司内网的数据,为了减少重复的劳动力,就考虑程序帮忙爬取信息数据。
apache下httpclient是一个很好的工具,不过公司内网是使用HTTPS协议的,于是乎,就需要考虑如何让httpclient支持https。
支持ssl的关键,在于如何创建一个SSLSocket,幸好,httpclient提供了支持。
请看:
org.apache.commons.httpclient.protocol.ProtocolSocketFactory
javadocs:A factory for creating Sockets.
实现一个简单的EasySSLProtocolSocketFactory,详见代码:
public class EasySSLProtocolSocketFactory implements ProtocolSocketFactory {
private SSLContext sslcontext = null;
private String ksfile;
private String tksfile;
private String kspwd;
private String tkspwd;
public EasySSLProtocolSocketFactory(String ks, String kspwd, String tks, String tkspwd){
this.ksfile = ks;
this.kspwd = kspwd;
this.tksfile = tks;
this.tkspwd = tkspwd;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException, UnknownHostException,
ConnectTimeoutException {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketfactory = getSSLContext().getSocketFactory();
if (timeout == 0) {
return socketfactory.createSocket(host, port, localAddress, localPort);
} else {
Socket socket = socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.bind(localaddr);
socket.connect(remoteaddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,
UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
private SSLContext createEasySSLContext() {
try {
SSLContext context = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(ksfile), kspwd.toCharArray());
tks.load(new FileInputStream(tksfile), tkspwd.toCharArray());
kmf.init(ks, kspwd.toCharArray());
tmf.init(tks);
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return context;
} catch (Exception e) {
throw new HttpClientError(e.toString());
}
}
private SSLContext getSSLContext() {
if (this.sslcontext == null) {
this.sslcontext = createEasySSLContext();
}
return this.sslcontext;
}
}
备注:
至于ssl相关的知识,和如何创建java key store,可见:
SSL双向认证java实现
如何使用,也简单:
Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(KS_FILE, KS_PWD, TKS_FILE, TKS_PWD), 443))
官方资料:
http://hc.apache.org/httpclient-3.x/sslguide.html
演示代码:
httpclient-ssl.zip
easy_install is not so easy。
这是我最近在学习python的一丝体会,好多lib都无法通过easy_install安装,比如:
Python Imaging Library (PIL)
只能通过手工安装方式安装:
*download the
pil_1.1.6
*tar xvf Imaging-1.1.6.tar.gz & chmox +x setup.py
*python setup.py build
结果,居然:
_imagingtk.c -o build/temp.linux-i686-2.6/_imagingtk.o
_imagingtk.c:20:16: error: tk.h: No such file or directory
_imagingtk.c:23: error: expected ‘)’ before ‘*’ token
_imagingtk.c:31: error: expected specifier-qualifier-list before ‘Tcl_Interp’
_imagingtk.c: In function ‘_tkinit’:
_imagingtk.c:37: error: ‘Tcl_Interp’ undeclared (first use in this function)
_imagingtk.c:37: error: (Each undeclared identifier is reported only once
_imagingtk.c:37: error: for each function it appears in.)
_imagingtk.c:37: error: ‘interp’ undeclared (first use in this function)
_imagingtk.c:45: error: expected expression before ‘)’ token
_imagingtk.c:51: error: ‘TkappObject’ has no member named ‘interp’
_imagingtk.c:55: warning: implicit declaration of function ‘TkImaging_Init’
error: command 'gcc' failed with exit status 1
tk.h No such file or directory
事实上,tk-dev包我已经安装了,查看setup.py代码,发现:
# Library pointers.
#
# Use None to look for the libraries in well-known library locations.
# Use a string to specify a single directory, for both the library and
# the include files. Use a tuple to specify separate directories:
# (libpath, includepath). Examples:
#
# JPEG_ROOT = "/home/libraries/jpeg-6b"
# TIFF_ROOT = "/opt/tiff/lib", "/opt/tiff/include"
#
# If you have "lib" and "include" directories under a common parent,
# you can use the "libinclude" helper:
#
# TIFF_ROOT = libinclude("/opt/tiff")
FREETYPE_ROOT = None
JPEG_ROOT = None
TIFF_ROOT = None
ZLIB_ROOT = None
TCL_ROOT = None
将TCL_ROOT = None 修改成:TCL_ROOT = '/usr/include/tk',即可
python setup.py build
python setup.py install
成功 :)
初次使用Struts2,老老实实为每个action method配置url mapping文件。
时间长了,难为觉得繁琐,为何不使用COC的方式呢?终于,想到了使用通配符。
查看Struts2 Docs,找到相关配置方法:
<package name="alliance" namespace="/alliance" extends="struts-default">
<action name="*/*" class="cn.zeroall.cow.web.alliance.action.{1}Action" method="{2}">
<result name="target" type="velocity">/templates/alliance/{1}/${target}.vm</result>
<result name="success" type="velocity">/templates/alliance/{1}/{2}.vm</result>
<result name="input" type="velocity">/templates/alliance/{1}/{2}.vm</result>
<result name="fail" type="velocity">/templates/common/error.vm</result>
</action>
</package>
恩,非常方便,可是启动jetty,发现满足正则的url,就是找不到Action。
无奈,debug代码,找到原因,需要在struts.properties中,配置:
struts.enable.SlashesInActionNames = true
见注释:
### Set this to true if you wish to allow slashes in your action names. If false,
### Actions names cannot have slashes, and will be accessible via any directory
### prefix. This is the traditional behavior expected of WebWork applications.
### Setting to true is useful when you want to use wildcards and store values
### in the URL, to be extracted by wildcard patterns, such as
### <action name="*/*" method="{2}" class="actions.{1}"> to match "/foo/edit" or
### "/foo/save".
启动,COC终于成功。
但是(又冒出一个但是),针对*/*正则的url mapping,如何做validation呢?
按照struts2的约定,是通过:
[package/]ActionName-${配置中的action name=""中的名字}-validation.xml
如何把"/"这个符号放入到${配置中的action name=""中的名字}呢?
"/"可不是一个合法的文件名。
比如,我要为AlliedMemberAction/doRegister做validation,那么约定的校验文件名应该是:
cn/zeroall/cow/web/alliance/action/AlliedMemberAction-AlliedMember
/doRegister-validation.xml
这个特殊符号,可难刹我也。
无奈,继续debug,发现在代码:
xwork框架中的,AnnotationActionValidatorManager:
private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
这个context就是action name=""中的url表达式。
思想斗争后,由于我不喜欢使用*-*的pattern,更喜欢使用*/*pattern,只好修改了源码:
private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context.replace("/", "-") + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
将context中的“/”变成"-"解决这个问题。
不清楚struts2官方怎么看待这个问题。
大家是否有更好的方案,请指教
最近在帮朋友做一个支付功能,用到了支付宝。
从支付宝管理界面,下载到商户合作文档,看了demo程序后,心是拔凉拔凉的。
说说review代码后的问题吧:
CheckURL.java
public static String check(String urlvalue ) {
String inputLine="";
try{
URL url = new URL(urlvalue);
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(
urlConnection.getInputStream()));
inputLine = in.readLine().toString();
}catch(Exception e){
e.printStackTrace();
}
//System.out.println(inputLine); 系统打印出抓取得验证结果
return inputLine;
}
*Inputstream不需要close?
*知道e.printStackTrace()的性能代价?
Md5Encrypt.java
*是采用什么编码的?我下载的是UTF8编码版本的,请问Md5Encrypt.java是什么编码?
Payment.java
public static String CreateUrl(String paygateway,String service,String sign_type,String out_trade_no,
String input_charset,String partner,String key,String seller_email,
String body,String subject,String price,String quantity,String show_url,String payment_type,
String discount,String logistics_type,String logistics_fee,String logistics_payment,
String return_url) {
//String notify_url,需要的请把参数加入以上的createurl
Map params = new HashMap();
params.put("service", service);
params.put("out_trade_no", out_trade_no);
params.put("show_url", show_url);
params.put("quantity", quantity);
params.put("partner", partner);
params.put("payment_type", payment_type);
params.put("discount", discount);
params.put("body", body);
// params.put("notify_url", notify_url);
params.put("price", price);
params.put("return_url", return_url);
params.put("seller_email", seller_email);
params.put("logistics_type", logistics_type);
params.put("logistics_fee", logistics_fee);
params.put("logistics_payment", logistics_payment);
params.put("subject", subject);
params.put("_input_charset", input_charset);
String prestr = "";
prestr = prestr + key;
//System.out.println("prestr=" + prestr);
String sign = com.alipay.util.Md5Encrypt.md5(getContent(params, key));
String parameter = "";
parameter = parameter + paygateway;
//System.out.println("prestr=" + parameter);
List keys = new ArrayList(params.keySet());
for (int i = 0; i < keys.size(); i++) {
String value =(String) params.get(keys.get(i));
if(value == null || value.trim().length() ==0){
continue;
}
try {
parameter = parameter + keys.get(i) + "="
+ URLEncoder.encode(value, input_charset) + "&";
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
parameter = parameter + "sign=" + sign + "&sign_type=" + sign_type;
return sign;
}
*多少个参数啊?超过3,4个参数,都不使用ParameterClass吗?方便client调用吗?
*这个方法做什么?createUrl?得到url。可事实上呢?return sign。sign是什么?是参数的加密窜。
方法中的
parameter不知道要来干吗用?
*又看到e.printStackTrace();
SignatureHelper.java
哇,总算看到一个过得去的代码,可以eclipse上,发现一个warning:import java.io.UnsupportedEncodingException;
有用到UnsupportedEncodingException这个吗?
SignatureHelper_return.java
*看看这个类名,符合java类名的规范吗?
*和SignatureHelper.java有什么区别?
SetCharacterEncodingFilter.java
哇塞,总算看到非常标准的代码了。可是:@author Craig McClanahan,原来是copy过来的。呜呼。
并且整个demo工程,是用myeclipse的。哎。。。
看不下去了,实在看不下去了。
我不清楚支付宝公司提供的demo程序的目的是什么?
--提供的java文件是允许打成lib包使用的?
--仅仅提供学习的?
就算是提供学习的,写得标准些,行不?
最后,我真希望,是我自己下错了demo程序--这个demo程序不是支付宝官方的demo。希望如此吧,阿门~
备注:
除了demo,那份接口文档,写得还是非常规范的。