Spring AOP Overview

AOP(Aspect-Oriented Programming)面向方面编程,提到这个概念,我们脑海中就会蹦出一些列的场景:日志记录、安全检查、事务控制等。而在日常 的工作中,特别是使用spring框架的开发中是必不可少的部分。但很多时候都停留在会使用的阶段,而对其原理也没做系统的研究与记录。这里我将自己学 习的一些记录

1、静态与动态

AOP经历了静态AOP、动态AOP的发展历程。静态AOP的代表是AspectJ,简单的说就是需要通过ajc的编译器修 改java字节码将aspect植入。这种方式的优点是高效但是也不够灵活,编译的工作也挺大量,学习成本也高。

随着技术的发展,特别是动态代理(DynamicProxy)的引进以及一些字节码操作技术的实现(cglib,asm)AOP也发展到了动态AOP的阶段。动态AOP主要是通过类加载或系统运行期间,对字节码进行操纵 的方式将aspect植入,这也带来一些性能问题,但是随着JVM的发展这些性能的损失也越来越小。

2、JAVA的实现

  • 动态代理
    JDK1.3之后引入了动态的机制,可以在运行时对目标接口生成代理对象。当然这里也只能是接口,这也是SpringAOP默认提供的代理实现。下面的例子展示了动态代理使用。

需要代理的对象

	public interface HelloService {

	    public void method1(int topicId);

	    public void method2(int forumId);

	}

aspect逻辑需要实现InvocationHandler接口

public class PerformanceHandler implements InvocationHandler {

	private Object target;

	public PerformanceHandler(Object target) {
		this.target = target;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		StopWatch watch = new StopWatch(method.getName());
		watch.start();
		Object obj = method.invoke(target, args);
		watch.stop();
		System.out.println(watch.shortSummary());
		return obj;
	}
}

Proxy获取代理对象

public class TestAOPService {
    public static void main(String[] args) {

        //希望被代理的目标业务类
        HelloService target = new HelloServiceImpl();

        //将目标业务类和横切代码编织到一起
        PerformanceHandler handler = new PerformanceHandler(target);

        //根据编织了目标业务类逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例
        HelloService proxy = (HelloService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target
                .getClass().getInterfaces(), handler);

        //调用代理实例
        proxy.method1(100);
        proxy.method2(1012);
    }
}
  • 动态字节码增强(CGLIB)
    我们可以采用asm,cglib等字节码增强技术,在程序运行期间动态修改class文件。SpringAOP在处理Class的代理采用Cglib的动态代理来实现。

Cglib通过生成子类来代理对象

	public class CglibProxy implements MethodInterceptor {
	    private Enhancer enhancer = new Enhancer();
	
	    public Object getProxy(Class<?> clazz) {
	        enhancer.setSuperclass(clazz); //设置需要创建子类的类
	        enhancer.setCallback(this);
	        return enhancer.create(); //通过字节码技术动态创建子类实例
	    }
	
	    //拦截父类所有方法的调用
	    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
	        StopWatch watch = new StopWatch(obj.getClass().getName() + "." + method.getName());
	        watch.start();
	        Object result = proxy.invokeSuper(obj, args); 
	        watch.stop();
	        System.out.println(watch.shortSummary());
	        return result;
	    }
	}

从实际运行的类来看已经不是我们本身的对象:

method1: 100
StopWatch 'org.java.codelib.proxy.HelloServiceImpl$$EnhancerByCGLIB$$d8d08640.method1': running time (millis) = 73
method1: 23
StopWatch 'org.java.codelib.proxy.HelloServiceImpl$$EnhancerByCGLIB$$d8d08640.method1': running time (millis) = 20
  • 其他的方式
    常见的其他方式还有自定义类加载器、动态代码生成等

3、基本概念

说到基本概念,关于AOP相信大家都会在各种资源上看到各种名词的解释:目标、切面、代理、通知、植入等反正一大堆,越说越混乱。其实想想AOP要做的无非是以下几个步骤:

  1. 找到需要改变的类(Joinpoint),
  2. 找到后何时改变:方法执行前?后?返回?等(Advice)。 >这其中可能还有几个问题:

  3. 一是如何找到(Pointcut),
  4. 再是找到后做何改变,如记录日志?事务?而这些记录日志事务的类就是(TargetObject),
  5. 最后让这些点统一起来工作就是植入(Weave)

Thrift入门

一、什么是thrift

Thrift的官网。Thrift是由 Facebook 开发的远程服务调用框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势

