精华内容
下载资源
问答
  • JAVA数据库编程和预编译语句

    千次阅读 2010-11-17 10:08:00
    JAVA数据库编程和预编译语句 一、首先介绍一下JDBC JDBC API是一系列抽象的接口,应用程序开发人员通过它可以编写访问数据库Java应用程序。JDBC API中,抽象的实现是由驱动程序开发商提供的。驱动程序实现...
    JAVA数据库编程和预编译语句
    一、首先介绍一下JDBC 
        JDBC API是一系列抽象的接口,应用程序开发人员通过它可以编写访问数据库的Java应用程序。JDBC API中,抽象类的实现是由驱动程序开发商提供的。驱动程序实现了应用程序和某个数据库产品之间的接口,它的治理器则对应用程序和驱动程序之间的交互进行控制。JDBC API主要用来连接数据库和直接调用SQL命令,执行各种SQL语句。利用JDBC API可以执行一般的SQL语句、动态SQL语句,以及带IN和OUT参数的存储过程。
      
      JDBC中的驱动程序必须提供实现方法的接口。JDBC API被定义在java.sql包中,其中定义了JDBC API用到的所有类、接口和方法,主要的类和接口有:
      
      ·DriverManager类——处理驱动程序的装入,为新的数据库连接提供支持。驱动程序要向该类注册后才能被使用。进行连接时,该类根据JDBC URL选择匹配的驱动程序。
      
      ·java.sql.Driver接口——驱动程序接口,负责确认URL与驱动程序的匹配、建立到数据库的连接等,其中的方法需要有相应的驱动程序实现。
      
      ·java.sql.Connection接口——表示到特定数据库的连接,其中的方法需要有相应的驱动程序实现。
      
      ·java.sql.Statement接口——为SQL语句提供一个容器,包括执行SQL语句、取得查询结果等方法。此接口有两个子类型:

      (1)java.sql.PreparedStatement,用于执行预编译的SQL语句;

      (2)java.sql.CallableStatement,用于执行对一个数据库内嵌过程的调用。

      ·java.sql.ResultSet接口——提供对结果集进行处理的手段。
     
    二、在数据库编程中使用预编译语句
     
        使用基本的java.sql.Statement的一个主要缺点是,每次执行数据库操作时,都将SQL命令传递给RDBMS,在执行前进行解析和编译。为了消除重复解析和编译SQL命令所产生的开销,JDBC为用户提供了两种使用预编译SQL语句的方式:PreparedStatement对象和CallableStatement对象
        Statement对象三种不同的用法用在截然不同的场合中。当某条语句只想执行一次时,是使用基本java.sql.Statemetn的理想情况。
        如果有一条SQL命令需要在一个循环内重复执行,然后就放弃,最好的方法是使用PreparedStatement,它是由RDBMS解析、编译并临时缓存的。最后,如果有一条语句或语句组需要频繁执行,CallableStatement是最理想的,因为它已经编译并永久地保存在RDBMS中,需要时,使用名字调用即可。 
        可以用Connection.prepareStatement来创建PreparedStatement的实例,如下: 
        String query="String * from Orders where Amount>?"; 
        PreparedStatement pstate; 
          try{ 
              pstate=connection.prepareStatement(query); 
          }catch(SQLException sqlex){ 
              //处理异常 
          } 
    在预编译语句中,变量用问号来指定。预编译语句可以包含无限数目的变量。 一旦创建了预编译语句,就可以像常规语句一样来执行它了。例如,上面代码段列出的预编译语句可以这样执行: 
        pstate.setDouble(1,100.00); 
          try{ 
              ResultSet result=pstate.executeQuery(); 
          } 
          catch(SQLException e){ 
              //处理异常 
       } 
    PreparedStatement类有许多指定变量的setXXX方法,其中XXX指数据类型。这些方法有两个参数,一个指定变量的位置(第一个变量从1开始),另一个指定变量值。例如,在上面的代码中,使用PredStatement.setDouble在预编译语句中将第一个(也是唯一的一个)变量设置为100.00。 
    下面例子演示如何在循环中使用PreparedStatement
     

    import java.sql.*;
    import javax.sql.*;
    public class PStatement {
      private static String dbUserName = "sa";
      private static String dbPassword = "dba";
      public static void main(String args[]){
        int[][] Orders = {{1001,327,2},
                          {1001,412,1},
                          {1001,906,5},
                          {1002,111,7},
                          {1002,112,19}};      
        try {
          Class.forName("com.inet.pool.PoolDriver");
          com.inet.tds.TdsDataSource tds = new com.inet.tds.TdsDataSource();
          tds.setServerName( "JUPITER" );
          tds.setDatabaseName( "MEMBERS" );
          tds.setUser( dbUserName );
          tds.setPassword( dbPassword );
          DataSource ds = tds;
          Connection con = ds.getConnection(dbUserName,dbPassword); 

          String SQLCmd ="INSERT INTO ORDERED_ITEMS (ORDER_NUMBER,ITEM_NUMBER,QTY) VALUES(?,?,?)";
          PreparedStatement pstmt = con.prepareStatement(SQLCmd); 
           for(int i=0;i<5;i++){
            pstmt.setInt(1, Orders[i][0]);
            pstmt.setInt(2, Orders[i][1]);
            pstmt.setInt(3, Orders[i][2]);
            pstmt.executeUpdate();
          }
          con.close();

        }catch(ClassNotFoundException e1){
          System.err.println(e1.getMessage());
        }catch(SQLException e2){
          System.err.println(e2.getMessage());
        }
    }
    }

    展开全文
  • 作为一名专门写bug的Java程序猿,相信大家都会遇到过这样的问题:项目的业务逻辑很复杂,而且还经常变化,今天的一个办理条件是小于5,明天就变成了大于10或者条件作废。这就很头疼了,里面的数字可以抽取到配置文件...

    作为一名专门写bug的Java程序猿,相信大家都会遇到过这样的问题:项目的业务逻辑很复杂,而且还经常变化,今天的一个办理条件是小于5,明天就变成了大于10或者条件作废。这就很头疼了,里面的数字可以抽取到配置文件,但是大于和小于呢?条件作废呢?

    对于业务规则的复杂性,我们可以使用一些规则引擎来解决代码可读性差的问题。市面上也有不少的规则引擎框架,开源的不开源的,收费的不收费的,我们这里推荐使用的是EasyRules(https://github.com/j-easy/easy-rules)。

    对于业务规则的变化性,部分变量的值可以抽取出来放到配置文件里面。但是大部分的需求变化,可不是改变一下变量的值那么简单,可能是一大段代码的重写,这就需要利用Java6之后提供的动态编译来实现了。

    废话不多说,精彩马上来!

    思路:在EasyRules中,一个if (...) {...}对应一条规则,也对应着一个类。这样我们可以将这个类的信息(源码、编译后字节码、类名、所属分组等)存到数据库,以提供系统在运行时修改源码、重新编译、动态加载、替换规则的功能。

    具体实现:定义规则类,这个类除了有EasyRule的类名、源码、编译后字节码等信息之外,还有一些其它属性,比如规则所属分组、执行优先级、启动状态等。当我们在页面新增(或者修改)了源码,提交之后对其进行编译,将得到类名和字节码,然后将这些数据保存到数据库。如果规则是启用状态,还要创建一个实例存放到到我们维护的一个map集合里(如果存在同类名的实例就替换),以供规则引擎去调用。

    一、EasyRules

    什么是EasyRules

    首先EasyRule是一个规则引擎.这个名字由来是受到了Martin Fowler 的文章 Should I use a Rules Engine

    You can build a simple rules engine yourself. All you need is to create a bunch of objects with conditions and actions, store them in a collection, and run through them to evaluate the conditions and execute the actions.

    框架特点

    • 轻量级类库和容易上手
    • 基于POJO的开发与注解的编程模型
    • 方便且适用于java的抽象的业务模型规则
    • 支持从简单的规则创建组合规则

    Useful abstractions to define business rules and apply them easily with Java
    The ability to create composite rules from primitive ones

    官方demo

    1. First, define your rule..

    Either in a declarative way using annotations:

    @Rule(name = "weather rule", description = "if it rains then take an umbrella" )
    public class WeatherRule {
    
        @Condition
        public boolean itRains(@Fact("rain") boolean rain) {
            return rain;
        }
        
        @Action
        public void takeAnUmbrella() {
            System.out.println("It rains, take an umbrella!");
        }
    }

    Or in a programmatic way with a fluent API:

    Rule weatherRule = new RuleBuilder()
            .name("weather rule")
            .description("if it rains then take an umbrella")
            .when(facts -> facts.get("rain").equals(true))
            .then(facts -> System.out.println("It rains, take an umbrella!"))
            .build();

    Or using an Expression Language:

    Rule weatherRule = new MVELRule()
            .name("weather rule")
            .description("if it rains then take an umbrella")
            .when("rain == true")
            .then("System.out.println(\"It rains, take an umbrella!\");");

    Or using a rule descriptor:

    Like in the following weather-rule.yml example file:

    name: "weather rule"
    description: "if it rains then take an umbrella"
    condition: "rain == true"
    actions:
      - "System.out.println(\"It rains, take an umbrella!\");"
    Rule weatherRule = MVELRuleFactory.createRuleFrom(new File("weather-rule.yml"));

    2. Then, fire it!

    public class Test {
        public static void main(String[] args) {
            // define facts
            Facts facts = new Facts();
            facts.put("rain", true);
    
            // define rules
            Rule weatherRule = ...
            Rules rules = new Rules();
            rules.register(weatherRule);
    
            // fire rules on known facts
            RulesEngine rulesEngine = new DefaultRulesEngine();
            rulesEngine.fire(rules, facts);
        }
    }

    上面例子没有提到的是,规则类可以设置执行优先级,具体做法就是在类里面定义一个返回类型是int的方法,然后在方法上面加一个注解@Priority。

    二、规则信息类

    我们将类名叫做JavaRuleDO,所有属性都对应着数据库JAVA_RULE表的字段。

    这里使用了lombok插件,因此可以省略了getter、setter和toString方法,还有其它注解,挺好用的,感兴趣的童鞋可以去看看。

    @Getter
    @Setter
    @ToString
    @Entity
    @Table(name = "JAVA_RULE")
    public class JavaRuleDO implements Serializable {
    	private static final long serialVersionUID = 830103606495004702L;
    
    	@Id
    	private Long id;
    	// 目标,一般指哪个系统
    	@Column
    	private String target;
    	// 文件名
    	@Column
    	private String fileName;
    	// 全类名
    	@Column
    	private String fullClassName;
    	// 类名
    	@Column
    	private String simpleClassName;
    	// 源码
    	@Column
    	private String srcCode;
    	// 编译后字节码
    	@Column
    	private byte[] byteContent;
    	// 创建时间
    	@Column
    	private Date createTime;
    	// 创建用户id
    	@Column
    	private Long createUserId = Consts.Entity.NULL_ID_PLACEHOLDER;
    	// 创建用户名称
    	@Column
    	private String createUserName;
    	// 更新时间
    	@Column
    	private Date updateTime;
    	// 更新用户id
    	@Column
    	private Long updateUserId = Consts.Entity.NULL_ID_PLACEHOLDER;
    	// 更新用户名称
    	@Column
    	private String updateUserName;
    	// 是否已删除,1是 0否
    	@Column
    	private Integer isDeleted = Consts.Entity.NOT_DELETED;
    	// 状态,1有效 0无效
    	@Column
    	private Integer status = Consts.Entity.NOT_VALID;
    	// 组别名称,一般指哪一系列规则
    	@Column
    	private String groupName;
    	// 顺序(优先级)
    	@Column
    	private Integer sort = Integer.MAX_VALUE;
    	// 规则名称
    	@Column
    	private String name;
    	// 规则描述
    	@Column
    	private String description;
    	
    }

    下面附上Oracle版本的建表SQL。

    create table JAVA_RULE
    (
      id                NUMBER(11) not null,
      target            VARCHAR2(32) not null,
      file_name         VARCHAR2(32) not null,
      full_class_name   VARCHAR2(64) not null,
      simple_class_name VARCHAR2(32) not null,
      src_code          CLOB not null,
      byte_content      BLOB not null,
      create_time       DATE not null,
      create_user_id    NUMBER(11) not null,
      create_user_name  VARCHAR2(128) not null,
      update_time       DATE,
      update_user_id    NUMBER(11),
      update_user_name  VARCHAR2(128),
      is_deleted        NUMBER(1) default 0 not null,
      status            NUMBER(1) default 1 not null,
      group_name        VARCHAR2(32) not null,
      sort              NUMBER(3) default 999,
      name              VARCHAR2(512) not null,
      description       VARCHAR2(2048)
    )
    ;
    comment on column JAVA_RULE.id
      is '主键';
    comment on column JAVA_RULE.target
      is '目标,一般指哪个系统';
    comment on column JAVA_RULE.file_name
      is '文件名';
    comment on column JAVA_RULE.full_class_name
      is '全类名';
    comment on column JAVA_RULE.simple_class_name
      is '类名';
    comment on column JAVA_RULE.src_code
      is '源码';
    comment on column JAVA_RULE.byte_content
      is '编译后字节码';
    comment on column JAVA_RULE.create_time
      is '创建时间';
    comment on column JAVA_RULE.create_user_id
      is '创建用户id';
    comment on column JAVA_RULE.create_user_name
      is '创建用户名称';
    comment on column JAVA_RULE.update_time
      is '更新时间';
    comment on column JAVA_RULE.update_user_id
      is '更新用户id';
    comment on column JAVA_RULE.update_user_name
      is '更新用户名称';
    comment on column JAVA_RULE.is_deleted
      is '是否已删除,1是 0否';
    comment on column JAVA_RULE.status
      is '状态,1有效 0无效';
    comment on column JAVA_RULE.group_name
      is '组别名称,一般指哪一系列规则';
    comment on column JAVA_RULE.sort
      is '顺序(优先级)';
    comment on column JAVA_RULE.name
      is '规则名称';
    comment on column JAVA_RULE.description
      is '规则描述';
    create unique index IDX_JAVA_RULE_FULL_CLASS_NAME on JAVA_RULE (FULL_CLASS_NAME);
    alter table JAVA_RULE
      add constraint PK_JAVA_RULE primary key (ID);

    三、动态编译

    动态编译一直是Java的梦想,从Java 6版本它开始支持动态编译了,可以在运行期直接编译.java文件,执行.class。但是还要慎用,因为存在性能和安全问题。

    下面提供了一个编译源码的工具方法。如果编译成功,返回一个CompileResult对象(自定义类型,下面给出了源码);如果编译失败,返回具体的编译不通过原因。其中,Result是贵公司封装的一个通用返回值包装器,这里不方便提供源码,抱歉。

    代码1:动态编译源码的工具方法(想了解更多细节可以去参考其它资料),暂时先去掉了后面会用到的其它工具方法。

    /**
     * 规则工具类
     * @author z_hh  
     * @date 2018年12月7日
     */
    @Slf4j
    public class DynamicRuleUtils {
        // 编译版本
        private static final String TARGET_CLASS_VERSION = "1.8";
    
    	/**
    	 * auto fill in the java-name with code, return null if cannot find the public class
    	 * 
    	 * @param javaSrc source code string
    	 * @return return the Map, the KEY means ClassName, the VALUE means bytecode.
    	 * @throws RuntimeException
    	 */
    	public static Result<CompileResult> compile(String javaSrc) throws RuntimeException {
    		Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
    		Matcher matcher = pattern.matcher(javaSrc);
    		if (matcher.find()) {
    			return compile(matcher.group(1) + ".java", javaSrc);
    		}
    		return Results.error("找不到类名称!");
    	}
    
    	/**
    	 * @param javaName the name of your public class,eg: <code>TestClass.java</code>
    	 * @param javaSrc source code string
    	 * @return return the Map, the KEY means ClassName, the VALUE means bytecode.
    	 * @throws RuntimeException
    	 */
    	public static Result<CompileResult> compile(String javaName, String javaSrc) throws RuntimeException {
    		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    		StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
    		try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
    			JavaFileObject javaFileObject = MemoryJavaFileManager.makeStringSource(javaName, javaSrc);
    			DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
    			List<String> options = new ArrayList<>();
    	        options.add("-target");
    	        options.add(TARGET_CLASS_VERSION);
    			JavaCompiler.CompilationTask task = compiler.getTask(null, manager, collector, options, null,
    					Arrays.asList(javaFileObject));
    			if (task.call()) {
    				return Results.success(CompileResult.builder()
    						.mainClassFileName(javaName)
    						.byteCode(manager.getClassBytes())
    						.build());
    			}
    			String errorMessage = collector.getDiagnostics().stream()
    				.map(diagnostics -> diagnostics.toString())
    				.reduce("", (s1, s2) -> s1 + "\n" +s2);
    			return Results.error(errorMessage);
    		} catch (IOException e) {
    			log.error("编译出错啦!", e);
    			return Results.error(e.getMessage());
    		}
    	}
    }
    
    /**
     * JavaFileManager that keeps compiled .class bytes in memory.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    final class MemoryJavaFileManager extends ForwardingJavaFileManager {
    	/**
    	 * Java source file extension.
    	 */
    	private final static String EXT = ".java";
    	private Map<String, byte[]> classBytes;
    
    	public MemoryJavaFileManager(JavaFileManager fileManager) {
    		super(fileManager);
    		classBytes = new HashMap<String, byte[]>();
    	}
    
    	public Map<String, byte[]> getClassBytes() {
    		return classBytes;
    	}
    
    	@Override
    	public void close() throws IOException {
    		classBytes = new HashMap<String, byte[]>();
    	}
    
    	@Override
    	public void flush() throws IOException {
    	}
    
    	/**
    	 * A file object used to represent Java source coming from a string.
    	 */
    	private static class StringInputBuffer extends SimpleJavaFileObject {
    		final String code;
    
    		StringInputBuffer(String name, String code) {
    			super(toURI(name), Kind.SOURCE);
    			this.code = code;
    		}
    
    		public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
    			return CharBuffer.wrap(code);
    		}
    
    		@SuppressWarnings("unused")
    		public Reader openReader() {
    			return new StringReader(code);
    		}
    	}
    
    	/**
    	 * A file object that stores Java bytecode into the classBytes map.
    	 */
    	private class ClassOutputBuffer extends SimpleJavaFileObject {
    		private String name;
    
    		ClassOutputBuffer(String name) {
    			super(toURI(name), Kind.CLASS);
    			this.name = name;
    		}
    
    		public OutputStream openOutputStream() {
    			return new FilterOutputStream(new ByteArrayOutputStream()) {
    				public void close() throws IOException {
    					out.close();
    					ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
    					classBytes.put(name, bos.toByteArray());
    				}
    			};
    		}
    	}
    
    	@Override
    	public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
    			JavaFileObject.Kind kind, FileObject sibling) throws IOException {
    		if (kind == JavaFileObject.Kind.CLASS) {
    			return new ClassOutputBuffer(className);
    		} else {
    			return super.getJavaFileForOutput(location, className, kind, sibling);
    		}
    	}
    
    	static JavaFileObject makeStringSource(String name, String code) {
    		return new StringInputBuffer(name, code);
    	}
    
    	static URI toURI(String name) {
    		File file = new File(name);
    		if (file.exists()) {
    			return file.toURI();
    		} else {
    			try {
    				final StringBuilder newUri = new StringBuilder();
    				newUri.append("mfm:///");
    				newUri.append(name.replace('.', '/'));
    				if (name.endsWith(EXT))
    					newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
    				return URI.create(newUri.toString());
    			} catch (Exception exp) {
    				return URI.create("mfm:///com/sun/script/java/java_source");
    			}
    		}
    	}
    }

    代码2: CompileResult类,之所以设置mainClassFileName,是因为我们暂时不支持内部类,只保存顶级类的字节码。

    /**
     * 类编译结果
     * @author z_hh  
     * @date 2018年12月5日
     */
    @Getter
    @Setter
    @Builder
    @ToString
    public class CompileResult {
    	
    	// 主类全类名
    	private String mainClassFileName;
    	
    	// 编译出来的全类名和对应class字节码
    	private Map<String, byte[]> byteCode;
    	
    }

    代码3:将编译得到的信息设置到实体对象。

    // 编译
        private Result<JavaRuleDO> compiler(JavaRuleDO entity) {
        	Result<?> result = DynamicRuleUtils.compile(entity.getSrcCode());
        	if (result.isError()) {
    			return (Result<JavaRuleDO>) result;
    		}
        	CompileResult compileResult = (CompileResult) result.get();
        	for (String classFullName : compileResult.getByteCode().keySet()) {
        		int lastIndex = classFullName.lastIndexOf(".");
    			String simpleName = lastIndex != -1 ? classFullName.substring(lastIndex + 1) : classFullName,
    				fileName = compileResult.getMainClassFileName();
    			// 只要最外层的类
    			if (fileName.startsWith(simpleName)) {
    				entity.setFileName(fileName);
    				entity.setFullClassName(classFullName);
    				entity.setSimpleClassName(simpleName);
    				entity.setByteContent(compileResult.getByteCode().get(classFullName));
    				return Results.success(entity);
    			}
    		}
        	return Results.error("没有找到最外层类!");
        }

    四、动态加载

    在动态编译阶段,我们已经得到了源码对应的字节码,这样就可以将其加载到JVM里面了。

    在这里有必要提两点:第一,在Java虚拟机层面,相同的一个类,除了有相同的全类名以外,还要由相同的类加载器进行加载;第二,类加载的双亲委派模型,工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

    因此,我们需要定义自己的类加载器,每次将class字节码加载到JVM都需要创建一个新的类加载器对象。主要是重写findClass方法,将我们传进去的字节码数组进行加载。

        /*
    	 * a temp memory class loader
    	 */
            private static class MemoryClassLoader extends URLClassLoader {
    		Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
    
    		public MemoryClassLoader(Map<String, byte[]> classBytes) {
    			super(new URL[0], MemoryClassLoader.class.getClassLoader());
    			this.classBytes.putAll(classBytes);
    		}
    
    		@Override
    		protected Class<?> findClass(String name) throws ClassNotFoundException {
    			byte[] buf = classBytes.get(name);
    			if (buf == null) {
    				return super.findClass(name);
    			}
    			classBytes.remove(name);
    			return defineClass(name, buf, 0, buf.length);
    		}
    	}

    具体使用方式。

    /**
    	 * 从JavaRuleDO获取规则Class对象
    	 * @param javaRule
    	 * @throws Exception
    	 */
    	public static Class<?> getRuleClass(JavaRuleDO javaRule) throws Exception {
    		Map<String, byte[]> bytecode = new HashMap<>();
    		String fullName = Objects.requireNonNull(javaRule.getFullClassName(), "全类名不能为空!");
    		bytecode.put(fullName, javaRule.getByteContent());
    		try (MemoryClassLoader classLoader = new MemoryClassLoader(bytecode)) {
    			return classLoader.findClass(fullName);
    		} catch (Exception e) {
    			log.error("加载类{}异常!", fullName);
    			throw e;
    		}
    	}

    五、定义规则基类

    这里强制所有规则类都要继承我们定义好的规则基类,有两个原因:

    1、定义优先级属性,并提供set方法供外部设值、get方法供规则引擎获取。

    2、重写hashCode方法和equals方法,以让容器(HashSet)认定相同类型的两个元素是相同的。

    /**
     * 规则基类
     * @author z_hh  
     * @date 2018年12月12日
     */
    public class BaseRule {
    
    	private int priority = Integer.MAX_VALUE;
    	
    	/*重写equals方法和hashCode方法,让Set集合判定同类型的两个对象相同*/
    	
    	@Override
    	public boolean equals(Object obj) {
    		return Objects.nonNull(obj) 
    				&& Objects.equals(this.getClass().getName(), obj.getClass().getName());
    	}
    	
    	@Override
    	public int hashCode() {
    		return Objects.hashCode(this.getClass().getName());
    	}
    	
    	/**
    	 * 获取优先级
    	 */
    	@Priority
    	public int getPriority() {
    		return priority;
    	}
    	
    	public void setPriority(int priority) {
    		this.priority = priority;
    	}
    	
    }

    六、维护规则容器

    首先我们定义一个容器接口,声明一些需要提供的接口。毕竟面向接口编程,方便以后扩展容器类型。

    /**
     * Java规则类存储器
     * @author z_hh  
     * @date 2018年12月12日
     */
    public interface JavaRuleStorage {
    	
    	/**
    	 * 容器是否包含指定规则
    	 * @param javaRule
    	 * @return
    	 */
    	boolean contains(String groupName, BaseRule rule);
    
    	/**
    	 * 添加规则到容器
    	 * @param javaRule
    	 */
    	boolean add(String groupName, BaseRule rule);
    	
    	/**
    	 * 批量添加规则到容器的指定组
    	 * @param javaRules
    	 */
    	boolean batchAdd(String groupName, Iterable<? extends BaseRule> rules);
    	
    	/**
    	 * 从容器移除指定规则
    	 * @param group
    	 */
    	boolean remove(String groupName, BaseRule rule);
    	
    	/**
    	 * 从容器移除指定组的规则
    	 * @param group
    	 */
    	boolean remove(String group);
    	
    	/**
    	 * 从容器获取指定组的所有规则
    	 * @param group
    	 * @return
    	 */
    	Collection<BaseRule> listObjByGroup(String group);
    }

    然后提供一个map实现版。或者使用Spring IOC容器,但我感觉没有那么灵活。这里的Multimap是Google Guava提供的集合类工具,类似于JDK里面的Map<K, Set<E>>。需要注意的是,新增元素的时候,如果存在相同key和相同value类型的元素,需要先将其移除。

    /**
     * Java规则类存储器Map版
     * @author z_hh  
     * @date 2018年12月12日
     */
    public class MapJavaRuleStorage implements JavaRuleStorage {
    	
    	private final Multimap<String, BaseRule> map = HashMultimap.create();
    
    	@Override
    	public boolean contains(String groupName, BaseRule rule) {
    		return map.containsEntry(groupName, rule);
    	}
    
    	@Override
    	public boolean add(String groupName, BaseRule rule) {
    		// 如果原来有,就先删除掉
    		if (map.containsEntry(groupName, rule)) {
    			map.remove(groupName, rule);
    		}
    		return map.put(groupName, rule);
    	}
    
    	@Override
    	public boolean batchAdd(String groupName, Iterable<? extends BaseRule> rules) {
    		return map.putAll(groupName, rules);
    	}
    
    	@Override
    	public boolean remove(String groupName, BaseRule rule) {
    		return map.remove(groupName, rule);
    	}
    
    	@Override
    	public boolean remove(String group) {
    		return !map.removeAll(group).isEmpty();
    	}
    
    	@Override
    	public Collection<BaseRule> listObjByGroup(String group) {
    		return map.get(group);
    	}
    
    }

    七、添加规则到容器、将规则从容器移除

    获取规则实例的工具方法(其中getRuleClass(...)方法上面已提供)。

    /**
    	 * 从JavaRuleDO获取规则实例对象
    	 * @param javaRule
    	 * @throws Exception
    	 */
    	public static BaseRule getRuleInstance(JavaRuleDO javaRule) throws Exception {
    		try {
    			BaseRule rule = (BaseRule) getRuleClass(javaRule).newInstance();
    			// 设置优先级
    			rule.setPriority(javaRule.getSort());
    			return rule;
    		} catch (Exception e) {
    			log.error("从JavaRuleDO获取规则实例异常!", e);
    			throw e;
    		}
    	}

    添加规则到容器,其中entity为JavaRuleDO实例。

    /**
         * 添加规则到容器
         * @param entity
         * @return
         */
        private Result<JavaRuleDO> addRuleToStorage(JavaRuleDO entity) {
    		try {
    			BaseRule rule = DynamicRuleUtils.getRuleInstance(entity);
    			return javaRuleStorage.add(entity.getGroupName(), rule) ? Results.success(entity)
    	    			: Results.error("添加规则到容器失败!");
    		} catch (Exception e) {
    			log.error("添加规则{}到容器异常!", entity.getName(), e);
    			return Results.error("添加规则到容器异常!");
    		}
        }

    将规则从容器移除,其中entity为JavaRuleDO实例。

    String groupName = entity.getGroupName();
    try {
    	BaseRule rule = DynamicRuleUtils.getRuleInstance(entity);
    	if (javaRuleStorage.contains(groupName, rule) && !javaRuleStorage.remove(groupName, rule)) {
    		return Results.error("从容器移除规则失败!");
    	}
    } catch (Exception e) {
    	log.error("从容器移除规则{}异常!", entity.getName(), e);
    	return Results.error("从容器移除规则异常!");
    }

    八、封装规则使用组件

    为了更方便使用规则引擎,我们特意创建了一个DynamicRuleManager,以实现链式调用。

    /**
     * 动态规则管理器
     * @author z_hh  
     * @date 2018年12月12日
     */
    @Component("dynamicRuleManager")
    public class DynamicRuleManager {
    	
    	public Builder builder() {
    		return new Builder(this);
    	}
        
        public class Builder {
        	private Rules rules = new Rules();
        	private Facts facts = new Facts();
        	private RulesEngine engine = new DefaultRulesEngine();
        	private JavaRuleStorage javaRuleStorage;
        	
        	public Builder(DynamicRuleManager dynamicRuleManager) {
        		javaRuleStorage = dynamicRuleManager.javaRuleStorage;
    		}
            
        	/**
        	 * 设置参数,该参数为值传递,在规则里面或者执行完之后可以取到
        	 * @param name
        	 * @param value
        	 * @return
        	 */
            public Builder setParameter(String name, Object value) {
            	facts.put(name, value);
                return this;
            }
            
            /**
             * 增加规则组(将指定所属分组的所有启用规则添加进来)
             * @param groupName
             * @return
             */
            public Builder addRuleGroup(String groupName) {
            	Collection<BaseRule> rs = javaRuleStorage.listObjByGroup(groupName);
            	rs.stream().forEach(rules::register);
            	return this;
            }
            
            /**
             * 运行规则引擎
             */
            public Builder run() {
            	engine.fire(rules, facts);
            	return this;
            }
            
            /**
             * 获取指定参数,并转为指定类型
             * @param pName
             * @param pType
             * @return
             */
            public <T> T getParameter(String pName, Class<T> pType) {
            	return facts.get(pName);
            }
            
        }
        
        @Autowired
        private JavaRuleStorage javaRuleStorage;
        
    }
    @Configuration
    public class RuleDefaultConf {
    
    	@Bean
    	@ConditionalOnMissingBean
    	public JavaRuleStorage javaRuleStorage() {
    		return new MapJavaRuleStorage();
    	}
    }

    九、使用案例

    1、创建规则。

    @Rule
    public class DemoRule1 extends BaseRule {
    
    	@Condition
    	public boolean when(@Fact("param1") String param1) {
    		System.out.println("我是参数1,value=" + param1);
    		return true;
    	}
    	
    	@Action
    	public void then(@Fact("param2") String param2) {
    		System.out.println("我是参数2,value=" + param2);
    	}
    }

    2、调用规则。

    Builder builder = dynamicRuleManager.builder()
    			.setParameter("param1", "Hello")
    			.setParameter("param2", "World")
    			.addRuleGroup("testRule")
    			.run();
    		String param1 = builder.getParameter("param1", String.class);
    		String param2 = builder.getParameter("param2", String.class);
    		System.out.println(param1 + " " + param2);

    3、执行结果。

    十、系统启动时将启用的规则添加到容器

    我们设置了dynamic.rule.target(目标,所属系统)参数从配置文件获取。

    /**
     * 应用启动监听器
     * @author z_hh  
     * @date 2018年12月10日
     */
    @Slf4j
    @WebListener
    public class AppRunListener implements ServletContextListener {
    	
    	@Value("${dynamic.rule.target}")
    	private String ruleTarget;
    	
    	/**
    	 * 将指定组的javaRule对象装进容器
    	 */
    	@Override
    	public void contextInitialized(ServletContextEvent event) {
    		if (StringUtils2.notEmpty(ruleTarget)) {
    			List<JavaRuleDO> javaRules = javaRuleService.createJpaQuery()
    				.where("status", Consts.Entity.IS_VALID)
    				.where("target", SqlFieldOperatorEnum.IN, Arrays.asList(ruleTarget.split(",")))
    				.list();
    			javaRules.stream()
    				.forEach(javaRule -> {
    					try {
    						BaseRule rule = DynamicRuleUtils.getRuleInstance(javaRule);
    						if (!javaRuleStorage.add(javaRule.getGroupName(), rule)) {
    							log.warn("添加规则{}到容器失败!", javaRule.getName());
    							javaRule.setStatus(Consts.Entity.NOT_VALID);
    							javaRuleService.save(javaRule);
    						}
    						log.info("添加了规则{}到容器", javaRule.getFullClassName());
    					} catch (Exception e) {
    						log.warn("添加规则{}到容器异常!", javaRule.getName());
    						javaRule.setStatus(Consts.Entity.NOT_VALID);
    						javaRuleService.save(javaRule);
    					}
    					
    				});
    		}
    	}
    	
    	@Override
    	public void contextDestroyed(ServletContextEvent event) {
    		// 
    	}
    	
    	@Autowired
    	private JavaRuleService javaRuleService;
    	@Autowired
    	private JavaRuleStorage javaRuleStorage;
    }

    十一、其它说明

    1、当我们在管理页面新增或者修改规则时,如果状态为启用,后台应该需要在编译之后创建规则实例并放进容器;反之,如果状态为禁用,后台就判断容器里是否有该规则类的实例,有的话需要将其移除。

    2、管理页面的启用和禁用,对应着这个规则类实例添加到容器和从容器里面移除的操作。

    3、删除规则时,如果启用状态不能删除,需要先将其禁用。

    十二、相关页面展示

    本文内容到此结束了,有什么问题或者建议,欢迎在评论区进行探讨!

    博文内容没有将涉及的代码全部展示。完整的代码已经打包上传到我的资源,大家可以去下载参考;还有,部分代码涉及到公司的框架,并没有包含在里面,可以换一种自己的方式实现的,抱歉。

    展开全文
  • Java数据库连接 - 预编译SQL语句

    千次阅读 2015-12-11 17:37:46
    3. 预编译SQL语句 将上述的Statement替换成PreparedStatement(表示预编译的...3.1 通过java.sql.Connection实例,调用prepareStatement(String sql)方法,以参数化的SQL语句作为参数,创建PreparedStatement对象. 3.2

    3. 预编译SQL语句

    将上述的Statement替换成PreparedStatement(表示预编译的 SQL 语句的对象),使用此对象可以高效地多次执行SQL语句。

     

    一般步骤:

    3.1 通过java.sql.Connection实例,调用prepareStatement(String sql)方法,以参数化的SQL语句作为参数,创建PreparedStatement对象.

    3.2 通过java.sql.PreparedStatement实例,调用setXXX()方法,设置对应的参数值.

    3.3 通过java.sql.PreparedStatement实例,调用executeQuery(),executeUpdate()等方法,执行数据库查询,更新等操作.

     

    java.sql.Connection类中成员方法:

    PreparedStatement prepareStatement(String sql)

        创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库。

     

    java.sql.PreparedStatement类中成员方法:

    ResultSet executeQuery()

              在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。

     

    int executeUpdate()

              在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL INSERT、UPDATE 或 DELETE语句;或者是一个什么都不返回的 SQL 语句,比如 DDL 语句。

     

     

    关键代码:

    String username = "ye";

    String password = "ye";

    String sql = "select * from user where username = ? and password = ?;";

     

    db.stmt = db.conn.prepareStatement(sql);

    db.stmt.setString(1, username);

    db.stmt.setString(2, password);

          

    ResultSet rs = db.stmt.executeQuery();

    展开全文
  • Java 运行时动态编译源代码原理和实现

    万次阅读 多人点赞 2017-03-02 23:08:37
    编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转换成class字节码的过程,而不是真正的机器码,这是因为...

    编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转换成class字节码的过程,而不是真正的机器码,这是因为中间隔着一个JVM。虽然对于编译的理解不同,但是编译的过程基本上都是相同的。但是我们熟悉的编译大都是点击一下Eclipse或者Intellij Idea的Run或者Build按钮,但是在点击后究竟发生什么?其实我没仔细了解过,只是知道这个程序运行起来了,但是如果你使用过javac命令去编译代码时,可能了解的就更深一些,据说印度的Java程序员最开始编程的时候使用的都是文本编辑器而不是IDE,这样更能接触底层的过程。
    除了使用javac命令编译Java程序,从Java 1.6开始,我们也可以在程序运行时根据程序实际运行来构建一些类并进行编译,这需要JDK提供给我们一些可供调用的接口来完成编译工作。

    一、编译源码需要啥?

    那么问题来了,如果要了解运行时编译的过程和对应的接口,首先要明白的就是编译这个过程都会涉及哪些工具和要解决的问题?从我们熟悉的构建过程开始:

    • 编译工具(编译器):显然没有这个东西我们啥也干不了;
    • 要编译的源代码文件:没有这个东西,到底编啥呢?
    • 源代码、字节码文件的管理:其实这里靠的是文件系统的支持,包括文件的创建和管理;
    • 编译过程中的选项:要编译的代码版本、目标,源代码位置,classpath和编码等等,见相关文章;
    • 编译中编译器输出的诊断信息:告诉你编译成功还是失败,会有什么隐患提出警告信息;

    按照这些信息,JDK也提供了可编程的接口对象上述信息,这些API全部放在javax.tools包下,对应上面的信息如下:

    • 编译器:涉及到的接口和类如下:

      • JavaCompiler
      • JavaCompiler.CompilationTask
      • ToolProvider

      在上面的接口和类中,ToolProvider类似是一个工具箱,它可以提供JavaCompiler类的实例并返回,然后该实例可以获取JavaCompiler.CompilationTask实例,然后由JavaCompiler.CompilationTask实例来执行对应的编译任务,其实这个执行过程是一个并发的过程。

    • 源代码文件:涉及到接口和类如下:

      • FileObject
      • ForwardingFileObject
      • JavaFileObject
      • JavaFileObject.Kind
      • ForwardingJavaFileObject
      • SimpleJavaFileObject

      上述后面的4个接口和类都是FileObject子接口或者实现类,FIleObject接口代表了对文件的一种抽象,可以包括普通的文件,也可以包括数据库中的数据库等,其中规定了一些操作,包括读写操作,读取信息,删除文件等操作。我们要用的其实是JavaFileObject接口,其中还增加了一些操作Java源文件和字节码文件特有的API,而SimpleJavaFileObject是JavaFileObject接口的实现类,但是其中你可以发现很多的接口其实就是直接返回一个值,或者抛出一个异常,并且该类的构造器由protected修饰的,所以要实现复杂的功能,需要我们必须扩展这个类。ForwardingFileObject、ForwardingJavaFileObject类似,其中都是包含了对应的FileObject和JavaFileObject,并将方法的执行委托给这些对象,它的目的其实就是为了提高扩展性。

    • 文件的创建和管理:涉及接口和类如下:

      • JavaFileManager
      • JavaFileManager.Location
      • StandardJavaFileManager
      • ForwardingJavaFileManager
      • StandardLocation

      JavaFileManager用来创建JavaFileObject,包括从特定位置输出和输入一个JavaFileObject,ForwardingJavaFileManager也是出于委托的目的。而StandardJavaFileManager是JavaFileManager直接实现类,JavaFileManager.Location和StandardLocation描述的是JavaFileObject对象的位置,由JavaFileManager使用来决定在哪创建或者搜索文件。由于在javax.tools包下没有JavaFileManager对象的实现类,如果我们想要使用,可以自己实现该接口,也可以通过JavaCompiler类中的getStandardFileManager完成,如下:

      JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
      DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
      // 该JavaFileManager实例是com.sun.tools.javac.file.JavacFileManager
      JavaFileManager manager= compiler.getStandardFileManager(collector, null, null);
    • 编译选项的管理:

      • OptionChecker

      这个接口基本上没有用过。

    • 诊断信息的收集:涉及接口和类如下:

      • Diagnostic
      • DiagnosticListener
      • Diagnostic.Kind
      • DiagnosticCollector

      Diagnostic会输出编译过程中产生的问题,包括问题的信息和出现问题的定位信息,问题的类别则在Diagnostic.Kind中定义。DiagnosticListener则是从编译器中获取诊断信息,当出现诊断信息时则会调用其中的report方法,DiagnosticCollector则是进一步实现了DiagnosticListener,并将诊断信息收集到一个list中以便处理。

    在Java源码运行时编译的时候还会遇到一个与普通编译不同的问题,就是类加载器的问题,由于这个问题过大,而且比较核心,将会专门写一篇文章介绍。

    二、如何在运行时编译源代码?

    好了说了这么多了,其实都是为了下面的实例作为铺垫,我们还是从上述的几个组件来说明。

    1、准备编译器对象

    这里只有一种方法,如下:

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    // ......
    // 在其他实例都已经准备完毕后, 构建编译任务, 其他实例的构建见如下
    Boolean result = compiler.getTask(null, manager, collector, options,null,Arrays.asList(javaFileObject));

    2、诊断信息的收集

    // 初始化诊断收集器
    DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
    // ......
    // 编译完成之后,获取编译过程中的诊断信息
    collector.getDiagnostics().forEach(item -> System.out.println(item.toString()))

    在这个过程中可以通过Diagnostic实例获取编译过程中出错的行号、位置以及错误原因等信息。

    3、源代码文件对象的构建

    由于JDK提供的FileObject、ForwardingFileObject、JavaFileObject、ForwardingJavaFileObject、SimpleJavaFileObject都无法直接使用,所以我们需要根据需求自定义,此时我们要明白SimpleJavaFileObject类中的哪些方法是必须要覆盖的,可以看如下过程:

    这里写图片描述

    下面是调用compiler中的getTask方法时的调用栈,可以看出从main()方法中开始调用getTask方法开始,直到编译工作开始进行,首先读取源代码,调用com.sun.tools.javac.main包中的readSource()方法,源代码如下:

    public CharSequence readSource(JavaFileObject filename) {
      try {
        inputFiles.add(filename);
        return filename.getCharContent(false);
      } catch (IOException e) {
        log.error("error.reading.file", filename, JavacFileManager.getMessage(e));
        return null;
      }
    }

    其中调用ClientCodeWrapper$WrappedFileObject对象中的filename.getCharContent(false)方法来读取要编译的源码,源代码如下:

    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
      try {
        return clientFileObject.getCharContent(ignoreEncodingErrors);
      } catch (ClientCodeException e) {
        throw e;
      } catch (RuntimeException e) {
        throw new ClientCodeException(e);
      } catch (Error e) {
        throw new ClientCodeException(e);
      }
    }

    而其中的clientFileObject.getCharContent(ignoreEncodingErrors),其实就是调用我们实现的自定义的JavaFIleObject对象,因此源代码文本是必须的,因此getCharContent方法是必须实现的,另外在编译器编译完成之后要将编译完成的字节码输出,如下图:

    这里写图片描述

    这时调用writeClass()输出字节码,通过打开一个输出流OutputStream来完成该过程,因此openOutputStream()这个方法也是必须实现的。因此该类的实现如下:

    public static class MyJavaFileObject extends SimpleJavaFileObject {
        private String source;
        private ByteArrayOutputStream outPutStream;
        // 该构造器用来输入源代码
        public MyJavaFileObject(String name, String source) {
            // 1、先初始化父类,由于该URI是通过类名来完成的,必须以.java结尾。
            // 2、如果是一个真实的路径,比如是file:///test/demo/Hello.java则不需要特别加.java
            // 3、这里加的String:///并不是一个真正的URL的schema, 只是为了区分来源
            super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
        }
        // 该构造器用来输出字节码
        public MyJavaFileObject(String name, Kind kind){
            super(URI.create("String:///" + name + kind.extension), kind);
            source = null;
        }
    
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors){
            if(source == null){
                throw new IllegalArgumentException("source == null");
            }
            return source;
        }
    
        @Override
        public OutputStream openOutputStream() throws IOException {
            outPutStream = new ByteArrayOutputStream();
            return outPutStream;
        }
    
        // 获取编译成功的字节码byte[]
        public byte[] getCompiledBytes(){
            return outPutStream.toByteArray();
        }
    }

    4、文件管理器对象的构建

    文件管理对象显然也是不能直接使用JDK提供的接口,因为只有ForwardingJavaFileManager是一个类,其他的都是接口,而且在ForwardingJavaFileManager中构造器又是protected,所以如果想定制化使用的话,需要实现接口或者继承类,如果只是简单使用,可以如下:

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
    // 该JavaFileManager实例是com.sun.tools.javac.file.JavacFileManager
    JavaFileManager manager= compiler.getStandardFileManager(collector, null, null);

    但是compiler.getStandardFileManager()返回的是com.sun.tools.javac.file.JavacFileManager实例,这个不是公开的类,所以我们无法直接使用,只能通过这种调用返回实例。

    但是我们课也可以构造自己的FileManager,为了更好的构建,需要理解JavaFileManager在内存中编译时的使用过程,如下:

    1. 在编译过程中,首先是编译器会遍历JavaFileManager对象,获取指定位置的所有符合要求的JavaFileObject对象,甚至可以递归遍历,这时调用的是list()方法,该方法会扫面所有涉及的到的包,包括一个类和它实现的接口和继承的类:

      这里写图片描述

    2. 之后根据获取到的JavaFileObject对象,获取它的二进制表示的名称,通过调用inferBinaryName()方法;

    3. 之后是输出编译类,而类的表示为JavaFileObject对象,注意此时的JavaFileObject.KindCLASS,调用的方法是getJavaFileForOutput(),注意该方法的调用是在JavaFileObjectopenOutputStream()方法之前,如下图:

      这里写图片描述

    既然了解了上述的流程,我们自定义的文件管理器如下:

    private static Map<String, JavaFileObject> fileObjects = new ConcurrentHashMap<>();
    // 这里继承类,不实现接口是为了避免实现过多的方法
    public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
            protected MyJavaFileManager(JavaFileManager fileManager) {
                super(fileManager);
            }
    
            @Override
            public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
                JavaFileObject javaFileObject = fileObjects.get(className);
                if(javaFileObject == null){
                    super.getJavaFileForInput(location, className, kind);
                }
                return javaFileObject;
            }
    
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
                JavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
                fileObjects.put(qualifiedClassName, javaFileObject);
                return javaFileObject;
            }
    }

    5、编译选项的选择

    在使用javac命令的时候,可以添加很多的选项,在实现API完成编译的时候也可以提供参数,比如编译目标,输出路径以及类路径等等,如下:

    List<String> options = new ArrayList<>();
    options.add("-target");
    options.add("1.8");
    options.add("-d");
    options.add("/");
    // 省略......
    compiler.getTask(null, javaFileManager, collector, options, null, Arrays.asList(javaFileObject));

    6、其他问题

    • 想将编译完成的字节码输出为文件,也不需要上面自定义JavaFileManager,直接使用JavaCompiler提供的即可,而且在自定义的JavaFileObject中也不需要实现OpenOutStream这种方法,代替要提供options.add(“-d”),options.add(“/”)等编译选项;如果不输出为文件按照上述的例子即可;
    • StandardLocation中的元素可以代替真实的路径位置,但是不会输出为文件,可以为一个内存中的文件;
    • 在编译完成之后要将字节码文件加载进来,因此就要涉及到类加载机制,由于这也是一个很大的话题,所以后面会专门总结一篇,但是在这里还是要说明一下,由于上面编译时没有额外的依赖包,所以不用考虑加载依赖文件的问题,但是当如果有这样的需求时,我们可以利用类加载的委托机制,将依赖文件的加载全部交给父加载器去做即可。

    三、完整的例子

    public class App {
        private static Map<String, JavaFileObject> fileObjects = new ConcurrentHashMap<>();
        public static void main( String[] args ) throws IOException {
            String code = "public class Man {\n" +
                    "\tpublic void hello(){\n" +
                    "\t\tSystem.out.println(\"hello world\");\n" +
                    "\t}\n" +
                    "}";
    
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
            JavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null));
    
            List<String> options = new ArrayList<>();
            options.add("-target");
            options.add("1.8");
    
            Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s*");
            Matcher matcher = CLASS_PATTERN.matcher(code);
            String cls;
            if(matcher.find()){
                cls = matcher.group(1);
            } else{
                throw new IllegalArgumentException("No such class name in " + code);
            }
    
            JavaFileObject javaFileObject = new MyJavaFileObject(cls, code);
            Boolean result = compiler.getTask(null, javaFileManager, collector, options, null, Arrays.asList(javaFileObject)).call();
    
            ClassLoader classloader = new MyClassLoader();
    
            Class<?> clazz = null;
            try {
                clazz  = classloader.loadClass(cls);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            Method method = null;
            try {
                method = clazz.getMethod("hello");
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            try {
                method.invoke(clazz.newInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    
        public static class MyClassLoader extends ClassLoader {
    
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                JavaFileObject fileObject = fileObjects.get(name);
                if(fileObject != null){
                    byte[] bytes = ((MyJavaFileObject)fileObject).getCompiledBytes();
                    return defineClass(name, bytes, 0, bytes.length);
                }
                try{
                    return ClassLoader.getSystemClassLoader().loadClass(name);
                } catch (Exception e){
                    return super.findClass(name);
                }
            }
        }
    
        public static class MyJavaFileObject extends SimpleJavaFileObject {
            private String source;
            private ByteArrayOutputStream outPutStream;
    
    
            public MyJavaFileObject(String name, String source) {
                super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
                this.source = source;
            }
    
            public MyJavaFileObject(String name, Kind kind){
                super(URI.create("String:///" + name + kind.extension), kind);
                source = null;
            }
    
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors){
                if(source == null){
                    throw new IllegalArgumentException("source == null");
                }
                return source;
            }
    
            @Override
            public OutputStream openOutputStream() throws IOException {
                outPutStream = new ByteArrayOutputStream();
                return outPutStream;
            }
    
            public byte[] getCompiledBytes(){
                return outPutStream.toByteArray();
            }
        }
    
        public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
            protected MyJavaFileManager(JavaFileManager fileManager) {
                super(fileManager);
            }
    
            @Override
            public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
                JavaFileObject javaFileObject = fileObjects.get(className);
                if(javaFileObject == null){
                    super.getJavaFileForInput(location, className, kind);
                }
                return javaFileObject;
            }
    
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
                JavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
                fileObjects.put(qualifiedClassName, javaFileObject);
                return javaFileObject;
            }
        }
    }

    相关文章:

    展开全文
  • ),提供了直接从数据库表自动生成java实体的功能。本篇介绍使用方法,十分之简单舒畅,后续在测试完毕后将提供Panda ORM的全部源代码(包括数据库自动生成Java实体和添加注解后自动增删改查两部分功能的源码)...
  • 本次试验用的是SQLserver2012,其他数据库只需要更改连接部分,模板仅供参考 ... * 数据库工具 */ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;public class DBU
  • Java连接数据库

    千次阅读 多人点赞 2019-08-13 10:53:23
    Java连接数据库--- 主推预编译Statement 准备工作 1.创建数据库 2.导入jar包 我使用的是mysql-connector-java-5.1.8-bin.jar 简单的插入数据 方法一:预编译Statement try { /** * 通过Class.forName ...
  • jdbc连接mysql数据库 - Java数据库基础

    千次阅读 2020-02-15 19:55:35
    最近复习了一下Java的基础,包括封装、继承、多态、IO、多线程、反射等等,但我感觉JDBC是个大块,花了半天时间实践总结了一下,顺带记录一波。...Java代码需要连接数据库,就可以通过JDBC来连接。...
  • JAVA连接数据库操作java.sql.Connection

    千次阅读 2020-06-03 16:59:13
    java.sql包是java内置的包,其中包含了一系列用于与数据库进行通信的和接口 使用方法: ODBC(Open DataBase Connectivity,开放式数据库连接,是由microsoft公司提供的api):它负责来了解不同厂商和类型的DBMS,为...
  • Java操作数据库详解

    千次阅读 2016-09-29 10:21:27
    Statement和PreparedStatement的区别JDBC简介JDBC(Java Data Base Connectivity)即java数据库连接,是Java核心类库的一部分,提供了操作多种关系数据库提供统一访问,是一种用于执行SQL语句的Java API。...
  • java Compiler API (java编译api)

    千次阅读 多人点赞 2019-05-23 09:59:50
    在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这...使用Java API来编译Java源代码有非常多方法,目前让...
  • 从数据库(Oracle)触发器调用Java代码

    千次阅读 2017-11-29 15:39:14
    官方教程:https://docs.oracle.com/database/122/JJDEV/calling-Java-from-database-triggers.htm#JJDEV13286https://docs.oracle.com/cd/B19306_01/java.102/b14187/chthree.htm参考: ...
  • 嵌入式数据库Java中的应用

    千次阅读 2004-12-21 20:33:00
    嵌入式数据库Java中的应用 内容: 一. Berkeley DB二. SQLite参考资料关于作者Java 专区中还有:教学工具与产品代码与组件所有文章
  • Java 语言中提供了丰富的和接口用于数据库编程,利用这些和接口可以方便的进行数据访问和处理。 Collection 接口 Collection 接口代表与特定的数据库的连接,在连接上下文中执行 SQL 语句并返回结果。...
  • JAVA访问数据库之连接数据库

    千次阅读 2016-01-06 09:57:35
    开发一个访问数据库的应用程序,首先要加载数据库的驱动程序,只需要在第一次访问数据库时加载一次,然后每次运行时创建一个...在连接数据库之前,首先要把JDBC驱动加载到java虚拟机,可以使用java.lang.Class
  • Java中使用JDBC连接数据库

    万次阅读 多人点赞 2019-01-23 17:02:38
    Java中使用JDBC连接数据库 加载驱动 创建数据库连接 创建执行sql的语句 执行语句 处理执行结果 释放资源 源代码附上: package com.demo.test; import java.sql.Connection; import java.sql....
  • Java操作数据库

    万次阅读 2015-11-19 17:14:34
    Java操作数据库--以SQL Server为例 crud介绍(增、删、改、查操作) CRUD是指在做计算处理时的增加(Create)、查询(Retrieve)(重新得到数据)、更新(Update)和删除(Delete)几个单记事的首字母简写。主要被用在...
  • 通过代码实现java编译

    千次阅读 2019-06-03 18:35:33
    通过代码实现java编译Java代码反编译直接上地址说明 Java代码反编译 相信很多童鞋肯定是遇到过反编译Java代码的情况,我猜想你大部分情况下用的是JD-JUI或者说是Idea、Eclipse的一些插件,但是是否曾经想过如何用...
  • Java 数据库操作

    万次阅读 2012-09-11 23:27:32
    JDBC是Java应用与数据库管理系统进行交互的标准API,包括两个包:核心API---java.sql和扩展的API---javax.sql。应用程序通过核心API的接口实现数据库连接和数据处理,其主要接口如下: 接口名称 ...
  • ►JDBC 是一种规范,各数据库厂商为Java 程序员提供标准的数据库访问和接口,使得独立于DBMS 的Java 应用程序的开发工具和产品成为可能。 ►其官方网站为:http://java.sun.com/products/jdbc/
  • 新手,c++操作mysql数据库怎么在sql语句中加入变量或者说有没有像java一样的预编译方法,把数据放入sql语句中执行? 还有像如下的错误表达式为整数或者未区分范围的枚举类型是怎么回事,那错了?求大神解答 ![图片...
  • Java代码编译是由Java源码编译器来完成,流程图如下所示: Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:   Java代码编译和执行的整个过程包含了以下三个重要的机制: Java源码编译机制 ...
  • JAVA通过JDBC链接数据库获取数据(三)PreparedStatement预编译SQL 一、PreparedStatement 作用 1、可以防止SQL注入:对JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement是无效的,这是因为...
  • 目录 1 基本信息 ...3 java程序动态扩展方式 3.1 调用java.lang.Class.forName(…)加载 3.2 用户自定义加载器 4 常见问题分析 4.1 由不同的加载器加载的指定还是相同的类型吗? 4.2 在代码...
  • java中操作数据库中的几个重要

    千次阅读 2017-03-24 09:58:13
    编写访问数据库Java程序还需要几个重要的和接口。 DriverManager DriverManager处理驱动程序的加载和建立新数据库连接。DriverManager是java.sql包中用于管理数据库驱动程序的。通常,应用程序只使用...
  • Java对MySQL数据库进行操作【java

    万次阅读 多人点赞 2016-09-23 14:28:34
    Java访问数据库主要用的方法是JDBC,它是java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法,接下来进行总结。 一、操作的一般过程 1、连接数据库 2、调用...
  • import java.sql.Connection;import java.sql.Date;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.SQLException;/* * 向users表中增加一条记录 */public cl...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 293,642
精华内容 117,456
关键字:

从数据库读java类动态编译

java 订阅