文章目录
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把java类中的各种成分映射成一个个的Java对象,在运行时可以知道任意一个类的属性和方法
Class类介绍
作用:
一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
获取Class对象的三种方式:
类名.class
对象.getClass()
Class.forName(类全名)
获取属性:
- getField
- getFields
- getDeclaredFields
- getDeclaredField
获取方法:
- getMethod
- getMethods
- getDeclareMethod
- getDeclareMethods
- invoke
获取构造方法:
- getConstructor
- getConstructors
- getDeclaredConstructor
- getDeclaredConstructors
创建对象:
- newInstance
- Constructors.newInstance
获取父类:
接口
- getAnnotatedInterfaces—AnnotatedType[]
- getInterfaces—Class[]
父类 - getSuperclass
- getInterfaces
Modifier: 修饰符
内省
内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。
不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
javaBean
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
获取Beaninfo
BeanInfo这个类就可以获取到类中的方法和属性。例如类 A 中有属性 name, 那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性
方法:
- introspector.getBeanInfo(Class A)—获取BeanInfo
- beaninfo.getPropertyDescriptors();
- getReadMethod
- getWriteMethod
动态代理
需加入asm-9.2.jar,cglib-3.3.0.jar这两个包。
JDK动态代理
底层实现:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
代码:
package com;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
public static void main(String[] args) {
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
HelloInterface hello = new Hello();
InvocationHandler handler = new ProxyHandler(hello);
HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
proxyHello.sayHello();
}
}
interface HelloInterface {
void sayHello();
}
class Hello implements HelloInterface{
@Override
public void sayHello() {
System.out.println("Hello World!");
}
}
class ProxyHandler implements InvocationHandler{
private Object object;
public ProxyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
}
CGLIB代理(第三方)
cglib是一个java字节码的生成工具,它动态生成一个被代理类的子类,子类重写被代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
代码:
package com;
import java.lang.reflect.Method;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGProxy {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 继承被代理类
enhancer.setSuperclass(HelloServiceImpl.class);
// 设置回调
enhancer.setCallback(new HelloMethodInterceptor());
// 设置代理类对象
HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
// 在调用代理类中方法时会被我们实现的方法拦截器进行拦截
helloService.sayBey();
}
}
class HelloServiceImpl {
public void sayHello() {
System.out.println("Hello Zhanghao");
}
public void sayBey() {
System.out.println("Bye Zhanghao");
}
}
class HelloMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("After: " + method.getName());
return object;
}
}
总结
- 静态代理比较容易理解, 需要被代理的类和代理类实现自同一个接口, 然后在代理类中调用真正实现类, 并且静态代理的关系在编译期间就已经确定了。而动态代理的关系是在运行期间确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。
- JDK 动态代理所用到的代理类在程序调用到代理类对象时才由 JVM 真正创建,JVM 根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的 class 文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
- 静态代理和动态代理都是基于接口实现的, 而对于那些没有提供接口只是提供了实现类的而言, 就只能选择 CGLIB 动态代理了
JDK动态代理与CGLIB动态代理的区别(面试题)
- JDK 动态代理基于 Java 反射机制实现, 必须要实现了接口的业务类才能用这种方法生成代理对象。
- CGLIB 动态代理基于 ASM 框架通过生成业务类的子类来实现。
- JDK 动态代理的优势是最小化依赖关系,减少依赖意味着简化开发和维护并且有 JDK 自身支持。还可以平滑进行 JDK 版本升级,代码实现简单。
- 基于 CGLIB 框架的优势是无须实现接口,达到代理类无侵入,我们只需操作我们关系的类,不必为其它相关类增加工作量,性能比较高。
描述代理的几种实现方式?分别说出优缺点
- 代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK 动态代理” 和 “CGLIB 动态代理” 实现。
- 静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object.
优点: 可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
缺点: 不同的接口要有不同的代理类实现,会很冗余。 - JDK 动态代理:
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现。 - jdk 的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象。
- jdk 动态代理之所以只能代理接口是因为代理类本身已经 extends 了 Proxy,而 java 是不允许多重继承的,但是允许实现多个接口
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。 - CGLIB 代理:
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK 方式解决不了; - CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
- CGLib 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLib 合适,反之,使用 JDK 方式要更为合适一些。
- 由于 CGLib 由于是采用动态创建子类的方法,对于 final 方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
Lombok
安装:
- Lombok详细教程及idea中lombok插件的安装:https://www.jianshu.com/p/3ce2f0a39df4
- Eclipse安装lombok插件,以及lombok事例:https://www.jianshu.com/p/1f4dff9686a1
作用:
帮使用者提高编码效率减少重复与冗余的代码
原理:
ASM 动态修改class文件
java bean相关
@Setter
@Getter
- @Getter(lazy = true) 懒加载属性(懒汉)
- 这个与上面@Getter不同,那个是修饰在类上的,也可以修饰在属性上。如果有lazy=true只能修饰在属性,并且还要是private final修饰,限制很大.
@ToString
@EqualsAndHashCode:生成equals方法与hashCode方法
@NoAragsConstructor
- 添加一个无参构造函数
- 这个注解在没有其它有参构造函数的情况下使用意义不大,因为在这种情况下java默认会添加一个无参构造函数
@AllArgsConstructor:添加一个所有参数的构造函数
@RequiredArgsConstructor
- 生成一个包含必填参数的构造函数
- 要与@NonNull 搭配使用,该注解修饰的属性就是必填参数
@Date: 这是一个综合注解了,等于同时使用@Getter, @Setter, @ToString,@EqualsAndHashCode,@RequiredArgsConstructor
@Value: 相当于不可变类的@Date,他会默认给属性加上final
@Accessors
- 这个注解要搭配@Getter与@Setter使用,用来修改默认的setter与getter方法的形式
- @Accessors有三个参数可以使用,chain 链式的形式;fluent 流式的形式(getter和setter方法名和属性名一致);prefix 生成指定前缀的属性的getter与setter方法,并且生成的getter与setter方法时会去除前缀(@Data不起作用)
@Synchroized:给方法加一个同步锁
@NonNull: 设置不能为空的参数或属性
@SneakyThrows:忽略异常(throws)