下载地址:
https://npm.taobao.org/mirrors/git-for-windows/
往下一直滑,选择想要的版本下载即可
环境搭建:
1. 使用许可说明
– 点击“Next”即可

2. 选择安装路径
– 在输入框内输入想要安装到的本机路径,也就是实际文件夹位置,或点击“Browse…”选择已经存在的文件夹
– 然后点击“Next”按钮继续

3. 安装组件选择
– 红框内的选项是默认勾选的,建议不要动。
– 绿色框是决定是否在桌面创建快捷方式的。
– 蓝色框是决定在所有控制台窗口中使用TrueType字体和是否每天检查Git是否有Windows更新的。这些根据自己需要选择
– 之后点击“Next”继续

4. 选择开始菜单
– 这个界面是创建开始菜单中的名称,不需要修改,直接点“Next”按钮继续

5. 选择Git文件默认编辑器
– 这个页面是在选择Git文件默认的编辑器,很少用到,所以默认Vim即可,直接点“Next”按钮继续

6. 调整PATH环境
这个界面是调整我们的PATH环境。
– 第一种配置是“仅从Git Bash使用Git”。这是最安全的选择,因为PATH根本不会被修改。只能使用 Git Bash 的 Git 命令行工具。但是这将不能通过第三方软件使用。
– 第二种配置是“从命令行以及第三方软件进行Git”。该选项被认为是安全的,因为它仅向PATH添加了一些最小的Git包装器,以避免使用可选的Unix工具造成环境混乱。我们将能够从Git Bash,命令提示符和Windows PowerShell以及在PATH中寻找Git的任何第三方软件中使用Git。这也是推荐的选项。
– 第三种配置是“从命令提示符使用Git和可选的Unix工具”。警告:这将覆盖Windows工具,如 “ find 和 sort ”。只有在了解其含义后才使用此选项。
– 我们选择推荐的选项第二种配置,点击“Next”按钮继续

7. 选择HTTPS后端传输
– 第一个选项是“使用 OpenSSL 库”。服务器证书将使用ca-bundle.crt文件进行验证。这也是我们常用的选项。
– 第二个选项是“使用本地 Windows 安全通道库”。服务器证书将使用Windows证书存储验证。此选项还允许您使用公司的内部根CA证书,例如通过Active Directory Domain Services 。
– 我们使用默认选项第一项,点击“Next”按钮继

8. 配置行尾符号转换
– 第一个选项是“签出Windows风格,提交Unix风格的行尾”。签出文本文件时,Git会将LF转换为CRLF。提交文本文件时,CRLF将转换为LF。对于跨平台项目,这是Windows上的推荐设置(“ core.autocrlf”设置为“ true”)
– 第二个选项是“按原样签出,提交Unix样式的行尾”。签出文本文件时,Git不会执行任何转换。 提交文本文件时,CRLF将转换为LF。对于跨平台项目,这是Unix上的建议设置(“ core.autocrlf”设置为“ input”)
– 第三种选项是“按原样签出,按原样提交”。当签出或提交文本文件时,Git不会执行任何转换。不建议跨平台项目选择此选项(“ core.autocrlf”设置为“ false”)
– 我们选择第一种选项,点击“Next”按钮继续

9. 配置终端模拟器以与Git Bash一起使用
– 第一个选项是“使用MinTTY(MSYS2的默认终端)”。Git Bash将使用MinTTY作为终端模拟器,该模拟器具有可调整大小的窗口,非矩形选择和Unicode字体。Windows控制台程序(例如交互式Python)必须通过“ winpty”启动才能在MinTTY中运行。
– 第二个选项是“使用Windows的默认控制台窗口”。Git将使用Windows的默认控制台窗口(“cmd.exe”),该窗口可以与Win32控制台程序(如交互式Python或node.js)一起使用,但默认的回滚非常有限,需要配置为使用unicode 字体以正确显示非ASCII字符,并且在Windows 10之前,其窗口不能自由调整大小,并且只允许矩形文本选择。
– 我们选择默认的第一种选项,点击“Next”按钮继

10. 配置额外的选项
– 第一个选项是“启用文件系统缓存”。文件系统数据将被批量读取并缓存在内存中用于某些操作(“core.fscache”设置为“true”)。 这提供了显着的性能提升。
– 第二个选项是“启用Git凭证管理器”。Windows的Git凭证管理器为Windows提供安全的Git凭证存储,最显着的是对Visual Studio Team Services和GitHub的多因素身份验证支持。 (需要.NET Framework v4.5.1或更高版本)。
– 第三个选项是“启用符号链接”。启用符号链接(需要SeCreateSymbolicLink权限)。请注意,现有存储库不受此设置的影响。
– 我们勾选默认的第一、第二选项,点击“Next”按钮继续

11. 安装中

12. 安装完成
– 在这个界面,可以勾选是否启动启动Git Bash和是否查看发行说明,然后点“Next”按钮退出安装界面

1 下载centos最新安装包

打开阿里巴巴镜像网站 https://developer.aliyun.com/mirror/ ,下载最新的centos镜像
操作步骤如下图所示

2 安装流程

以管理员身份运行VMware虚拟机工作台,

在主页中选择【创建新的虚拟机】。

默认选择典型(选择自定义可以设置虚拟机硬件兼容性),点击【下一步】。

点击【浏览】选择刚才下载的系统镜像所在路径,点击【下一步】。

给虚拟机起一个名字,然后点击【浏览】修改虚拟系统安装位置,点击【下一步】。

重新设定磁盘大小,一般我们默认20GB够用了,点击【下一步】。

配置硬件界面,这里一般不需要做特殊设定,先将复选框的√去掉,点击【完成】。

检查VM是否启用了虚拟打印机,操作步骤如下。

选择【设备】->【更改设置】

然后启用虚拟打印机,点击【确定】

点击【开启虚此拟机】进入安装界面。

调整方向键确保选中,上图红框所示的位置,然后点击【回车键】,等待系统启动

选择语言,滚动菜单,选择“中文”→“简体中文(中国)”,点击“继续”。

点击【安装位置】

分区选中【自动配置分区】,然后点击【完成】按钮

点击【网络和主机名】

点击【关闭】切换按钮,等待系统自动获得IP地址,然后点击【完成】按钮

点击【开始安装】按钮

点击【ROOT密码】,给root用户设置密码

输入root的密码后,点击【完成】

然后静静的等待系统完成安装

点击【重启】按钮,等待系统完成重启,进入控制台,输入账号root和密码进行登录,登录成功可以看到如下图所示的结果

到此我们已经完成centos系统的安装

3 可能遇到的问题

弹出错误提示:

解决方案:

重新启动的电脑进入BIOS设置,将Intel VT-x设置为Enabled(启用状态) 。

不同的主板设置方式不一样,比如我的电脑的设置步骤如下

VMware Workstation Pro是用于在单个Linux或Windows PC上运行多个操作系统的行业标准。
Workstation 15.5 Pro在领先的台式机上进行了改进,提供了新的用户界面控件,Jumbo Frame支持,对最新的Windows和Linux操作系统的支持等等。
下面我们来演示一下他的安装过程

1 在官网下载最新的的VM安装包

输入在浏览器中输入https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 进入下载界面,下载windows版的安装包

2 安装过程

启动安装包,进入VMware安装界面

然后点击【下一步】

在许可协议中,勾选【我接受许可协议中的条款】,再点击【下一步】

自定义安装路径,点击右上角的【更改】选择软件的安装目录,再点击【下一步】

