继上一篇 
扩展Spring-实现对外部引用的属性文件的 属性值 进行加密、解密 ,这次要实现的是对整个外部属性文件进行加密,Spring在加载这个外部属性文件时进行解密。
分析过程与在 
扩展Spring-实现对外部引用的属性文件的 属性值 进行加密、解密 中介绍的基本一致,只不过这次的入口就在 PropertiesLoaderSupport.java 这个抽象类的loadProperties方法。代码片段:(注意注释部分)
 @Override
    @Override

 /** *//**
    /** *//**
 * Load properties into the given instance.
     * Load properties into the given instance.
 * @param props the Properties instance to load into
     * @param props the Properties instance to load into
 * @throws java.io.IOException in case of I/O errors
     * @throws java.io.IOException in case of I/O errors
 * @see #setLocations
     * @see #setLocations
 */
     */

 protected void loadProperties(Properties props) throws IOException
    protected void loadProperties(Properties props) throws IOException  {
{

 if (this.locations != null)
        if (this.locations != null)  {
{

 for (int i = 0; i < this.locations.length; i++)
            for (int i = 0; i < this.locations.length; i++)  {
{
 Resource location = this.locations[i];
                Resource location = this.locations[i];

 if (logger.isInfoEnabled())
                if (logger.isInfoEnabled())  {
{
 logger.info("Loading properties file from " + location);
                    logger.info("Loading properties file from " + location);
 }
                }
 InputStream is = null;
                InputStream is = null;

 try
                try  {
{
 // 属性文件的输入流
                    // 属性文件的输入流
 // 因为这个属性文件是我们事先加密过的
                    // 因为这个属性文件是我们事先加密过的
 // 所以在这里我们只要将此输入流解密 再将其作为输入流返回
                    // 所以在这里我们只要将此输入流解密 再将其作为输入流返回
 // 其他的工作就按照Spring的流程即可
                    // 其他的工作就按照Spring的流程即可
 is = location.getInputStream();
                    is = location.getInputStream();

 if (location.getFilename().endsWith(XML_FILE_EXTENSION))
                    if (location.getFilename().endsWith(XML_FILE_EXTENSION))  {
{
 this.propertiesPersister.loadFromXml(props, is);
                        this.propertiesPersister.loadFromXml(props, is);

 } else
                    } else  {
{

 if (this.fileEncoding != null)
                        if (this.fileEncoding != null)  {
{
 this.propertiesPersister.load(props, new InputStreamReader(is,
                            this.propertiesPersister.load(props, new InputStreamReader(is,
 this.fileEncoding));
                                this.fileEncoding));

 } else
                        } else  {
{
 this.propertiesPersister.load(props, is);
                            this.propertiesPersister.load(props, is);
 }
                        }
 }
                    }

 } catch (IOException ex)
                } catch (IOException ex)  {
{

 if (this.ignoreResourceNotFound)
                    if (this.ignoreResourceNotFound)  {
{

 if (logger.isWarnEnabled())
                        if (logger.isWarnEnabled())  {
{
 logger.warn("Could not load properties from " + location + ": "
                            logger.warn("Could not load properties from " + location + ": "
 + ex.getMessage());
                                + ex.getMessage());
 }
                        }

 } else
                    } else  {
{
 throw ex;
                        throw ex;
 }
                    }

 } finally
                } finally  {
{

 if (is != null)
                    if (is != null)  {
{
 is.close();
                        is.close();
 }
                    }
 }
                }
 }
            }
 }
        }
 }
    }
开始我们的实现过程:
1.生成密钥:DesUtil.java 这其中包括生成密钥,对文件进行加密、解密操作。
此解密方法返回输入流对象,以便在 spring的loadProperties方法中使用。
最终密钥文件生成为:mytest.key
 package com.sec;
package com.sec;

 import java.io.ByteArrayInputStream;
import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
import java.io.FileOutputStream;
 import java.io.InputStream;
import java.io.InputStream;
 import java.io.ObjectInputStream;
import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream;
 import java.io.OutputStream;
import java.io.OutputStream;
 import java.security.Key;
import java.security.Key;
 import java.security.SecureRandom;
import java.security.SecureRandom;

 import javax.crypto.Cipher;
import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
import javax.crypto.CipherInputStream;
 import javax.crypto.CipherOutputStream;
import javax.crypto.CipherOutputStream;
 import javax.crypto.KeyGenerator;
import javax.crypto.KeyGenerator;

 import org.apache.commons.io.IOUtils;