二、安装

这里是windows下的安装过程

1、下载 主页下载最新的release包和windows下的编译文件,该文件负责将idl文件编译成各种语言的代码:https://dist.apache.org/repos/dist/release/thrift/0.9.0/thrift-0.9.0.exe

2、作为java的实现语言 如果是maven管理需要加入依赖:

<dependency>  
  <groupId>org.apache.thrift</groupId>  
  <artifactId>libthrift</artifactId>  
  <version>0.9.0</version>  
</dependency>  

3、作为python的实现语言 在windows下需要语言安装python是必须的,同时需要有安装模块的工具,这里用setuptools:根据具体的平台找到相应的工具,这里下载后安装,将python安装目录下的script设置为windows的PATH变量,如

接下来进入到解压后thrift的release包目录,如F:\thrift\thrift-0.9.0\lib\py,在命令控制台运行:

setup.py install  

即完成了安装

三、简单的实例

根据IDL语言编写thrift的描述文件Hello.thrift

namespace java org.java.codelib.thrift.sample  
  
service Hello {  
	i32 add(1:i32 para1, 2:i32 para2),  
	void sayHello(1:string name);  
}  

其中

  • namespace指明了包结构,这里会生成的java包为org.java.codelib.thrift.sample
  • service相当于java中的接口描述,会将Hello生成具体语言的接口
  • add和sayHello声明了两个方法:其中add接收两个参数并返回结果,而sayHello接收string类型的参数,这里的参数类型string、i32等与java中的String、int类似 利用thrift-0.9.0.exe文件编译成java实现代码:thrift-0.9.0.exe --gen java Hello.thrift

这样在目录下生成了gen-java的文件夹,包含了java的实现代码Hello.java 接下来需要实现Hello.java文件中的Hello.Iface 接口:

public class HelloImpl implements Hello.Iface {  
  
	@Override  
	public int add(int para1, int para2) throws TException {  
		return para1 + para2;  
	}  
  
	@Override  
	public void sayHello(String name) throws TException {  
		System.out.println("Hello Thrift From " + name);  
	}  
  
}  

创建服务端代码:

public class HelloServiceServer {  
  
	/** 
	 * @param args 
	 * @author Administrator 
	 * @date 2013-1-26 
	 */  
	public static void main(String[] args) {  
		try {  
			// 设置服务端口为 7911   
			TServerSocket serverTransport = new TServerSocket(7911);  
			// 设置协议工厂为 TBinaryProtocol.Factory   
			Factory proFactory = new TBinaryProtocol.Factory(true, true);  
			// 关联处理器与 Hello 服务的实现  
			Hello.Processor<HelloImpl> processor = new Hello.Processor<HelloImpl>(new HelloImpl());  
  
			Args arg = new Args(serverTransport);  
			arg.processor(processor);  
			arg.protocolFactory(proFactory);  
			TServer server = new TSimpleServer(arg);  
			server.serve();  
			System.out.println("Start server on port 7911...");  
		} catch (TTransportException e) {  
			e.printStackTrace();  
		}  
	}  
  
}  

下面是客户端的代码:

public class HelloServiceClient {  
  
	/** 
	 * @param args 
	 * @author Administrator 
	 * @date 2013-1-26 
	 */  
	public static void main(String[] args) {  
		TTransport transport;  
		try {  
			transport = new TSocket("localhost", 7911);  
			TProtocol protocol = new TBinaryProtocol(transport);  
			Hello.Client client = new Hello.Client(protocol);  
			transport.open();  
			System.out.println(client.add(12, 20));  
			client.sayHello("robin");  
			transport.close();  
		} catch (TTransportException e) {  
			e.printStackTrace();  
		} catch (TException e) {  
			e.printStackTrace();  
		}  
	}  
  
}  

先运行服务端的代码,再运行客户端就可以在控制台看到相应的输出。这样就完成了一个thrift的简单实例

四、Thrift架构简介

这是thrift的架构图:

  • 黄色部分是用户实现的业务逻辑,
  • 褐色部分是根据Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,
  • 红色部分是根据Thrift 文件生成代码实现数据的读写操作。
  • 红色部分以下是 Thrift 的传输体系、协议以及底层 I/O 通信,使用 Thrift 可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码

下面是thrift的网络协议栈:

+-------------------------------------------+  
| Server                                    |  
| (single-threaded, event-driven etc)       |  
+-------------------------------------------+  
| Processor                                 |  
| (compiler generated)                      |  
+-------------------------------------------+  
| Protocol                                  |  
| (JSON, compact etc)                       |  
+-------------------------------------------+  
| Transport                                 |  
| (raw TCP, HTTP etc)                       |  
+-------------------------------------------+  

从上面可以看到,主要包括几个部分:

Transport

常见的传输层有

  • TSocket —— 使用阻塞式 I/O 进行传输,是最常见的模式
  • TNonblockingTransport —— 使用非阻塞方式,用于构建异步客户端

      TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(10005);  
      TCompactProtocol.Factory proFactory = new TCompactProtocol.Factory();  
      Hello.Processor<HelloImpl> processor = new Hello.Processor<HelloImpl>(new HelloImpl());  
    	  
      Args args = new org.apache.thrift.server.TNonblockingServer.Args(serverTransport);  
      args.processor(processor);  
      args.protocolFactory(proFactory);  
    	  
      TServer server = new TNonblockingServer(args);  
      System.out.println("Start server on port 10005 ...");  
      server.serve();  
    
  • TFramedTransport —— 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO。若使用 TFramedTransport 传输层,其服务器必须修改为非阻塞的服务类型。如服务端采用
  • TnonblockingTransport 见上面的代码,下面是客户端的写法:

      TTransport transport = new TFramedTransport(new TSocket("localhost", 10005));  
      TProtocol protocol = new TBinaryProtocol(transport);  
    	  
      Hello.Client client = new Hello.Client(protocol);  
      transport.open();  
      client.add(12, 20);  
      client.sayHello("robin");  
      transport.close();  
    

Protocol

协议定义了内存和网络传输格式之间的映射,也可理解为不同传输结构见的解码和编码的约定。在thrift中常用的有JSON, XML, plain text, compact binary 等。

  1. binary: 相当简单的二进制编码:将filed和对应的value合并在一起简单的二进制编码TBinaryProtocol

    Server:

     // 设置协议工厂为 TBinaryProtocol.Factory   
     Factory proFactory = new TBinaryProtocol.Factory(true, true);  
    

    Client:

     TProtocol protocol = new TBinaryProtocol(transport);  
    
  2. compact:https://issues.apache.org/jira/browse/THRIFT-110高效率的、密集的二进制编码格式进行数据传输

    server:

     TCompactProtocol.Factory proFactory = new TCompactProtocol.Factory(); 
    

    Client:

     TCompactProtocol protocol = new TCompactProtocol(transport);  
    
  3. jsonTJSONProtocol 使用 JSON 的数据编码协议进行数据传输与上面的一样:

     TJSONProtocol.Factory proFactory = new TJSONProtocol.Factory();  
     TJSONProtocol protocol = new TJSONProtocol(transport);  
    

Processor

一个Processor类似一个管道,主要处理输入输出流。在thrift中输入、输出流由Protocol对象来表示,Processor接口非常简单:

interface TProcessor {  
	bool process(TProtocol in, TProtocol out) throws TException  
}  

Thrift服务的特定processor的实现在编译时(生成具体语言的代理实现)生成。Processor主要的流程是读取网络中传送的输入流,处理读取的stream(用户实现的handler)并将相应结构返回给调用方

Server

一个服务就是讲上述几个特性整合起来,具体的描述如下:

  • Create a transport
  • Create input/output protocols for the transport
  • Create a processor based on the input/output protocols
  • Wait for incoming connections and hand them off to the processor

Thrift idl描述和跨语言的web服务

从上文的描述我们知道需要两个步骤:

一、编写idl描述性

thrift采用IDL(Interface Definition Language)来定义通用的服务接口,并通过生成不同的语言代理实现来达到跨语言、平台的功能。在thrift的IDL中,我们需要关注一下几点:

  1. 基本类型

与java中的char,int,long等基本类型一样,IDL中也有用来描述基本类型的定义

  • bool 表示一个布尔值,取true或false
  • byte 表示一个带符号的字节
  • i16 表示一个带符号的16位整形
  • i32 表示一个带符号的32位整形
  • i64 表示一个带符号的64位整形
  • double 表示一个带符号的64位整形
  • string 表示一个不可知编码的文本或二进制串
  1. 结构

定义了一个通用的对象以此来跨语言。主要采用struct来描述。如下我们定义了一个User对象:

struct User {  
    1: i16 gender = 1,  
    2: string username,  
    3: string password,  
    4: i32 id  
}  