取消勾选上方的两个选项,再点击【下一步】。

快捷方式设置界面,根据你的情况选择,选择完成点击【下一步】

点击【安装】。等待 VMware Workstation 安装完成。

虚拟机安装完成后,点击【许可证】,就进行激活 VMware 虚拟机。

输入许可证,下面提供可用的许可证
UY758-0RXEQ-M81WP-8ZM7Z-Y3HDA
VF750-4MX5Q-488DQ-9WZE9-ZY2D6
UU54R-FVD91-488PP-7NNGC-ZFAX6
YC74H-FGF92-081VZ-R5QNG-P6RY4
YC34H-6WWDK-085MQ-JYPNX-NZRA2

请选择一项复制到VMware的许可证密钥中,再点击【输入】

点击【完成】,有的计算机可能需要重启,会有以下提示

点击【是】进项重启即可,到此VM安装完成

Cygwin可以在Windows环境下模拟linux编译环境,使用它我们可以模拟一些需要在linux环境下运行的服务器,比如我们可以模拟redis在window下运行。下面我们来介绍如何在windows 10下面安装Cygwin并搭建Redis5.0.8版本数据库服务器。

1 下载Cygwin安装包

进入官方下载最新版的Cygwin,点击链接地址进入Cygwin官网

下载完成后后运行安装包,下面是详细讲解安装步骤

2 安装Cygwin

安装向导启动成功后可以看到如下图所示的安装向导窗体

直接点击下一步按钮

直接点击下一步按钮

设置你要安装的路径,然后点击下一步

设置网络下载文件路径(一般在安装路径下面新建一个download目录来保存下载文件),然后点击下一步

此时会默认从官网下载,安装所需文件,如果没有下载成功会弹出如下所示提示框

直接点击确定按钮

在文本框输入:http://mirrors.aliyun.com/cygwin/, 在点击旁边的Add按钮,然后在点击下一步

此时就会通过阿里云进行下载了,等待下载进度完成,就打开一个选择安装包页面
因为我们需要编译Redis源码,所以需要安装gcc-core、gcc-g++以及make编译器,分别在搜索框中输入gcc和make能够快速的帮我们找到对应的包(这几个包都在Devel节点下),找到要安装的包所在行,在Skip英文字母的位置双击鼠标左键,直到Skip变成相应的版本号,表示安装包选择成功,具体效果如下面的图片所示

安装包选择完成后,点击下一步

点击下一步确认安装,进入安装进度页面

等待安装完成可以看到,安装完成页面

页面上的两个复选框,可以根据你的需要选择,然后点击完成按钮完成安装

3 安装Redis

3.1 下载Redis安装包

进入Redis官网( https://redis.io/ ),下载最新的稳定版Redis

3.2 下载Redis编译补丁包

在Github上下载最新的hiredis源码包,因为redis安装包里面的hiredis在cygwin中无法编译通过,所以需要使用github上最新的hiredis来替换redis里面的hiredis
在浏览器中输入 https://github.com/redis/hiredis.git ,将项目下载下来

3.3 解压安装包

将hiredis-master.zip与redis-5.0.8.tar.gz压缩包,复制到你常用软件的安装目录,比如我现在将它们复制到,如下图所示的安装目录

然后将它们使用压缩软件进行解压,然后将hiredis-master,移动到解压后的redis-5.0.8/deps目录下,然后删除deps目录下面的hiredis目录,最后将hiredis-master目录重命名为hiredis

3.4 编译安装

打开Cygwin,使用CD命令进入到到redis-5.0.8/deps 目录下面。
注意:因为cygwin是模拟Linux系统,所以我们要切换盘符的时候需要加上/cygdrive,比如我想进入D盘,那么需要执行下面命令行

cd /cygdrive/d

先进入deps目录
执行命令行

cd /cygdrive/e/SoftWare/Redis/Redis-5.0.8/redis-5.0.8/deps/

编译依赖性
执行下面命令行

make hiredis linenoise lua

下面是编译过程截图

下面是编译成功截图

返回redis-5.0.8目录,然后编译redis,依次执行下面命令

cd ../
make

下面是编译过程截图

下面是编译成功截图

启动服务器
执行命令行

./src/redis-server

下面是启动成功截图

在打开一个cygwin窗口
执行下面依次执行下面命名,连接redis服务器

#进入redis安装目录,根据你的安装路径调整目录
cd /cygdrive/e/SoftWare/Redis/Redis-5.0.8/redis-5.0.8/
#连接服务器
./src/redis-cli

执行命令后看到下面的结果,表示连接服务器成功

使用set和get命令测试一下

#新增一个string类型的键
set hellokey hellova
#获取string类型键对应的值
get hellokey

操作成功可以看到如下所示的截图

补充:
如果不想每次都需要切换到redis的安装目录,进行启动数据库和连接数据库操作,可以在编译redis完成后,进行一次安装,这样就能在任意位置执行redis相关命令

#安装到全局
make install
#然后在任一位置都可以执行
redis-server #启动数据库服务器
redis-cli #连接数据

到此我们已经完成Redis开发环境服务搭建了

什么是CGLIB

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib/releases/tag/RELEASE_3_2_10
小伙伴们可以直接进入,如下图所示页面,然后点击下载。

为什么使用CGLIB
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

CGLIB组成结构

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解。关于ASM的下载,我们这里也整理好了链接:https://asm.ow2.io/
访问此链接后,按照下图所示操作,点页面上的Maven Central,进入下载页面:

下载页面,点击下载按钮进行下载即可:

然后选择第一个,jar文件就好。

例子

说了这么多,可能大家还是不知道CGLIB是干什么用的。下面我们将使用一个简单的例子来演示如何使用CGLIB对一个方法进行拦截。
首先,我们需要在工程的POM文件中引入cglib的dependency,这里我们使用的是2.2.2版本

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

依赖包下载后,我们就可以干活了,按照国际惯例,写个hello world

public class SampleClass {
    public void test(){
        System.out.println("hello world");
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("after method run...");
                return result;
            }
        });
        SampleClass sample = (SampleClass) enhancer.create();
        sample.test();
    }
}

在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截,运行程序后输出为:

before method run...
hello world
after method run...

在上面的程序中,我们引入了Enhancer和MethodInterceptor,可能有些读者还不太了解。别急,我们后面将会一一进行介绍。就目前而言,一个使用CGLIB的小demo就完成了

常用的API

目前网络上对CGLIB的介绍资料比较少,造成对cglib的学习困难。这里我将对cglib中的常用类进行一个介绍。为了避免解释的不清楚,我将为每个类都配有一个demo,用来做进一步的说明。首先就从Enhancer开始吧。

Enhancer

Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

public class SampleClass {
    public String test(String input){
        return "hello world";
    }
}

下面我们将以这个类作为主要的测试类,来测试调用各种方法

@Test
public void testFixedValue(){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new FixedValue() {
        @Override
        public Object loadObject() throws Exception {
            return "Hello cglib";
        }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    System.out.println(proxy.test(null)); //拦截test,输出Hello cglib
    System.out.println(proxy.toString()); 
    System.out.println(proxy.getClass());
    System.out.println(proxy.hashCode());
}

程序的输出为:

Hello cglib
Hello cglib
class com.zeus.cglib.SampleClassEnhancerByCGLIBe3ea9b7

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

    at com.zeus.cglib.SampleClassEnhancerByCGLIBe3ea9b7.hashCode(<generated>)
    ...

上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。

Enhancer.setSuperclass用来设置父类型,从toString方法可以看出,使用CGLIB生成的类为被代理类的一个子类,形如:SampleClassEnhancerByCGLIBe3ea9b7

Enhancer.create(Object…)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造放法只是Java字节码层面的函数,但是Enhancer却不能对其进行操作。Enhancer同样不能操作static或者final类)。我们也可以先使用Enhancer.createClass()来创建字节码(.class),然后用字节码动态的生成增强后的对象。