import org.apache.commons.io.IOUtils;


 public class DesUtil
public class DesUtil  {
{

 /** *//**
    /** *//**
 * 生成密钥
     * 生成密钥
 *
     * 
 * @param keyPath 密钥文件
     * @param keyPath 密钥文件
 */
     */

 public static void createDesKey(String keyPath)
    public static void createDesKey(String keyPath)  {
{
 FileOutputStream fos = null;
        FileOutputStream fos = null;
 ObjectOutputStream oos = null;
        ObjectOutputStream oos = null;

 try
        try  {
{
 SecureRandom sr = new SecureRandom();
            SecureRandom sr = new SecureRandom();
 KeyGenerator kg = KeyGenerator.getInstance("DES");
            KeyGenerator kg = KeyGenerator.getInstance("DES");
 kg.init(sr);
            kg.init(sr);
 fos = new FileOutputStream(keyPath);
            fos = new FileOutputStream(keyPath);
 oos = new ObjectOutputStream(fos);
            oos = new ObjectOutputStream(fos);
 // 生成密钥
            // 生成密钥
 Key key = kg.generateKey();
            Key key = kg.generateKey();
 oos.writeObject(key);
            oos.writeObject(key);

 } catch (Exception e)
        } catch (Exception e)  {
{
 e.printStackTrace();
            e.printStackTrace();

 } finally
        } finally  {
{
 IOUtils.closeQuietly(fos);
            IOUtils.closeQuietly(fos);
 IOUtils.closeQuietly(oos);
            IOUtils.closeQuietly(oos);
 }
        }
 }
    }


 /** *//**
    /** *//**
 * 获得密钥
     * 获得密钥
 *
     * 
 * @param keyPath
     * @param keyPath
 * @return
     * @return
 */
     */

 public static Key getKey(String keyPath)
    public static Key getKey(String keyPath)  {
{
 Key kp = null;
        Key kp = null;
 InputStream is = null;
        InputStream is = null;
 ObjectInputStream ois = null;
        ObjectInputStream ois = null;

 try
        try  {
{
 is = ClassLoader.getSystemClassLoader().getResourceAsStream(keyPath);
            is = ClassLoader.getSystemClassLoader().getResourceAsStream(keyPath);
 return getKey(is);
            return getKey(is);

 } catch (Exception e)
        } catch (Exception e)  {
{
 e.printStackTrace();
            e.printStackTrace();

 } finally
        } finally  {
{
 IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(is);
 IOUtils.closeQuietly(ois);
            IOUtils.closeQuietly(ois);
 }
        }
 return kp;
        return kp;
 }
    }
 
    

 /** *//**
    /** *//**
 * 获得密钥
     * 获得密钥
 * @param is
     * @param is
 * @return
     * @return
 */
     */

 public static Key getKey(InputStream is)
    public static Key getKey(InputStream is)  {
{
 Key key = null;
        Key key = null;
 ObjectInputStream ois = null;
        ObjectInputStream ois = null;

 try
        try  {
{
 ois = new ObjectInputStream(is);
            ois = new ObjectInputStream(is);
 key = (Key)ois.readObject();
            key = (Key)ois.readObject();

 } catch (Exception e)
        } catch (Exception e)  {
{
 e.printStackTrace();
            e.printStackTrace();

 } finally
        } finally  {
{
 IOUtils.closeQuietly(ois);
            IOUtils.closeQuietly(ois);
 }
        }
 return key;
        return key;
 }
    }


 /** *//**
    /** *//**
 * 加密源文件并保存到目标文件
     * 加密源文件并保存到目标文件
 *
     * 
 * @param srcFile
     * @param srcFile
 *            源文件
     *            源文件
 * @param destFile
     * @param destFile
 *            目标文件
     *            目标文件
 * @param key
     * @param key
 *            加密用的Key
     *            加密用的Key
 * @throws Exception
     * @throws Exception
 */
     */