上面的结构定义与C语言的结构描述很相同,每个字段都有一个唯一标识:1,2,3,4(处于版本管理的原因,推荐使用上唯一标识)。同时还可以有默认值。可以将结构的fields设置为optional,即如果在没有设置值时将不会序列化

  1. 容器

thrift的容器是强类型容器,能够与常用语言中的容器想对应,并可使用java泛型的方式进行标注。在thrift中提供了三种容器:

  • list<type> 一个有序元素列表。可翻译为java ArrayList或STL的vector,或者脚本语言中的原生数组,可包含重复数据
  • set<type> 一个无序不重复元素集。与STL的set,java的HashSet,Python的set,或者PHP/Ruby中的原生dictionary
  • map<type1,type2> 一个主键唯一键值映射表,翻译为STL的map,java HashMap,Python dictionary
  1. 异常

与结构的声明一致,唯一不同的是用exception关键字

exception NotFoundException{  
    1:i16 errorType,  
    2:string message  
}  
  1. 服务

一个服务的定义在语义上相当于面向对象编程中的一个接口。服务的定义如下:

service <name> {  
  <returntype> <name> (<arguments>)[throws (<exceptions>)]  
  ...  
}  

在方法的声明中有一个oneway修饰符,表示客户端只会触发一个请求,而不会监听任何响应,oneway的方法必须是void:

oneway void zip()  

一个例子

service UserService{  
  void saveUser(1:User user),  
  User get(1:i32 id) throws (1:NotFoundException nfe)  
}  

同时service也可以从另外的service继承,需要使用关键字extends,这与java的继承关键字一样:

service Calculator extends shared.SharedService   

二、其他的说明

  1. include通过include引用其他的thrift文件,默认在当前路径下寻找,也可以在相对路径下寻找,需要通过编译参数-I来设置

  2. namespace与java的package作用一样:

     namespace java org.java.codelib.thrift.sa  
     namespace python org.java.codelib.thrift.sa  
    

    上面指定了java和python的package,在只有--gen java/python时将会生产响应的package

  3. 指定常量

     const i32 INT32CONSTANT = 9853  
     const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}  
    

    或枚举:

     enum Operation {  
       ADD = 1,  
       SUBTRACT = 2,  
       MULTIPLY = 3,  
       DIVIDE = 4  
     }  
    

三、一个例子