可以使用一个InvocationHandler(如果对InvocationHandler不懂,可以参考这里)作为回调,使用invoke方法来替换直接访问类的方法,但是你必须注意死循环。因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中。

public void testInvocationHandler() throws Exception{
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
                return "hello cglib";
            }else{
                throw new RuntimeException("Do not know what to do");
            }
        }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    Assert.assertEquals("hello cglib", proxy.test(null));
    Assert.assertNotEquals("Hello cglib", proxy.toString());
}

为了避免这种死循环,我们可以使用MethodInterceptor,MethodInterceptor的例子在前面的hello world中已经介绍过了,这里就不浪费时间了。

有些时候我们可能只想对特定的方法进行拦截,对其他的方法直接放行,不做任何操作,使用Enhancer处理这种需求同样很简单,只需要一个CallbackFilter即可:

@Test
public void testCallbackFilter() throws Exception{
    Enhancer enhancer = new Enhancer();
    CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {
        @Override
        protected Object getCallback(Method method) {
            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
                return new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "Hello cglib";
                    }
                };
            }else{
                return NoOp.INSTANCE;
            }
        }
    };
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallbackFilter(callbackHelper);
    enhancer.setCallbacks(callbackHelper.getCallbacks());
    SampleClass proxy = (SampleClass) enhancer.create();
    Assert.assertEquals("Hello cglib", proxy.test(null));
    Assert.assertNotEquals("Hello cglib",proxy.toString());
    System.out.println(proxy.hashCode());
}

ImmutableBean

通过名字就可以知道,不可变的Bean。ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。这有点类似于Guava中的不可变视图

为了对ImmutableBean进行测试,这里需要再引入一个bean

public class SampleBean {
    private String value;

    public SampleBean() {
    }

    public SampleBean(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

然后编写测试类如下:

@Test(expected = IllegalStateException.class)
public void testImmutableBean() throws Exception{
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类
    Assert.assertEquals("Hello world",immutableBean.getValue()); 
    bean.setValue("Hello world, again"); //可以通过底层对象来进行修改
    Assert.assertEquals("Hello world, again", immutableBean.getValue());
    immutableBean.setValue("Hello cglib"); //直接修改将throw exception
}

Bean generator

cglib提供的一个操作bean的工具,使用它能够在运行时动态的创建一个bean。

@Test
public void testBeanGenerator() throws Exception{
    BeanGenerator beanGenerator = new BeanGenerator();
    beanGenerator.addProperty("value",String.class);
    Object myBean = beanGenerator.create();
    Method setter = myBean.getClass().getMethod("setValue",String.class);
    setter.invoke(myBean,"Hello cglib");

    Method getter = myBean.getClass().getMethod("getValue");
    Assert.assertEquals("Hello cglib",getter.invoke(myBean));
}

在上面的代码中,我们使用cglib动态的创建了一个和SampleBean相同的Bean对象,包含一个属性value以及getter、setter方法

Bean Copier
cglib提供的能够从一个bean复制到另一个bean中,而且其还提供了一个转换器,用来在转换的时候对bean的属性进行操作。

public class OtherSampleBean {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
@Test
public void testBeanCopier() throws Exception{
    BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false);//设置为true,则使用converter
    SampleBean myBean = new SampleBean();
    myBean.setValue("Hello cglib");
    OtherSampleBean otherBean = new OtherSampleBean();
    copier.copy(myBean, otherBean, null); //设置为true,则传入converter指明怎么进行转换
   assertEquals("Hello cglib", otherBean.getValue());
}

BulkBean

相比于BeanCopier,BulkBean将copy的动作拆分为getPropertyValues和setPropertyValues两个方法,允许自定义处理属性

@Test
public void testBulkBean() throws Exception{
    BulkBean bulkBean = BulkBean.create(SampleBean.class,
            new String[]{"getValue"},
            new String[]{"setValue"},
            new Class[]{String.class});
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    Object[] propertyValues = bulkBean.getPropertyValues(bean);
    assertEquals(1, bulkBean.getPropertyValues(bean).length);
    assertEquals("Hello world", bulkBean.getPropertyValues(bean)[0]);
    bulkBean.setPropertyValues(bean,new Object[]{"Hello cglib"});
    assertEquals("Hello cglib", bean.getValue());
}

使用注意:
1. 避免每次进行BulkBean.create创建对象,一般将其声明为static的
2. 应用场景:针对特定属性的get,set操作,一般适用通过xml配置注入和注出的属性,运行时才确定处理的Source,Target类,只需要关注属性名即可。

BeanMap

BeanMap类实现了Java Map,将一个bean对象中的所有属性转换为一个String-to-Obejct的Java Map

@Test
public void testBeanMap() throws Exception{
    BeanGenerator generator = new BeanGenerator();
    generator.addProperty("username",String.class);
    generator.addProperty("password",String.class);
    Object bean = generator.create();
    Method setUserName = bean.getClass().getMethod("setUsername", String.class);
    Method setPassword = bean.getClass().getMethod("setPassword", String.class);
    setUserName.invoke(bean, "admin");
    setPassword.invoke(bean,"password");
    BeanMap map = BeanMap.create(bean);
    Assert.assertEquals("admin", map.get("username"));
    Assert.assertEquals("password", map.get("password"));
}

我们使用BeanGenerator生成了一个含有两个属性的Java Bean,对其进行赋值操作后,生成了一个BeanMap对象,通过获取值来进行验证

keyFactory

keyFactory类用来动态生成接口的实例,接口需要只包含一个newInstance方法,返回一个Object。keyFactory为构造出来的实例动态生成了Object.equals和Object.hashCode方法,能够确保相同的参数构造出的实例为单例的。

public interface SampleKeyFactory {
    Object newInstance(String first, int second);
}

我们首先构造一个满足条件的接口,然后进行测试

@Test
public void testKeyFactory() throws Exception{
    SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);
    Object key = keyFactory.newInstance("foo", 42);
    Object key1 = keyFactory.newInstance("foo", 42);
    Assert.assertEquals(key,key1);//测试参数相同,结果是否相等
}

Mixin(混合)

Mixin能够让我们将多个对象整合到一个对象中去,前提是这些对象必须是接口的实现。可能这样说比较晦涩,以代码为例:

public class MixinInterfaceTest {
    interface Interface1{
        String first();
    }
    interface Interface2{
        String second();
    }

    class Class1 implements Interface1{
        @Override
        public String first() {
            return "first";
        }
    }

    class Class2 implements Interface2{
        @Override
        public String second() {
            return "second";
        }
    }