 public static void encrypt(String srcFile, String destFile, Key key) throws Exception
    public static void encrypt(String srcFile, String destFile, Key key) throws Exception  {
{
 InputStream is = null;
        InputStream is = null;
 OutputStream out = null;
        OutputStream out = null;
 CipherInputStream cis = null;
        CipherInputStream cis = null;

 try
        try  {
{
 Cipher cipher = Cipher.getInstance("DES");
            Cipher cipher = Cipher.getInstance("DES");
 cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.init(Cipher.ENCRYPT_MODE, key);
 is = ClassLoader.getSystemClassLoader().getResourceAsStream(srcFile);
            is = ClassLoader.getSystemClassLoader().getResourceAsStream(srcFile);
 out = new FileOutputStream(destFile);
            out = new FileOutputStream(destFile);
 cis = new CipherInputStream(is, cipher);
            cis = new CipherInputStream(is, cipher);
 byte[] buffer = new byte[1024];
            byte[] buffer = new byte[1024];
 int r;
            int r;

 while ((r = cis.read(buffer)) > 0)
            while ((r = cis.read(buffer)) > 0)  {
{
 out.write(buffer, 0, r);
                out.write(buffer, 0, r);
 }
            }

 } finally
        } finally  {
{
 IOUtils.closeQuietly(cis);
            IOUtils.closeQuietly(cis);
 IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(is);
 IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(out);
 }
        }

 }
    }


 /** *//**
    /** *//**
 * 解密文件
     * 解密文件
 *
     * 
 * @param file
     * @param file
 * @param key
     * @param key
 * @return
     * @return
 * @throws Exception
     * @throws Exception
 */
     */

 public static InputStream decrypt(InputStream is, Key key) throws Exception
    public static InputStream decrypt(InputStream is, Key key) throws Exception  {
{
 OutputStream out = null;
        OutputStream out = null;
 CipherOutputStream cos = null;
        CipherOutputStream cos = null;
 ByteArrayOutputStream bout = null;
        ByteArrayOutputStream bout = null;

 try
        try  {
{
 Cipher cipher = Cipher.getInstance("DES");
            Cipher cipher = Cipher.getInstance("DES");
 cipher.init(Cipher.DECRYPT_MODE, key);
            cipher.init(Cipher.DECRYPT_MODE, key);

 bout = new ByteArrayOutputStream();
            bout = new ByteArrayOutputStream();
 byte[] buf = new byte[1024];
            byte[] buf = new byte[1024];
 int count = 0;
            int count = 0;

 while ((count = is.read(buf)) != -1)
            while ((count = is.read(buf)) != -1)  {
{
 bout.write(buf, 0, count);
                bout.write(buf, 0, count);
 buf = new byte[1024];
                buf = new byte[1024];
 }
            }
 byte[] orgData = bout.toByteArray();
            byte[] orgData = bout.toByteArray();
 byte[] raw = cipher.doFinal(orgData);
            byte[] raw = cipher.doFinal(orgData);
 return new ByteArrayInputStream(raw);
            return new ByteArrayInputStream(raw);

 } finally
        } finally  {
{
 IOUtils.closeQuietly(cos);
            IOUtils.closeQuietly(cos);
 IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(out);
 IOUtils.closeQuietly(bout);
            IOUtils.closeQuietly(bout);
 }
        }
 }
    }
 }
}

2.对jdbc.properties文件进行加密:加密的方法为DesUtil的encrypt方法。
jdbc.properties内容如下:
 driver=oracle.jdbc.OracleDriver
driver=oracle.jdbc.OracleDriver
 dburl=jdbc:oracle:thin:@127.0.0.1:1521:root
dburl=jdbc:oracle:thin:@127.0.0.1:1521:root
 username=blogjava
username=blogjava
 password=javadesps