下面的例子是thrift分发包中的tutorial,这里对package做了些修改,其他保持不变。这里分别生成python和java的代码,服务端采用python客户端用java来实现跨语言的调用

  1. shared.thrift

     namespace java org.java.codelib.thrift.sa  
     namespace py shared  
    	  
     struct SharedStruct {  
         1: i32 key  
         2: string value  
     }  
    	  
     service SharedService {  
         SharedStruct getStruct(1: i32 key)  
     }  
    
  2. tutorial.thrift

     include "shared.thrift"  
    	  
     namespace java org.java.codelib.thrift.sa  
     namespace py tutorial  
    	  
     typedef i32 MyInteger  
    	  
     const i32 INT32CONSTANT = 9853  
     const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}  
    	  
     enum Operation {  
         ADD = 1,  
         SUBTRACT = 2,  
         MULTIPLY = 3,  
         DIVIDE = 4  
     }  
    	  
     struct Work {  
         1: i32 num1 = 0,  
         2: i32 num2,  
         3: Operation op,  
         4: optional string comment,  
     }  
    	  
     exception InvalidOperation {  
         1: i32 what,  
         2: string why  
     }  
    	  
     service Calculator extends shared.SharedService {  
    	  
         void ping(),  
    	  
         i32 add(1:i32 num1, 2:i32 num2),  
    	  
         i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),  
    	  
         oneway void zip()  
     }  
    
  3. 生成python和java的代理实现代码

     thrift>thrift-0.9.0.exe --gen py shared.thrift  
     thrift>thrift-0.9.0.exe --gen py tutorial.thrift  
     thrift>thrift-0.9.0.exe --gen java shared.thrift  
     thrift>thrift-0.9.0.exe --gen java tutorial.thrift  
    
  4. PythonServer.py作为服务端python的实现:

     import sys  
     sys.path.append('../gen-py')  
    	  
     from tutorial import Calculator  
     from tutorial.ttypes import *  
    	  
     from shared.ttypes import SharedStruct  
    	  
     from thrift.transport import TSocket  
     from thrift.transport import TTransport  
     from thrift.protocol import TBinaryProtocol  
     from thrift.server import TServer  
    	  
     class CalculatorHandler:  
       def __init__(self):  
         self.log = {}  
    	  
       def ping(self):  
         print 'ping()'  
    	  
       def add(self, n1, n2):  
         print 'add(%d,%d)' % (n1, n2)  
         return n1+n2  
    	  
       def calculate(self, logid, work):  
         print 'calculate(%d, %r)' % (logid, work)  
    	  
         if work.op == Operation.ADD:  
           val = work.num1 + work.num2  
         elif work.op == Operation.SUBTRACT:  
           val = work.num1 - work.num2  
         elif work.op == Operation.MULTIPLY:  
           val = work.num1 * work.num2  
         elif work.op == Operation.DIVIDE:  
           if work.num2 == 0:  
             x = InvalidOperation()  
             x.what = work.op  
             x.why = 'Cannot divide by 0'  
             raise x  
           val = work.num1 / work.num2  
         else:  
           x = InvalidOperation()  
           x.what = work.op  
           x.why = 'Invalid operation'  
           raise x  
    	  
         log = SharedStruct()  
         log.key = logid  
         log.value = '%d' % (val)  
         self.log[logid] = log  
    	  
         return val  
    	  
       def getStruct(self, key):  
         print 'getStruct(%d)' % (key)  
         return self.log[key]  
    	  
       def zip(self):  
         print 'zip()'  
    	  
     handler = CalculatorHandler()  
     processor = Calculator.Processor(handler)  
     transport = TSocket.TServerSocket(port=9090)  
     tfactory = TTransport.TBufferedTransportFactory()  
     pfactory = TBinaryProtocol.TBinaryProtocolFactory()  
    	  
     server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)  
    	  
     # You could do one of these for a multithreaded server  
     #server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)  
     #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)  
    	  
     print 'Starting the server...'  
     server.serve()  
     print 'done.'  
    
  5. CalculatorClient作为java的客户端实现:

     public class CalculatorClient {  
    	  
         public static void main(String[] args) {  
             TTransport transport;  
             try {  
                 transport = new TSocket("localhost", 9090);  
                 TProtocol protocol = new TBinaryProtocol(transport);  
                 Calculator.Client client = new Calculator.Client(protocol);  
                 transport.open();  
                 System.out.println(client.add(12, 20));  
                 transport.close();  
             } catch (TTransportException e) {  
                 e.printStackTrace();  
             } catch (TException e) {  
                 e.printStackTrace();  
             }  
         }  
    	  
     }  
    
  6. 更多例子见thrift分发包下的tutorial


Linux中find与grep命令使用小结

find基本语法

find path -option [-print] [-exec -ok command] {} \;

命令参数:

  • path: 查找的目录。如.表示当前目录,/表示系统根目录。
  • -print: 将匹配的文件输出到标准输出。
  • -exec: 对匹配的文件执行该参数所给出的shell命令。相应命令的形式为’command’ { } \;,注意{}和\;之间的空格。
  • -ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。

常用的option参数

-name filename查找名为filename的文件
-perm按执行权限来查找
-user username按文件属主来查找
-mtime -n +n按文件更改时间来查找文件,-n指n天以内,+n指n天以前
-atime -n +n按文件访问时间来查GIN: 0px">
-ctime -n +n按文件创建时间来查找文件,-n指n天以内,+n指n天以前
-size n[c]查长度为n块[或n字节]的文件
-type b/d/c/p/l/f查是块设备、目录、字符设备、管道、符号链接、普通文件

一些例子

  • find . -name "*.log" -print 在当前目录下查找以.log结尾的文件
  • find . -name "ab[cd]" -exec rm -rf {} \; 在当前目录下查找文件abc或abd并将找到的文件删除
  • find . -name "ab?" 在当前目录下查找文件ab开头并且文件名只有3个字符
  • find /etc -size +10k 在目录/etc查找大于10k的文件
  • find /etc -size 10k 在目录/etc查找等于10k的文件
  • find /etc -size +10k -a -size -1M 在目录/etc查找大于10k并小于1M的文件,注意k与M大小写
  • find /etc -size +30k -a -size -50k -exec ls -lh {} \;
  • find . -name "*.log" -atime 10 -exec rm -rf {} \;查找当前目录下以log结尾的文件并且最后在10天前访问过的文件。找到后将其删除

grep基本用法

grep [OPTIONS] PATTERN [FILE...]

grep是在指定的文件中找到匹配的字符,并返回所在的行。如grep "size" anaconda-ks.cfg 在该文件中找到包含字符size的行,当然也支持正则匹配。