    interface MixinInterface extends Interface1, Interface2{

    }
    @Test
    public void testMixin() throws Exception{
        Mixin mixin = Mixin.create(new Class[]{Interface1.class, Interface2.class,
                        MixinInterface.class}, new Object[]{new Class1(),new Class2()});
        MixinInterface mixinDelegate = (MixinInterface) mixin;
        assertEquals("first", mixinDelegate.first());
        assertEquals("second", mixinDelegate.second());
    }
}

Mixin类比较尴尬,因为他要求Minix的类(例如MixinInterface)实现一些接口。既然被Minix的类已经实现了相应的接口,那么我就直接可以通过纯Java的方式实现,没有必要使用Minix类。

String switcher

用来模拟一个String到int类型的Map类型。如果在Java7以后的版本中,类似一个switch语句。

@Test
public void testStringSwitcher() throws Exception{
    String[] strings = new String[]{"one", "two"};
    int[] values = new int[]{10,20};
    StringSwitcher stringSwitcher = StringSwitcher.create(strings,values,true);
    assertEquals(10, stringSwitcher.intValue("one"));
    assertEquals(20, stringSwitcher.intValue("two"));
    assertEquals(-1, stringSwitcher.intValue("three"));
}

Interface Maker

正如名字所言,Interface Maker用来创建一个新的Interface

@Test
public void testInterfaceMarker() throws Exception{
    Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});
    InterfaceMaker interfaceMaker = new InterfaceMaker();
    interfaceMaker.add(signature, new Type[0]);
    Class iface = interfaceMaker.create();
    assertEquals(1, iface.getMethods().length);
    assertEquals("foo", iface.getMethods()[0].getName());
    assertEquals(double.class, iface.getMethods()[0].getReturnType());
}

上述的Interface Maker创建的接口中只含有一个方法,签名为double foo(int)。Interface Maker与上面介绍的其他类不同,它依赖ASM中的Type类型。由于接口仅仅只用做在编译时期进行类型检查,因此在一个运行的应用中动态的创建接口没有什么作用。但是InterfaceMaker可以用来自动生成代码,为以后的开发做准备。

Method delegate
MethodDelegate主要用来对方法进行代理

interface BeanDelegate{
    String getValueFromDelegate();
}
@Test
public void testMethodDelegate()  throws Exception{
    SampleBean bean = new SampleBean();
    bean.setValue("Hello cglib");
    BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(bean,"getValue", BeanDelegate.class);
    assertEquals("Hello cglib", delegate.getValueFromDelegate());
}

**关于Method.create的参数说明: **

 第二个参数为即将被代理的方法 
 第一个参数必须是一个无参数构造的bean。因此MethodDelegate.create并不是你想象的那么有用 
 第三个参数为只含有一个方法的接口。当这个接口中的方法被调用的时候,将会调用第一个参数所指向bean的第二个参数方法

**缺点: **
为每一个代理类创建了一个新的类,这样可能会占用大量的永久代堆内存
你不能代理需要参数的方法
如果你定义的接口中的方法需要参数,那么代理将不会工作,并且也不会抛出异常;如果你的接口中方法需要其他的返回类型,那么将抛出IllegalArgumentException

MulticastDelegate

多重代理和方法代理差不多,都是将代理类方法的调用委托给被代理类。使用前提是需要一个接口,以及一个类实现了该接口,通过这种interface的继承关系,我们能够将接口上方法的调用分散给各个实现类上面去。
多重代理的缺点是接口只能含有一个方法,如果被代理的方法拥有返回值,那么调用代理类的返回值为最后一个添加的被代理类的方法返回值。

public interface DelegatationProvider {
    void setValue(String value);
}

public class SimpleMulticastBean implements DelegatationProvider {
    private String value;
    @Override
    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
@Test
public void testMulticastDelegate() throws Exception{
    MulticastDelegate multicastDelegate = MulticastDelegate.create(DelegatationProvider.class);
    SimpleMulticastBean first = new SimpleMulticastBean();
    SimpleMulticastBean second = new SimpleMulticastBean();
    multicastDelegate = multicastDelegate.add(first);
    multicastDelegate  = multicastDelegate.add(second);

    DelegatationProvider provider = (DelegatationProvider) multicastDelegate;
    provider.setValue("Hello world");

    assertEquals("Hello world", first.getValue());
    assertEquals("Hello world", second.getValue());
}

Constructor delegate

为了对构造函数进行代理,我们需要一个接口,这个接口只含有一个Object newInstance(…)方法,用来调用相应的构造函数

interface SampleBeanConstructorDelegate{
    Object newInstance(String value);
}

/**
 * 对构造函数进行代理
 * @throws Exception
 */
@Test
public void testConstructorDelegate() throws Exception{
    SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
            SampleBean.class, SampleBeanConstructorDelegate.class);
    SampleBean bean = (SampleBean) constructorDelegate.newInstance("Hello world");
    assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
    System.out.println(bean.getValue());
}

Parallel Sorter(并行排序器)
能够对多个数组同时进行排序,目前实现的算法有归并排序和快速排序

@Test
public void testParallelSorter() throws Exception{
    Integer[][] value = {
            {4, 3, 9, 0},
            {2, 1, 6, 0}
    };
    ParallelSorter.create(value).mergeSort(0);
    for(Integer[] row : value){
        int former = -1;
        for(int val : row){
            assertTrue(former < val);
            former = val;
        }
    }
}

FastClass

顾明思义,FastClass就是对Class对象进行特定的处理,比如通过数组保存method引用,因此FastClass引出了一个index下标的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的获取method的方法。通过数组存储method,constructor等class信息,从而将原先的反射调用,转化为class.index的直接调用,从而体现所谓的FastClass。

@Test
public void testFastClass() throws Exception{
    FastClass fastClass = FastClass.create(SampleBean.class);
    FastMethod fastMethod = fastClass.getMethod("getValue",new Class[0]);
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    assertEquals("Hello world",fastMethod.invoke(bean, new Object[0]));
}

注意

由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。

CGLIB和Java动态代理的区别

Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

类图

1 什么是类图

类图是描述系统中类以及类之间相互关系的图表

2 为什么使用类图

1. 可以直观的显示类型的结构
2. 可以直观的显示类型之间的关系比如:继承、包含等。可以显示出是一个类型还是一个接口 (相关的知识会在后续进行讲解)
3. 可以直接生成java代码

3 使用类图

工具

astch mommunity(社区版), 对于专门的语言没有设计类型比如String。

astch performance(专业版),有相关的语言支持,比如Java、C++等

下载

社区版,直接官网下载

专业版,在从社群中获取,不建议直接从官网下载,可能会出现无法破解的情况. 路径下获取

破解

  1. 获取 astah-professional-7_2_0-1ff236-jre-64bit-setup.exe 安装
  2. 将压缩包 astah-pro.v7.2.0-1ff236.zip 中的 astach-pro.jar 文件覆盖到软件的安装路径即可

使用

  1. 创建文件: File – New By Template – Java8.asta

    image

    1. 新建类型对象:

    a.选中项目 – 右键 – Auto Create Class Diagram

image

  1. 创建类图

    a. 在Class Diagram 界面中,点击菜单中的类型按钮,再到接卖弄中点击,创建类图

    b. 填写类型名称

    c. 点击类图中类型下第一根横线上的菱形图标定义字段

    image

    d. 填写修饰符、字段名、返回类型

    image

    e. 点击类图中类型下的第二根横线上的矩形定义方法

    f. 填写访问修饰符、方法名、参数列表、返回类型

深复制和浅复制也称为深拷贝和浅拷贝。简单的说就是创建一个和当前对象一模一样的对象。在日常编码的过程中使用的几率并不多,但在面试中却会被经常问到。

了解深复制和浅复制的原理后,可以帮助我们对Java中的值传递和引用传递有更深刻的理解。

浅复制