password=javadesps
加密后得到文件desJdbc.properties,其内容如下:
 (ä8i6nOIÏ,d¢MÖÇäðëñÖn$BÞd|ê¾. ÓF—pêLylGýÓ$Iv'ÕJô
(ä8i6nOIÏ,d¢MÖÇäðëñÖn$BÞd|ê¾. ÓF—pêLylGýÓ$Iv'ÕJô
3.为了进行测试,新建SpringTestBean.java类,该类中只包含driver、url、username、password属性以及get、set方法。
 package com.spring;
package com.spring;

 import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.builder.ToStringStyle;


 public class SpringTestBean
public class SpringTestBean  {
{
 private String driver   = null;
    private String driver   = null;
 private String url      = null;
    private String url      = null;
 private String username = null;
    private String username = null;
 private String password = null;
    private String password = null;
 //
    //  get set 方法略
 get set 方法略


 /** *//**
    /** *//**
 * 重写toString方法 观察测试结果用
     * 重写toString方法 观察测试结果用
 */
     */
 @Override
    @Override

 public String toString()
    public String toString()  {
{
 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("driver", driver)
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("driver", driver)
 .append("url", url).append("username", username).append("password", password)
            .append("url", url).append("username", username).append("password", password)
 .toString();
            .toString();
 }
    }
 }
}

4.配置applicationContext.xml文件。内容如下:
 <?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
 <beans
<beans
 xmlns="http://www.springframework.org/schema/beans"
    xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:context="http://www.springframework.org/schema/context"
    xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 http://www.springframework.org/schema/aop
                        http://www.springframework.org/schema/aop 
 http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                        http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
 http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx 
 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
                        http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
 http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-2.5.xsd"
           http://www.springframework.org/schema/context/spring-context-2.5.xsd"
 default-lazy-init="true">
           default-lazy-init="true">
 
    
 <bean id="propertyConfigurer"
    <bean id="propertyConfigurer"
 class="com.spring.DecryptPropertyPlaceholderConfigurer">
        class="com.spring.DecryptPropertyPlaceholderConfigurer">
 <property name="locations">
        <property name="locations">
 <list>
            <list>      
 <value>classpath:com/spring/desJdbc.properties</value><!-- 加密后文件 -->
                <value>classpath:com/spring/desJdbc.properties</value><!-- 加密后文件 -->
 </list>
            </list>
 </property>
        </property>
 <property name="fileEncoding" value="utf-8"/>
        <property name="fileEncoding" value="utf-8"/> 
 <property name="keyLocation" value="classpath:com/spring/mytest.key" /><!-- 密钥文件位置 -->
        <property name="keyLocation" value="classpath:com/spring/mytest.key" /><!-- 密钥文件位置 -->
 </bean>
    </bean> 
 
   
 <!-- 测试用bean -->
   <!-- 测试用bean -->
 <bean id="testBean" class="com.spring.SpringTestBean" destroy-method="close">
    <bean id="testBean" class="com.spring.SpringTestBean" destroy-method="close">
 <property name="driver" value="${driver}" />
        <property name="driver" value="${driver}" />
 <property name="url" value="${dburl}" />
        <property name="url" value="${dburl}" />
 <property name="username" value="${username}" />
        <property name="username" value="${username}" /> 
 <property name="password" value="${password}" />
        <property name="password" value="${password}" />
 </bean>
    </bean>
 </beans>
</beans>
5.实现自己的PropertyPlaceholderConfigurer类----DecryptPropertyPlaceholderConfigurer.java,继承自Spring的PropertyPlaceholderConfigurer类:
 package com.spring;
package com.spring;

 import java.io.IOException;
import java.io.IOException;
 import java.io.InputStream;
import java.io.InputStream;
 import java.io.InputStreamReader;
import java.io.InputStreamReader;
 import java.util.Properties;
import java.util.Properties;

 import org.apache.commons.io.IOUtils;
import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
 import org.springframework.core.io.Resource;
import org.springframework.core.io.Resource;
 import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.DefaultPropertiesPersister;
 import org.springframework.util.PropertiesPersister;
import org.springframework.util.PropertiesPersister;

 import com.sec.DesUtil;
import com.sec.DesUtil;


 public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer
public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer  {
{
 private Resource[]          locations;
    private Resource[]          locations;
 private Resource            keyLocation;
    private Resource            keyLocation;
 private PropertiesPersister propertiesPersister    = new DefaultPropertiesPersister();
    private PropertiesPersister propertiesPersister    = new DefaultPropertiesPersister();
 private String              fileEncoding           = "utf-8";
    private String              fileEncoding           = "utf-8";
 private boolean             ignoreResourceNotFound = false;
    private boolean             ignoreResourceNotFound = false;

 @Override
    @Override

 public void setLocations(Resource[] locations)
    public void setLocations(Resource[] locations)  {
{
 this.locations = locations;
        this.locations = locations;
 }
    }

 @Override
    @Override

 public void setFileEncoding(String encoding)
    public void setFileEncoding(String encoding)  {
{
 this.fileEncoding = encoding;
        this.fileEncoding = encoding;
 }
    }

 @Override
    @Override

 public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound)
    public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound)  {
{
 this.ignoreResourceNotFound = ignoreResourceNotFound;
        this.ignoreResourceNotFound = ignoreResourceNotFound;
 }
    }


 public void setKeyLocation(Resource keyLocation)
    public void setKeyLocation(Resource keyLocation)  {
{
 this.keyLocation = keyLocation;
        this.keyLocation = keyLocation;
 }
    }

 @Override
    @Override

 /** *//**
    /** *//**
 * Load properties into the given instance.
     * Load properties into the given instance.
 * @param props the Properties instance to load into
     * @param props the Properties instance to load into
 * @throws java.io.IOException in case of I/O errors
     * @throws java.io.IOException in case of I/O errors
 * @see #setLocations
     * @see #setLocations
 */
     */

 protected void loadProperties(Properties props) throws IOException
    protected void loadProperties(Properties props) throws IOException  {
{

 if (this.locations != null)
        if (this.locations != null)  {
{

 for (int i = 0; i < this.locations.length; i++)
            for (int i = 0; i < this.locations.length; i++)  {
{
 Resource location = this.locations[i];
                Resource location = this.locations[i];
 InputStream is = null;        // 属性文件输入流
                InputStream is = null;        // 属性文件输入流
 InputStream keyStream = null; // 密钥输入流
                InputStream keyStream = null; // 密钥输入流
 InputStream readIs = null;    // 解密后属性文件输入流
                InputStream readIs = null;    // 解密后属性文件输入流

 try
                try  {
{
 // 属性文件输入流
                    // 属性文件输入流
 is = location.getInputStream();
                    is = location.getInputStream();
 // 密钥输入流
                    // 密钥输入流
 keyStream = keyLocation.getInputStream();
                    keyStream = keyLocation.getInputStream();
 // 得到解密后的输入流对象
                    // 得到解密后的输入流对象
 readIs = DesUtil.decrypt(is, DesUtil.getKey(keyStream));
                    readIs = DesUtil.decrypt(is, DesUtil.getKey(keyStream));
 // 以下操作按照Spring的流程做即可
                    // 以下操作按照Spring的流程做即可

 if (location.getFilename().endsWith(XML_FILE_EXTENSION))
                    if (location.getFilename().endsWith(XML_FILE_EXTENSION))  {
{
 this.propertiesPersister.loadFromXml(props, readIs);
                        this.propertiesPersister.loadFromXml(props, readIs);

 } else
                    } else  {
{

 if (this.fileEncoding != null)
                        if (this.fileEncoding != null)  {
{
 this.propertiesPersister.load(props, new InputStreamReader(readIs,
                            this.propertiesPersister.load(props, new InputStreamReader(readIs,
 this.fileEncoding));
                                this.fileEncoding));

 } else
                        } else  {
{
 this.propertiesPersister.load(props, readIs);
                            this.propertiesPersister.load(props, readIs);
 }
                        }
 }
                    }

 } catch (Exception ex)
                } catch (Exception ex)  {
{

 if (this.ignoreResourceNotFound)
                    if (this.ignoreResourceNotFound)  {
{

 if (logger.isWarnEnabled())
                        if (logger.isWarnEnabled())  {
{
 logger.warn("Could not load properties from " + location + ": "
                            logger.warn("Could not load properties from " + location + ": "
 + ex.getMessage());
                                + ex.getMessage());
 }
                        }
 }
                    }

 } finally
                } finally  {
{
 IOUtils.closeQuietly(is);
                    IOUtils.closeQuietly(is);
 IOUtils.closeQuietly(keyStream);
                    IOUtils.closeQuietly(keyStream);
 IOUtils.closeQuietly(readIs);
                    IOUtils.closeQuietly(readIs);
 }
                }
 }
            }
 }
        }
 }
    }
 }
}

6.测试代码:SpringCryptTest.java,这个代码很简单,就是用Spring去取得我们的测试bean----SpringTestBean,打印它的属性,测试已经加密过的desJdbc.properties文件,能否被Spring正确解密并加载。
 package com.spring;
package com.spring;

 import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


 public class SpringCryptTest
public class SpringCryptTest  {
{

 public static void main(String args[])
    public static void main(String args[])  {
{
 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
 "com/spring/applicationContext.xml");
            "com/spring/applicationContext.xml");
 SpringTestBean bean = (SpringTestBean)ctx.getBean("testBean");
        SpringTestBean bean = (SpringTestBean)ctx.getBean("testBean");
 System.out.println(bean.toString());
        System.out.println(bean.toString());
 }
    }
 }
}
执行结果:
 SpringTestBean[driver=oracle.jdbc.OracleDriver,url=jdbc:oracle:thin:@127.0.0.1:1521:root,username=blogjava,password=javadesps]
SpringTestBean[driver=oracle.jdbc.OracleDriver,url=jdbc:oracle:thin:@127.0.0.1:1521:root,username=blogjava,password=javadesps]
OK。到此,Spring已经正确解密并加载了此外部属性文件。 
本文为原创,欢迎转载,转载请注明出处
BlogJava。