常用的option参数

  • -i 不区分大小写
  • -v 反向查找
  • -n 打印出行号

更多的时候和其他的命令一起使用

more install.log |grep "lib"
ps -ef|grep java

python 操作excel之pyExcelerator

pyExcelerator

pyExcelerator is a library for generating Excel 97/2000/XP/2003 and OpenOffice Calc compatible spreadsheets. pyExcelerator has full-blown support for UNICODE in Excel and Calc spreadsheets, allows using variety of formatting features, provides interface to printing options of Excel and OpenOffice Calc.

支持生成excel97+,支持OpenOffice。

Main advantage is possibility of generating Excel spreadsheets without COM servers. The only requirement – Python 2.4b2 or higher. From version 0.5 pyExcelerator can import data from Excel spreadsheets. 最主要的优势是不依赖于Windows/COM接口,需要注意的是python2.4+

在ubuntu中的安装

sudo python setup.py install   

安装后,创建一个文件做简单测试:

#!/usr/bin/env python  
# -*- coding: utf-8 -*-  
  
from pyExcelerator import *  
  
w = Workbook()  
ws = w.add_sheet('Hey, Dude')  
w.save('mini.xls')  

执行:

chmod a+x excel.py  
./excel.py  

在当前目录下可看到文件mini.xls

pyExcelerator结构介绍

在应用pyExcelerator的时候,需要导入模块

from pyExcelerator import *  

进到该package中的信息,可以看到pyExcelerator的模块

from Workbook import Workbook  
from Worksheet import Worksheet  
from Row import Row  
from Column import Column  
from Formatting import Font, Alignment, Borders, Pattern, Protection  
from Style import XFStyle  
from ImportXLS import *  
from ExcelFormula import *  

这里可以看到主要的划分目录,这也是excel中常见的模块:Workbook、Worksheet、Row、Column。其他还包括样式Formatting、Style模块,读取excel模块ImportXLS,支持公式的模块ExcelFormula。同时,在安装目录下有文件夹examples包含大量的使用实例

生成一个excel的步骤

  1. 实例化Workbook

     wb = Workbook()  
    
  2. 增加一个工作表Worksheet,获得工作表实例

     ws = wb.add_sheet('sheet1')  
    
  3. 对Worksheet进行写数据、样式、合并等属性的操作

     #这里写入数据,指定到具体的行、列。style指定样式,包括num_format_str、font、alignment、borders等  
     def write(self, r, c, label="", style=Style.XFStyle()):  
     #合并单元格,指定起止的行、列  
     def write_merge(self, r1, r2, c1, c2, label="", style=Style.XFStyle()):  
    
  4. 保存Workbook

     wb.save()  
    

    在写入的时候,如果当前写入目录同文件名的文件打开,会抛出异常,如:

     IOError: [Errno 13] Permission denied: 'mini.xls'
    

一个简单的写入数据到excel的例子

w = Workbook()  
ws = w.add_sheet('sheet')  
#第一行作为header:注意是(0,0)作为第一行第一列  
ws.write(0,0,u"姓名")  
ws.write(0,1,u"年龄")  
ws.write(0,2,u"班级")  
  
data = [["aaaa",9,u"三年二班"], ["bbbb",9,u"三年二班"], ["cccc",9,u"三年二班"]]  
  
#这里一般的处理是对数据循环,对应到sheet中的行列,写入数据  
for i in range(len(data)):  
    for j in range(len(data[i])):  
        ws.write(i+1,j,data[i][j])  
    i+=1  
  
w.save('mini.xls')  

写入特定样式

在ws中的写方法提供了

def write(self, r, c, label="", style=Style.XFStyle()):  

其中style=Style.XFStyle()就是定义各种样式的参数,在Style.XFStyle()的构造函数中:

def __init__(self):  
    self.num_format_str  = _default_num_format#数字或日期的格式  
    self.font            = _default_font.copy()#字体  
    self.alignment       = _default_alignment.copy()#对齐方式  
    self.borders         = _default_borders.copy()#边框  
    self.pattern         = _default_pattern.copy()  
    self.protection      = _default_protection.copy()  

这里以写入数字格式为例。在Style.py中StyleCollection定义了_std_num_fmt_list,对支持的数字格式做了说明,如:

_std_num_fmt_list = ['general','0','0.00','#,##0','#,##0.00','D-MMM-YY','D-MMM','MMM-YY',...]  

一般过程