下面的实例中,我们创建一个原始类Monster,调用对象的clone方法复制一个新的对象。
注意:要调用对象的clone方法,需要让类实现Cloneable接口,并重写clone方法

public class Monster implements Cloneable{
    private String name;        //怪物名称
    private int level;          //怪物级别
    private Point position;     //怪物坐标

    public Monster(String name, int level){
        this.name = name;
        this.level = level;
        position = new Point(100, 100);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //Cloneable接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中 clone()方法的,
        //如果 clone类没有实现 Cloneable接口,并调用了 Object的 clone()方法
        //(即调用super.clone()方法),那么Object的 clone()方法就会抛出 CloneNotSupportedException异常。
        return super.clone();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public Point getPosition() {
        return position;
    }

    public void setPosition(int x, int y) {
        position.setX(x);
        position.setY(y);
    }
}
class Point{//坐标类
    private int x;
    private int y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }

}
public class MonsterTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Monster monster1 = new Monster("史莱克", 99);
        //通过clone方法,将对象monster1中的值复制到对象monster2
        Monster monster2 = (Monster)monster1.clone();
        //1.monster1和monster2是两个对象,下面的结果是false
        System.out.println(monster1.equals(monster2));
        //2.对比对象中的值(浅复制中,只复制了对象的引用)
        System.out.println(monster1.getName() == monster2.getName());
        System.out.println(monster1.getName().hashCode());
        System.out.println(monster2.getName().hashCode());
        //巩固第二点,修改monster2的坐标值
        monster2.setPosition(111, 111);
        //monster2的坐标值修改后,对象monster1的坐标值也被修改了
        System.out.println(monster1.getPosition().getX() + "\t" + monster1.getPosition().getY());
    }
}

我们可以通过这个例子体会浅复制的特点:
被复制对象的所有成员属性都有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
即浅复制后,新对象的地址变了,但新对象中字段(属性)所指向的内存地址(引用)却没变,新旧对象的字段指向了同一块内存空间。

深复制

深复制则是指在clone对象本身,也clone对象中中的属性(字段)
我们可以通过下面的代码体会:

public class Monster implements Cloneable{
    private String name;
    private int level;
    private Point position;

    public Monster(String name, int level){
        this.name = name;
        this.level = level;
        position = new Point(100, 100);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Monster monster = (Monster)super.clone();
        //对象属性也通过clone方法复制-深复制
        monster.position = (Point)position.clone();
        return monster;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public Point getPosition() {
        return position;
    }

    public void setPosition(int x, int y) {
        position.setX(x);
        position.setY(y);
    }
}
class Point implements Cloneable{
    private int x;
    private int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //作为属性类同样实现clone方法,就实现了深复制
        return super.clone();
    }
}
public class MonsterTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Monster monster1 = new Monster("史莱克", 99);
        //通过clone方法,将对象monster1中的值复制到对象monster2
        Monster monster2 = (Monster)monster1.clone();
        //1.monster1和monster2是两个对象,下面的结果是false - 同浅复制
        System.out.println(monster1.equals(monster2));
        //2.对比对象中的值(浅复制中,只复制了对象的引用) - 浅复制
        System.out.println(monster1.getName() == monster2.getName());
        System.out.println(monster1.getName().hashCode());
        System.out.println(monster2.getName().hashCode());
        //修改monster2的坐标值
        monster2.setPosition(111, 111);
        //深复制后,monster2的坐标值修改,对象monster1的坐标值并没有被修改
        System.out.println(monster1.getPosition().getX() + "\t" + monster1.getPosition().getY());
    }
}

上面的代码虽然实现了深复制,但是有个瑕疵,就是如果Monster类中出现自定义的引用类型属性(字段),这个引用类型就需要实现Cloneable接口并重写clone方法。引用类型多的情况下,会增加代码量。

实现深复制的另一种方法就是使用序列化技术:
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

  注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Monster implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private int level;
    private Point position;

    public Monster(String name, int level){
        this.name = name;
        this.level = level;
        position = new Point(100, 100);
    }

    //深复制
    public Object deepClone() throws Exception{
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public Point getPosition() {
        return position;
    }

    public void setPosition(int x, int y) {
        position.setX(x);
        position.setY(y);
    }
}
class Point implements Serializable{
    private static final long serialVersionUID = 1L;
    private int x;
    private int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }
}
public class MonsterTest {
    public static void main(String[] args) throws Exception {
        Monster monster1 = new Monster("史莱克", 99);
        //调用自定义的deepClone方法(深复制),将对象monster1中的值复制到对象monster2
        Monster monster2 = (Monster)monster1.deepClone();
        //1.monster1和monster2是两个对象,下面的结果是false 
        System.out.println(monster1.equals(monster2));
        //2.对比对象中的值
        System.out.println(monster1.getName() == monster2.getName());
        System.out.println(monster1.getName().hashCode());
        System.out.println(monster2.getName().hashCode());
        //修改monster2的坐标值
        monster2.setPosition(111, 111);
        //深复制后,monster2的坐标值修改,对象monster1的坐标值并没有被修改
        System.out.println(monster1.getPosition().getX() + "\t" + monster1.getPosition().getY());
    }
}

Java平台最初的目标是为嵌入式设备提供一个软件环境。然而,历史的怪圈却让Java成为了企业软件开发的首选语言。过去,Java的客户端应用所受到的关注比利润丰厚的服务器端市场要少得多。不过,现在Java平台已经拥有了强大的客户端组件——JavaFX,可用于开发桌面、平板电脑、移动和嵌入式系统上的应用程序。本文将为读者展示如何在Android设备上部署JavaFX应用程序。

任何致力于客户端开发的软件平台都需要有一套创建用户界面的方法。AWT(抽象窗口工具包)曾经被看作是Java平台用户界面的根基。一些更高级的工具包(例如Swing)在一定程度上都是以AWT为基础的。自从1995年Java首次发布,AWT就是Java平台的一部分,现在看来,其设计原则已经相当陈旧,无法与当今的硬件和软件能力相匹配。

新的Java客户端组件,JavaFX,是在充分汲取了Java领域以及其他UI框架的经验后,重新设计而成。JavaFX的关键原则之一就是要尽可能地充分利用硬件(如GPU)资源。实际上,如今的用户界面所需的工具包必须是高响应并且高性能的。

能够让JavaFX应用运行在iOS和Android平台上是至关重要的。如今,越来越多的应用程序不仅需要能够在桌面电脑上运行,也需要能够在移动设备和平板电脑上运行。用三种语言编写同一应用的三种版本的代价相当昂贵:一个桌面版本,一个iOS版本和一个Android版本。而使用JavaFX,则可以将同一个应用直接部署到全部三个平台上。当然,各个平台都有其各自的UI特性需要遵循,不过JavaFX平台提供了许多方法以达成这一目标,例如使用CSS和自定义皮肤。JavaFX作为官方的Java客户端组件,是Java SE环境重要的组成部分。在所支持的系统上,它是与JDK和JRE绑定在一起的。因此,在Windows,MacOS X,Linux和嵌入式ARM系统上,Oracle将JavaFX作为Java SE版本的一部分统一发布。对于iOS和Android平台来说,目前尚无Oracle官方发布的JavaFX。不过,开发者社区已经弥补了这一缺口。RoboVM团队正在添加RoboVM对JavaFX的支持,这样就可以使用RoboVM编译器编译JavaFX应用程序并在iOS设备上运行它们。

稍后在本文中我们将着重阐述要能够在iOS和Android平台运行JavaFX应用的另一个原因。在此之前,我们首先为大家介绍如何在Android平台上运行已有的JavaFX应用。

