package com.geccocrawler.gecco.dynamic; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.geccocrawler.gecco.annotation.Gecco; import javassist.CannotCompileException; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.CtNewMethod; import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ClassFile; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.ArrayMemberValue; import javassist.bytecode.annotation.IntegerMemberValue; import javassist.bytecode.annotation.MemberValue; import javassist.bytecode.annotation.StringMemberValue; /** * 动态生成SpiderBean,支持类,属性和注解全部动态生成,也支持只动态生成注解 * * @author huchengyi * */ public class JavassistDynamicBean implements DynamicBean { private static Log log = LogFactory.getLog(JavassistDynamicBean.class); public static final String HtmlBean = "html"; public static final String JsonBean = "json"; private static ClassPool pool; static { pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(JavassistDynamicBean.class)); } private CtClass clazz; private ClassFile cfile; private ConstPool cpool; public JavassistDynamicBean(String spiderBeanName) { try { clazz = pool.get(spiderBeanName); } catch (NotFoundException e) { log.error(spiderBeanName + " not found"); } } /** * 构造类 * * @param spiderBeanName * 名称 * @param beanType * 类型html/json */ public JavassistDynamicBean(String spiderBeanName, String beanType) { try { clazz = pool.get(spiderBeanName); } catch (NotFoundException e) { log.debug(spiderBeanName + " not found, to be create!"); try { clazz = pool.makeClass(spiderBeanName); if (beanType.equals(HtmlBean)) { CtClass htmlBeanInterface = pool.get("com.geccocrawler.gecco.spider.HtmlBean"); clazz.addInterface(htmlBeanInterface); } else { CtClass jsonBeanInterface = pool.get("com.geccocrawler.gecco.spider.JsonBean"); clazz.addInterface(jsonBeanInterface); } } catch (NotFoundException cex) { // create error log.error("create class " + spiderBeanName + " error.", cex); } } if (clazz.isFrozen()) { clazz.defrost(); log.info(spiderBeanName + " is frozen"); } cfile = clazz.getClassFile(); cpool = cfile.getConstPool(); } @Override public JavassistDynamicBean gecco(String matchUrl, String... pipelines) { gecco(new String[]{matchUrl}, pipelines); return this; } @Override public JavassistDynamicBean gecco(String matchUrl, String downloader, int timeout, String... pipelines) { gecco(new String[]{matchUrl}, "", 3000, pipelines); return this; } @Override public JavassistDynamicBean gecco(String[] matchUrl, String... pipelines) { gecco(matchUrl, "", 3000, pipelines); return this; } @Override public JavassistDynamicBean gecco(String[] matchUrl, String downloader, int timeout, String... pipelines) { AnnotationsAttribute attr = new AnnotationsAttribute(cpool, AnnotationsAttribute.visibleTag); Annotation annot = new Annotation(Gecco.class.getName(), cpool); // matchUrl //annot.addMemberValue("matchUrl", new StringMemberValue(matchUrl, cpool)); ArrayMemberValue arrayMemberValueMatchUrl = new ArrayMemberValue(cpool); MemberValue[] elementMatchUrls = new StringMemberValue[matchUrl.length]; for (int i = 0; i < matchUrl.length; i++) { elementMatchUrls[i] = new StringMemberValue(matchUrl[i], cpool); } arrayMemberValueMatchUrl.setValue(elementMatchUrls); annot.addMemberValue("matchUrl", arrayMemberValueMatchUrl); // downloader annot.addMemberValue("downloader", new StringMemberValue(downloader, cpool)); // timeout annot.addMemberValue("timeout", new IntegerMemberValue(cpool, timeout)); // pipelines ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cpool); MemberValue[] elements = new StringMemberValue[pipelines.length]; for (int i = 0; i < pipelines.length; i++) { elements[i] = new StringMemberValue(pipelines[i], cpool); } arrayMemberValue.setValue(elements); annot.addMemberValue("pipelines", arrayMemberValue); attr.addAnnotation(annot); cfile.addAttribute(attr); return this; } @Override public DynamicBean removeField(String fieldName) { try { clazz.removeField(clazz.getField(fieldName)); clazz.removeMethod(clazz.getDeclaredMethod("get" + StringUtils.capitalize(fieldName))); clazz.removeMethod(clazz.getDeclaredMethod("set" + StringUtils.capitalize(fieldName))); } catch (NotFoundException e) { e.printStackTrace(); log.error("can't remove field : " + fieldName); } return this; } /** * 返回已经存在的属性 */ @Override public DynamicField existField(String fieldName) { return new JavassistDynamicField(this, clazz, cpool, fieldName); } /** * 由于有歧义,已经被existField代替 */ @Override @Deprecated public DynamicField field(String fieldName) { return existField(fieldName); } /** * 如果当前属性不存在先创建属性以及setter/getter方法和注解。 如果已经属性返回当前属性 */ @Override public DynamicField field(String fieldName, CtClass fieldType) { try { clazz.getField(fieldName); } catch (NotFoundException e) { try { CtField f = new CtField(fieldType, fieldName, clazz); clazz.addField(f); getter(fieldName, f); setter(fieldName, f); } catch (CannotCompileException ex) { ex.printStackTrace(); } } return new JavassistDynamicField(this, clazz, cpool, fieldName); } @Override public DynamicField field(String fieldName, Class<?> fieldClass) { return field(fieldName, FieldType.type(fieldClass)); } private void getter(String fieldName, CtField field) { try { CtMethod m = CtNewMethod.getter("get" + StringUtils.capitalize(fieldName), field); clazz.addMethod(m); } catch (Exception ex) { ex.printStackTrace(); } } private void setter(String fieldName, CtField field) { try { CtMethod m = CtNewMethod.setter("set" + StringUtils.capitalize(fieldName), field); clazz.addMethod(m); } catch (Exception ex) { ex.printStackTrace(); } } @Override public DynamicField stringField(String fieldName) { return field(fieldName, FieldType.stringType); } @Override public DynamicField intField(String fieldName) { return field(fieldName, FieldType.intType); } @Override public DynamicField longField(String fieldName) { return field(fieldName, FieldType.longType); } @Override public DynamicField floatField(String fieldName) { return field(fieldName, FieldType.floatType); } @Override public DynamicField doubleField(String fieldName) { return field(fieldName, FieldType.doubleType); } @Override public DynamicField requestField(String fieldName) { return field(fieldName, FieldType.requestType); } @Override public DynamicField listField(String fieldName, Class<?> memberClass) { return field(fieldName, FieldType.listType(memberClass.getName())); } @Override public Class<?> loadClass() { try { Class<?> loadClass = clazz.toClass(GeccoClassLoader.get(), null); log.debug("load class : " + clazz.getName()); return loadClass; } catch (CannotCompileException e) { e.printStackTrace(); log.error(clazz.getName() + " cannot compile," + e.getMessage()); return null; } finally { // clazz.detach(); } } @Override public Class<?> register() { Class<?> loadClass = loadClass(); if (loadClass.getAnnotation(Gecco.class) != null) { GeccoClassLoader.get().addClass(loadClass.getName(), loadClass); } log.debug("register class : " + clazz.getName()); return loadClass; } @Override public void unloadClass() { if (clazz != null) { clazz.detach(); } } @Override public ConstPool getConstPool() { return cpool; } }