#实例一个style对象  
style = XFStyle()  
#设置相应的格式  
style.num_format_str = 'D-MMM-YY'  
ws.write(self, r, c, label="", style)  

一个写入数字格式的例子:

from datetime import datetime  
num_data = ["123", "123.1232", "123.12", datetime.now(), datetime.now(), datetime.now()]  
fmts =     ['general', '0.00', '#,##0.00', 'D-MMM-YY', 'h:mm:ss AM/PM','M/D/YY']  
  
w = Workbook()  
ws = w.add_sheet('sheet')  
  
for i in range(len(num_data)):  
    ws.write(i+1, 0, num_data[i])  
    ws.write(i+1, 2, fmts[i])  
    #写入样式  
    style = XFStyle()  
    style.num_format_str = fmts[i]  
    ws.write(i+1, 4, num_data[i], style)  
    i+=1  
  
w.save('mini.xls')  

当然也可以设置字体格式

style.font.name = 'Times New Roman'  
style.font.struck_out = True  
style.font.bold = True  
style.font.outline = True  

其中,字体的更多设置见字体见Style.py中_default_font = Formatting.Font()pyExcelerator对excel的常见操作如合并单元格,冻结行列等都支持

合并单元格

from pyExcelerator import *  
  
#这里设置边框  
borders = Borders()  
borders.left = 1  
borders.right = 1  
borders.top = 1  
borders.bottom = 1  
  
#这里设置对齐方式  
al = Alignment()  
al.horz = Alignment.HORZ_CENTER  
al.vert = Alignment.VERT_CENTER  
  
style = XFStyle()  
style.borders = borders  
style.alignment = al  
  
wb = Workbook()  
ws = wb.add_sheet('sheet1')  
  
#跨列合并  
ws.write_merge(1, 1, 1, 5, u"这个合并(1,1)-(1,5)", style)  
ws.write_merge(2, 2, 2, 3, u"这个合并(2,2)-(2,3)", style)  
ws.write_merge(2, 2, 4, 5, u"这个合并(2,4)-(2,5)", style)  
#跨行合并  
ws.write_merge(2, 4, 1, 1, u"这个合并(2,1)-(4,1)", style)  
ws.write_merge(3, 4, 2, 2, u"这个合并(3,2)-(4,2)", style)  
ws.write_merge(3, 4, 3, 3, u"这个合并(3,3)-(4,3)", style)  
ws.write_merge(3, 4, 4, 4, u"这个合并(3,4)-(4,4)", style)  
ws.write_merge(3, 4, 5, 5, u"这个合并(3,5)-(4,5)", style)  
#合并多行多列  
ws.write_merge(1, 4, 6, 7, u"这个合并(1,6)-(4,7)", style)  
  
wb.save('merged.xls')  

这里跨行合并的时候需要注意,不知道出于什么原因考虑,在合并的时候并不支持对同一列的跨行合并,见Cell.py中MulBlankCell的构造方法:

def __init__(self, parent, col1, col2, xf_idx):  
    assert col1 < col2, '%d < %d is false'%(col1, col2)  
    self.__parent = parent  
    self.__col1 = col1  
    self.__col2 = col2  
    self.__xf_idx = xf_idx  

如果需要改功能,在源代码中注释掉这个断言即可

冻结行列

之前的操作是针对行列中某个写入的属性设置,比如Font、Alignment,如果设置冻结行、列则属于worksheet的操作。 在WorkSheet.py中Worksheet的构造函数设置了大量这样的属性,可以通过get/set方法进行设置。以下是对冻结行列的相关设置

self.__panes_frozen = 0  
self.__vert_split_pos = None  
self.__horz_split_pos = None  
self.__vert_split_first_visible = None  
self.__horz_split_first_visible = None  

如:

self.__panes_frozen = True  
self.__vert_split_pos = 2  #垂直方向(列)冻结两列  
self.__horz_split_pos = 2  #水平方向(行)冻结两列  

ws4.panes_frozen = False  
ws4.horz_split_pos = 20    #这个时候好像并不是指冻结20列  
ws4.horz_split_first_visible = 4 #excel打开时初始显示在第四列,即excel的scrollbar显示在第四列的位置  

这个例子来自sample目录下:

# -*- coding: utf-8 -*-  
from pyExcelerator import *  
  
w = Workbook()  
ws1 = w.add_sheet('sheet 1')  
ws2 = w.add_sheet('sheet 2')  
ws3 = w.add_sheet('sheet 3')  
ws4 = w.add_sheet('sheet 4')  
ws5 = w.add_sheet('sheet 5')  
ws6 = w.add_sheet('sheet 6')  
  