Android上的JavaFX

本文的剩余部分将介绍如何在Android上部署JavaFX应用程序。关于如何在Android平台上编译、打包和部署JavaFX应用程序的详细说明可以访问JavaFX移植团队的网站。

通常来说,部署JavaFX应用程序的步骤如下:
1、下载Android SDK和JavaFX-Android SDK
2、创建一个JavaFX应用
3、使用JavaFX-Android SDK创建基于上述JavaFX应用的Android项目
4、使用Ant构建系统创建Android程序包
5、将程序包上传至应用商店

第一步:下载Android SDK和JavaFX-Android SDK

在编译和构建应用程序之前,首先需要安装Android SDK和JavaFX-Android SDK。

Android SDK是由Google提供的软件开发工具包,可以从Android开发者支持网站上下载。其中包含了android.jar API和将Java类文件转换成Dalvik字节码的工具。Android SDK还提供了可与Android设备通信的工具,用于日志检查和将应用程序传输到设备上。下载完成后,可以很方便地将ANDROID_SDK环境变量指向所下载的adt-bundle-xxx/sdk文件夹(xxx与所下载的版本和对应的操作系统相关。)

Dalvik JavaFX-Android SDK(由JavaFX的Android移植团队提供)可以从BitBucket网站的JavaFX Ports项目中下载。下载并解压最新的dalvik-sdk-version.zip文件。(我们可以将环境变量DALVIK_SDK设置指向刚刚解压缩的dalvik-sdk文件夹)。JavaFX-Android SDK中包含一个运行在Android平台上的JavaFX实现,一些用于构建Android程序包的工具以及一个“Hello,Android” JavaFX示例程序。可以在刚刚下载的DALVIK_SDK/samples目录下找到这个示例程序。除此之外,我们还需要用于构建apk文件的Ant程序。如果电脑上还没有Ant程序,可以从Apache Ant网站上下载。

第二步:创建JavaFX应用

在Android平台创建JavaFX应用与在桌面电脑系统上创建JavaFX应用的步骤完全一致。我们可以选择惯用的IDE和构建工具来创建JavaFX应用。这样,创建Android程序包的旅途就顺利开始了。在这一步,我们无须创建特定的JavaFX应用程序启动器或增加任何配置。JavaFX-Android SDK已经为我们提供了相应的工具,我们将在接下来的步骤里讨论这些工具。

虽然通常来说,最好能够保持代码的平台独立性,不过在某些情况下,例如在没有相应的JavaFX或Java API可用时,如果能够利用Android平台特有的实现会更加方便一些。Android平台提供了许多为应用提供各种功能的服务(如获取位置信息)。要使用这些服务,只需添加对android.jar(位于Android SDK中)的依赖并引用其所包含的类即可。不过,需要注意的是,这些Android平台特有的功能无法在其他系统上(如桌面系统)使用。另外,考虑到桌面应用的UI面积比手持设备的更大,在创建Android应用的布局时,需要能够适用于较小的UI面积。可以研究一下samples文件夹下的HelloAndroid类,以了解如何获取屏幕的边界并使用屏幕边界设置Stage和Scene的边界。

如果想要用示例程序执行上述步骤,使用cd命令切换到之前下载的DALVIK_SDK目录下的samples/HelloWorld文件夹下。

将应用打包成jar文件。打包示例程序,首先需要cd切换到DALVIK_SDK/samples/HelloWorld/javafx目录下,然后构建应用程序——在Linux和MacOS上使用./gradlew,在Windows上使用gradlew.bat。(我们不需要下载Gradle,gradlew会为我们完成这项工作。)执行这个命令会在javafx/build/libs文件夹下创建一个jar文件(HelloWorld.jar)。除了Gradle之外,我们也可以使用Maven,Ant或者其他任何工具来构建应用,只要能够生成jar文件即可。

第三步:使用JavaFX-Android SDK生成基于JavaFX应用的Android项目

DALVIK_SDK目录下的“tools”文件夹中包含一个名为“convertJavaFXToAndroid.sh”的构建脚本,可用于生成Android项目。简单来说,Android项目就是一个文件夹,其中包含了一些文件和构建脚本,用于生成Android程序包(apk文件)。

构建项目之前,我们需要进行一系列的参数配置,包括Android SDK和JavaFX-Android SDK的位置,JavaFX应用的位置以及包含main类的文件名称等。

我们只需对DALVIK_SDK/samples/HelloWorld/convertJavaFXToAndroid.sh文件稍作修改,就能够让它正常运行。在文件顶部,将变量ANDROID_SDK指向之前下载的ANDROID_SDK文件夹。(目前还没有Windows版本的构建脚本convertJavaFXToAndroid.bat,不过我们可以拷贝.sh文件,然后稍作修改,就可以自制一个Windows版本的构建脚本。)关于生成Android项目的更多信息,请参见DALVIK_SDK/samples/HelloWorld/README文件。

在调用convertJavaFXToAndroid.sh脚本之前,在这个脚本的同一文件夹下必须要包含build.gradle及其他gradle相关的文件。将samples目录下的所有gradle相关的文件和整个gradle目录拷贝到我们自己创建的JavaFX项目的根目录下,并对convertJavaFXToAndroid.sh脚本做出相应的修改。然后调用convertJavaFXToAndroid.sh脚本。我们就可以在samples/HelloWorld/javafx/build目录下找到新生成的Android项目。

在构建项目时,我们可以使用与示例程序的构建脚本相同的模式。相关参数的定义如下:

-PDIR:生成Android项目的文件夹路径。
-PPACKAGE:Android包名称。
-PNAME:生成的Android apk文件名。
-PANDROID_SDK:Android SDK。
-PJFX_SDK:Dalvik SDK。
-PJFX_APP:JavaFX应用jar包所在目录。
-PJFX_MAIN:JavaFX主启动器的类名。

第四步:使用Ant构建系统创建Android程序包

在上一步骤中生成的Android项目文件夹“build”中包含一个build.xml文件。切换到build文件夹下并运行“ant debug”。这个命令会生成一个可以安装到Android设备上的Android“调试程序包”。

使用Android SDK中的Android工具可以将上述.apk文件发送到设备上。如,调用命令

$ANDROID_SDK/platform-tools/adb -r install /path/to/the.apk

如果需要获取日志信息,可以执行如下命令:

$ANDROID_SDK/platform-tools/adb logcat

需要让JavaFX应用能够运行在iOS和Android平台的另一个重要的原因是通过应用商店(Apple应用商店和Google Play商店)发布的这种模式。过去,如何将Java应用发布到各种各样的移动手机上,是令许多Java客户端开发人员感到相当有挫败感的工作之一。尽管有过许多标准化的尝试(例如,MIDP),不过如果没有设备制造商、网络运营商或二者共同的帮助,想要将J2ME应用部署到大量的设备上仍是一件相当困难的事情。现在,在移动设备上创建和安装应用已经变得十分简单。iOS和Android平台都有自己的应用商店,可供开发者上传应用,终端用户下载和运行应用。虽然iOS应用商店和Android Play商店都有一系列应用程序需要满足的要求和设计准则,不过在iOS应用商店和Google Play商店都已经存在JavaFX的应用,也就是说基于JavaFX的应用可以被应用商店所接受。这为Java客户端应用开发者创造了巨大的市场。

