SPI机制
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
基于接口的编程+策略模式+配置文件 组合实现的动态加载机制。
需要遵循如下约定:
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的jar包放在主程序的classpath中;
- 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
- SPI的实现类必须携带一个不带参数的构造方法;

反射
反射机制是 Java 语言提供的一种基础功能,赋予程序在 运行时 自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以 运行时 修改类定义。
- 在运行时能判断任意一个对象所属的类。
- 在运行时能构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
应用场景
反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。
特点
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
动态代理
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。
(动态)代理模式主要涉及三个要素
- 抽象类接口
- 被代理类(具体实现抽象接口的类)
- 动态代理类:实际调用被代理类的方法和属性的类
实现方式
实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用字节码操作机制,类似 ASM、CGLIB(基于 ASM)、Javassist 等。
举例,常可采用的JDK提供的动态代理接口InvocationHandler来实现动态代理类。其中invoke方法是该接口定义必须实现的,它完成对真实方法的调用。通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。此外,我们常可以在invoke方法实现中增加自定义的逻辑实现,实现对被代理类的业务逻辑无侵入
动态代理:JDK动态代理和CGLIB代理的区别?
JDK动态代理只能对实现了接口的类生成代理,而不能针对类,
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能。
Spring在选择用JDK还是CGLiB的依据是什么?
- 当Bean实现接口时,Spring就会用JDK的动态代理
- 当Bean没有实现接口时,Spring使用CGlib是实现
- 可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>)
自动装箱、拆箱
自动装箱实际上算是一种 语法糖。什么是语法糖?可以简单理解为 Java 平台为我们自动进行了一些转换,保证不同的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的。
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:1
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:1
Integer i = 10;
- 装箱:自动将基本数据类型转换为包装器类型(通过调用包装器的valueOf方法实现)
- 拆箱:自动将包装器类型转换为基本数据类型(通过调用包装器的 xxxValue方法实现的,xxx代表对应的基本数据类型)
Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别:
- 第一种方式不会触发自动装箱的过程;而第二种方式会触发;
- 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)
注意:当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d); //true Integer缓存了-128~127,在该范围内返回的是同一个实例
System.out.println(e==f); //false 超过了缓存范围,不同实例
System.out.println(c==(a+b)); //true 包含算术运算,会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等
System.out.println(c.equals(a+b)); //true a+b先各自触发自动拆箱过程,再触发自动装箱过程,再进行equals比较
System.out.println(g==(a+b)); //true 同上
System.out.println(g.equals(a+b)); //false a+b最后装箱后是Integer型,Long型equals参数如果是非Long型则直接返回false
System.out.println(g.equals(a+h)); //true a+h装箱后是Long型,比较的是数值
}
}
扩展
Integer缓存机制(-128~127)并不是只有 Integer 才有,同样存在于其他的一些包装类,比如:
- Boolean,缓存了 true/false 对应实例,确切说,只会返回两个常量实例 Boolean.TRUE/FALSE。
- Short,同样是缓存了 -128 到 127 之间的数值。
- Byte,数值有限,所以全部都被缓存。
- Character,缓存范围’\u0000’ 到 ‘\u007F’。