for i in range(0x100):  
    ws1.write(i/0x10, i%0x10, i)  
  
for i in range(0x100):  
    ws2.write(i/0x10, i%0x10, i)  
  
for i in range(0x100):  
    ws3.write(i/0x10, i%0x10, i)  
  
for i in range(0x100):  
    ws4.write(i/0x10, i%0x10, i)  
  
for i in range(0x100):  
    ws5.write(i/0x10, i%0x10, i)  
  
for i in range(0x100):  
    ws6.write(i/0x10, i%0x10, i)  
  
ws1.panes_frozen = True  
ws1.horz_split_pos = 2  
  
ws2.panes_frozen = True  
ws2.vert_split_pos = 2  
  
ws3.panes_frozen = True  
ws3.horz_split_pos = 2  
ws3.vert_split_pos = 2  
  
ws4.panes_frozen = False  
ws4.horz_split_pos = 20  
#excel打开时初始显示在第四列,即excel的scrollbar显示在第四列的位置  
ws4.horz_split_first_visible = 4   
  
ws5.panes_frozen = False  
ws5.vert_split_pos = 40  
ws4.vert_split_first_visible = 2  
  
ws6.panes_frozen = False  
ws6.horz_split_pos = 12  
ws4.horz_split_first_visible = 2  
ws6.vert_split_pos = 40  
ws4.vert_split_first_visible = 2  
  
w.save('panes.xls')  

设置行列高度

对行高、列宽的设置可以通过对Worksheet的属性设置来改变。Worksheet中提供了对col和row操作的方法 对列宽的设置

ws.col(i).width = 0  

行高没有相应的属性如height设置,可以通过style

ws.row(i).height = 0  #但是测试好像并没有效果  

可以通过字体样式来达到设置高度的目的

fnt = Font()  
fnt.height = i*20  
style = XFStyle()  
style.font = fnt  
ws.row(i).set_style(style)  

例子如下:

w = Workbook()  
ws = w.add_sheet('sheet')  
  
for i in range(6, 80):  
    fnt = Font()  
    #这里设置字体的高度,可以用来设置行的高度。可没有宽度  
    fnt.height = i*5  
    style = XFStyle()  
    style.font = fnt  
    ws.write(1, i, 'Test', style) #也可以这样来设置行高  
    ws.write(1, i, 'Test')  
    #这里设置列宽,主要通过设置Worksheet中的col来操作,其底层也是通过设置Column.width来达到改变其宽度的目的  
    ws.col(i).width = 0x0d00 + i*50  
    #这里设置行高,通过设置字体高度  
    ws.row(1).set_style(style)  
w.save('width.xls')  

写入公式

pyExcelerator支持对excel公式的写入,主要在模块ExcelFormula,定义了公式的语法,解析等 主要体现在方法:Worksheet.write 在Worksheet的构造函数中,设置相关写入公式的属性设置,如

self.__save_recalc = 0  
self.__frmla_opts = ExcelFormula.RecalcAlways | ExcelFormula.CalcOnOpen  
self.__show_formulas = 0  

这里设置公式参数,包括四个属性见ExcelFormula.py中:

NoCalcs                           #不计算  
RecalcAlways                   #值改变时立即计算  
CalcOnOpen                    #打开时计算  
PartOfShareFormula        #这个啥意思:部分共享公式?求指点  

一个例子:

from pyExcelerator import *  
import random  
  
w = Workbook()  
ws = w.add_sheet('sheet')  
ws.frmla_opts=RecalcAlways  
#ws.show_formulas = True  
  
#写入一些数据  
for i in range(30):  
    for j in range(20):  
        ws.write(i, j, random.randint(1, 100))  
          
#这里写公式          
ws.write(30, 0, Formula("SUM(A1:A30)"))  
ws.write(30, 2, Formula("SUM(B1:B30)*SUM(C1:C30)"))  

至于更复杂的公式就没尝试了,比如IF,SUMIF之类。其实我也不会写,不过在examples目录也有解析较复杂的例子

f = ExcelFormula.Formula(  
""" -((1.80 + 2.898 * 1)/(1.80 + 2.898))* 
AVERAGE((1.80 + 2.898 * 1)/(1.80 + 2.898);  
        (1.80 + 2.898 * 1)/(1.80 + 2.898);  
        (1.80 + 2.898 * 1)/(1.80 + 2.898)) +  
SIN(PI()/4)""")