JavaFX的Android应用与任何其他Android应用没有什么两样。上传到Google Play商店的方式是相同的。而且与其他Android应用类似,在上传应用之前,需要将许多指导方针考虑在内。这些指导方针不应被视为令人厌烦的教条,而应被看作是提供给JavaFX开发者的帮助,以保持他们的应用与其他Android应用的一致性,这会让终端用户更容易接受JavaFX应用。

第五步:部署到应用商店(正在进行的工作)

将JavaFX应用上传到Google Play商店并非十分困难,不过目前为止仍有许多步骤需要遵循。从使用惯用的IDE编写JavaFX应用到将应用提交到Play商店的道路仍然很漫长。而且需要熟悉各种各样的系统。如果用Maven开发JavaFX应用,就需要用到3种不同的构建系统:用于应用开发的Maven,用于创建Android项目的Gradle和用于构建Android程序包的Ant。

意图改善这个问题的一些工作正在进行中。有许多方法能够改变这种状况,其中一些方法已在研究阶段。例如, Android最近将Gradle转为其首选的构建环境。Android的Gradle插件可以让编译JavaFX应用变得更加容易,不过目前仍然还有许多问题有待解决。

另外,与JDK一同发布的JavaFXPackager主要用于为不同的目标环境提供相应的程序包。如果这一工具能够集成在JavaFX-Android SDK中将会更好。

此外,一些IDE(NetBeans、Eclipse和IntelliJ IDEA)已经包含了一些用于Android开发的插件。如果能够在这些IDE中集成对JavaFX的支持,熟悉这些IDE的开发人员的工作将会变得更加轻松高效。

JavaFX的Android移植团队选择先为开发者提供一个端到端工具集,以帮助他们将应用上传到Play商店中。这说明已经没有技术或法律问题阻碍我们编写Android上的JavaFX应用。

接下来,端到端部署的各个部分也将会不断完善。

底层实现揭秘

JavaFX平台是在OpenJDK的子项目——OpenJFX中开发的,OpenJDK通常被视为Java平台开发的大本营。所有的代码开发和话题讨论都是基于一个开放的环境的。

OpenJFX代码库中包含了多个平台上的JavaFX API和实现。JavaFX本身的体系结构是模块化的,平台相关的部分与通用的部分是相互独立的。考虑到JavaFX其中一项设计原则就是要尽可能的利用硬件加速,能够实现这样的设计相当不易。

JavaFX-Android SDK是基于OpenJFX代码库中的代码所创建。移植过程中有两个主要的挑战:

OpenJFX代码中包含一些原生代码,需要交叉编译这些代码以适用于Android系统。
Android上的Dalvik运行环境只包含Java 7的一个子集。不过对拉姆达表达式的支持已经包含在其中。

第一个挑战已经在OpenJFX中彻底解决。实现Android平台上的JavaFX原生功能所需的所有代码已经包含在OpenJFX中。

第二个挑战之所以能够解决的主要原因是OpenJFX的开发者们已经达成共识,不在JavaFX 8u20这一版本中使用Java 8特有的功能。不过,还有许多对Java 7的API调用仍然无法在Android平台上正常运行。好消息是JavaFX-Android SDK本身已经包含了这些缺失的API实现。RetroLambda 项目(已经包含在发布包中)能够替换类文件中动态调用的字节码,因此我们可以在Android平台上的JavaFX应用中使用拉姆达表达式。另外,需要注意的是,目前还不支持java.util.Streams。

Android概念的映射

Android应用的生命周期与典型的桌面应用的生命周期有一定区别。Android使用了Activity的概念。Android与JavaFX之间的概念转换作为其中一部分工作,由JavaFX-Android SDK统一完成。JavaFX-Android SDK包含了一个名为FXActivity的Activity的子类,在JavaFX应用启动时,这个类将被实例化。

整个JavaFX应用都是这个Activity的一部分。在JavaFX应用启动时,JavaFX将接管一切。这样做的好处是让JavaFX的生命周期事件和应用的组织结构,包括导航,能够像桌面应用一样。在进行生命周期管理时,不需要了解Android平台相关的知识。

一般情况下,开发者希望应用是设备无关的,不过在很多时候,也希望能够充分利用Android的特性。针对这些情况,JavaFX-Android SDK为JavaFX开发者提供了一些用于访问Android API的钩子。第一个钩子就是由JavaFX-Android SDK生成的Android的清单文件。默认的清单文件适用于比较简单的应用,不过开发者也可以对其进行配置。在这个文件中将完成权限请求和基础的Android配置参数的设置工作。

另外,Android平台还为JavaFX平台提供了许多与Android设备关联更加紧密的服务,例如位置服务、与NFC阅读器通信的服务等。在标准的Android应用中,可以通过Android的Activity和Context类访问这些服务。在JavaFX平台中并不存在这些Android特有的类。不过JavaFX-Android SDK提供了一个静态方法

Context context = FXActivity.getInstance();

用来访问与创建和运行JavaFX应用的Activity相关联的Context实例。

这个静态方法所返回的Context实例可用于获取Android特有的服务。关于如何使用NFC阅读器的示例可以参考这里。在开源项目OpenMapFX中可以找到另外一个类似的关于如何使用GPS服务的示例。

如有更多关于JavaFX在Android平台上的问题,从Google小组中的JavaFX Android论坛里可以获取到更多有用的信息。

关于作者
Johan Vos从1995年开始使用Java。他是帮助将Java移植到Linux的Blackdown小组的一员。在他合伙创建的公司LodgON中,他的主要工作是为社交网络软件提供基于Java的解决方案。由于他无法在嵌入式开发和企业应用开发之间做出选择,他的主要工作方向是端到端的Java开发,同时也比较精通后端系统和嵌入式设备开发。他目前最感兴趣的技术是后端的Java EE/Glassfish技术和前端的JavaFX技术。他是一个Java Champion,是BeJUG和Devoxx指导小组成员,还是一个JCP成员。他是《Pro JavaFX 8》这本书的主要作者,而且他还是许多Java会议的演讲嘉宾(包括JavaOne和Devoxx)。如果对他感兴趣,可以访问他的博客或者关注他的推特。

查看英文原文:Building and Deploying Android Apps Using JavaFX

转自:http://www.infoq.com/cn/articles/Building-JavaFX-Android-Apps
附上其他 javafx教程:

http://blog.csdn.net/yuanyuan110_l/article/category/918866/2
http://blog.csdn.net/trocp/article/details/8112077
http://www.javafxblogs.com/http://www.javafxblogs.com/
Javafx To android:

https://bitbucket.org/javafxports/android/downloads
javafx扩展开源插件jfxtras:

JFXtras 0.6 Final Released

BEM即块(Block)、元素(Element)、修饰符(modifier),是由著名的俄罗斯搜索引擎团队Yandex提出的一种前端命名方法论。BEM命名约定更加严格,而且包含更多的信息,一般用于团队开发一个耗时的大项目中。
我们常见的BEM命名方式一般都是经过改良的,本文介绍的是Nicolas Gallagher(Twitter前端工程师)的改进版。
命名约定的格式如下:

.block{ ... }           /* 代表更高级别的抽象或组件 */
.block__element{ ... }  /* 代表.block的后代,用于形成一个完整的block整体 */
.block--modifier{ ... } /* 代表block的不同状态或不同版本 */

之所以使用两个连字符和下划线而不是一个,是为了让自己定义的块可以用单个连字符来界定,如:

.myHeader-search{ ... }                 /* 自定义的某个块 */
.myHeader-search__textElement{ ... }    /* 自定义块中的textElement元素 */
.myHeader-search--full{ ... }           /* 自定义块中的full修饰符 */

BEM的关键在于,光凭名字就可以告诉其他开发者某个标记(class)是用来干什么的。通过直观地阅读class属性,我们就能明白模块之间是如何关联的:有一些仅仅是组件,有一些则是这些组件的子孙或者元素,还有一些是组件其他形态或者是修饰符。我们通过下面的示例来进一步说明:

<!-- 假设有个组件Person -->
<div class="Person">
    <!-- hand是子元素,left、male是状态或修饰符:左、男性,即男性的左手-->
    <div class="hand left male"></div>
    <!-- 女性的右手 -->
    <div class="hand right female"></div>
</div>

上面的代码使用了常规CSS来表示,各个class都是有意义的,但是他们之间却是脱节的,拿female来说,是指女性人类还是指某种雌性的动物?hand是在表示指针(英文hand有钟表指针的含义)还是一只正在玩指纸牌的手呢?使用BEM我们可以获得更多的描述和更加清晰的结构,并且通过命名我们就可以知道元素之间的关联。代码如下:

<!-- 使用BEM命名 -->
<div class="Person">
    <!-- 女性人类的手 -->
    <div class=Person__hand--female></div>
    <!-- 人类的左手 -->
    <div class="Person_hand--left"></div>
</div>

再看一个常规方式命名的例子:

<!-- 搜索表单,full表示当前状态 -->
<form class="site-search full">
    <input type="text" class="field" />
    <input type="submit" class="button" value="搜索" />
</form>

上面的命名不好的地方就在于名称不够精确,不能告诉我们足够的信息。尽管我们可以用它们完成工作,但它们确实非常含糊不清,不够优雅。使用BEM记号法后就是下面的代码:

<!-- 状态或修饰符使用两个横线(--) -->
<form class="site-search site-search--full">
    <!-- 模块下的元素使用两个下划线(__) -->
    <input type="text" class="site-search__field"/>
    <input type="submit" class="site-search__button" value="搜索" />
</form>

在上面的代码中,我们可以清晰地看到有个叫.site-search的块,内部有个叫做.site-search__field的元素,并且site-search还有另一种形态叫做.site-search–full

  • 通常人们会认为BEM这种写法难看。但是,如果仅仅认为这种写法看上去不怎么好看而羞于使用,那么我们将错失最重要的东西。
  • BEM看上去有些怪怪的,但是它的好处远远超过外观上的瑕疵
  • BEM有可能导致输入更长的文本,但是大部分编辑器都有代码自动补全的功能,而且gzip压缩将会让我们消除对文件体积的担忧

BEM命名规则:http://segmentfault.com/a/1190000000391762
class命名方案:http://www.w3cplus.com/css/css-class-name.html
常用的CSS命名规范:http://www.html5cn.org/article-7600-1.html
通用CSS笔记、建议与指导:https://github.com/chadluo/CSS-Guidelines/blob/master/README.md
CSS代码重构和优化之路:http://luopq.com/2016/01/05/css-optimize/
CSS进阶:http://caibaojian.com/toutiao/6098

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最方便的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

本篇指南主要介绍Log4j 1.x版本的配置与使用

Log4j最新版下载地址:http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.17/log4j-1.2.17.zip

一、在Eclipse中配置Log4j

1.1、新建Java工程,导入包log4j-1.2.17.jar,工程如下图:

1.2、在src下创建log4j.propertiese配置文件,复制并粘贴以下内容到文件中:

### 根logger主要定义log4j支持的日志级别及输出目的地 ###
log4j.rootLogger=debug,stdout,file

### 配置控制台输出及输出格式 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG级别以上的日志到工程根目录下的logs/log.log文件,可以自行修改为绝对路径 ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=logs/log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ]  %m%n

注意:粘贴到Eclipse中后,有些小伙伴会呈现“乱码”:\u6839,这是Unicode编码,并不影响程序的执行。

1.3、在Java类中书写代码

package com.xuetang9.kenny;
import org.apache.log4j.Logger;
/**
 * 使用Log4j打印日志信息<br/>
 * @author Kenny
 * @version 1.0
 * @date 2018年1月8日 下午9:41:13
 * @copyright 老九学堂
 */
public class LoggerTest {
    private static Logger logger = Logger.getLogger(LoggerTest.class);
    public static void main(String[] args) {
        //日志记录的行为是分等级的,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL或者您定义的级别。
        //Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
        logger.debug("记录debug级别信息");
        logger.info("记录info级别信息");
        logger.warn("记录warn级别信息");
    }
}

1.4、输出结果:

控制台:

logs/log.log文件中:

二、Log4j的基本使用方法

Log4j是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,诸如:数据库,文件,控制台,UNIX系统日志等。
Log4j中有三个主要组成部分:
– loggers: 负责捕获记录信息。
– appenders : 负责发布日志信息,以不同的首选目的地。
– layouts: 负责格式化不同风格的日志信息。

2.1、配置文件的定义

log4j也可以通过配置文件的方式进行设置,目前支持两种格式的配置文件:xml文件和properties文件(推荐)。
1、配置根Logger,语法为:

log4j.rootLogger=[level], appenderName1, appenderName2, ....

level表示日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别(E、F、G等)。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,我们可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。appenderName就是指日志信息输出到哪个地方。可以同时指定多个输出目的地。
2、配置日志信息输出目的地(Appender),语法如下:

log4j.appender.appenderName=fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1=value1
    ...............
log4j.appender.appenderName.option=valueN

其中,log4j提供的appender有以下几种,根据项目需要使用即可:

org.apache.log4j.ConsoleAppender            输出到控制台
org.apache.log4j.FileAppender               输出到文件
org.apache.log4j.DailyRollingFileAppender   每天产生一个日志文件
org.apache.log4j.RollingFileAppender        文件大小到达指定尺寸的时候产生一个新的文件
org.apache.log4j.WriterAppender             将日志信息以流格式发送到任意指定的地方

3、配置日志信息输出的格式(布局),语法如下:

log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1=value1
    ..............
log4j.appender.appenderName.layout.option=valueN

Log4j提供的格式layout有以下几种:

org.apache.log4j.HTMLLayout     以HTML表格形式布局
org.apache.log4j.PatternLayout  可以灵活地指定布局模式(常用)
org.apache.log4j.SimpleLayout   包含日志信息的级别和信息字符串
org.apache.log4j.TTCCLayout     包含日志产生的时间、线程、类别等等信息

Log4j采用类似C语言的printf函数格式化日志信息,打印参数如下:

%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n”
%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2018年12月31日 23:59:59,123
%l 输出日志事件的发生位置,包括类名、发生的线程,以及在代码中的行数。如:LoggerTest.main(LoggerTest.java:18)

4、日志级别
每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:
A:off 最高等级,用于关闭所有日志记录。
B:fatal 指出每个严重的错误事件将会导致应用程序的退出。
C:error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
D:warm 表明会出现潜在的错误情形。
E:info 一般和在粗粒度级别上,强调应用程序的运行全程。
F:debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
G:all 最低等级,用于打开所有日志记录。

上面这些级别是定义在org.apache.log4j.Level类中。Log4j只建议使用4个级别,优先级从高到低分别是error,warn,info和debug。通过使用日志级别,可以控制应用程序中相应级别日志信息的输出。例如,如果使用b了info级别,则应用程序中所有低于info级别的日志信息(如debug)将不会被打印出来。