学习java深度学习,提升编程思维,适合掌握基础知识的工作者学习
1.反射和代理
1.1 概念介绍
在Java编程中,反射和代理是两个强大的特性,能够在运行时动态地操作和扩展类的行为。通过反射,我们可以在不知道类的具体信息的情况下操作类的属性和方法;而代理则允许我们创建一个代理类来拦截并增强目标对象的行为
1.2应用场景
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制,掌握反射和代理机制,有利于我们了解这些框架的内核原理,提升编程思维
- xml的bean配置的注入
- AOP拦截
- 数据库事务
- springMVC
- Java注解
- 开发工具如IDEA,提供一个类的属性方法展示给我们智能快捷选择
这些内核都是反射和代理机制在其作用
1.3 反射-reflect
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。
准备工作
Food.java
public class Food {
private Integer id;
private String name;
// ....省略get set 等
}
1.3.1 获得类-Class
获得类有三种方式,看代码演示
/***
* @Description: 得到Class
*/
@Test
public void t1() throws ClassNotFoundException {
Class cls=null;
//1.通过类名直接获得
cls= Food.class;
//2.通过实例获得
cls=new Food(1, "火锅").getClass();
//3.通过路径+类名的字符串获得
cls=Class.forName("com.jsoft.reflection.Food");
printClassInfo(cls);
}
/***
* @Description: 读取类的信息
*/
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
打印效果
Class name: com.jsoft.reflection.Food
Simple name: Food
Package name: com.jsoft.reflection
is interface: false
is enum: false
is array: false
is primitive: false
1.3.2 获得类的字段-Field
演示代码
public class Father extends Man{
private Integer age;
public String job;
}
public class Man{
private Integer id;
private String name;
public String address;
}
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。
我们先看看如何通过Class实例获取字段信息。Class类提供了以下几个方法来获取字段:
注意:
Declared修饰就只管当前类字段,和private、public无关
否则就包含父类的public
测试代码
@Test
public void t2() throws NoSuchFieldException {
Class cls= Father.class;
//1.得到当前类的所有字段,不包含父类
Field[] fs=cls.getDeclaredFields();
System.out.println("1.得到当前类的所有字段,不包含父类");
for (Field f : fs) {
printFeild(f);
}
//2.得到当前类某个字段
System.out.println("2.得到当前类某个字段,不包含父类");
Field f=cls.getDeclaredField("job");
printFeild(f);
//3.得到当前类及父类的所有public字段
System.out.println("3.得到当前类及父类的所有public字段");
fs=cls.getFields();
for (Field f1 : fs) {
printFeild(f);
}
//4.得到当前类及父类的public字段
System.out.println("4.得到当前类及父类的public字段,如果是private,则要报错");
f=cls.getField("address");
printFeild(f);
}
/***
* @Description: 打印字段信息
*/
public void printFeild( Field f){
System.out.println("//------------字段信息 start");
System.out.println("字段名称:"+f.getName());
System.out.println("字段类型:"+f.getType().getName());
int m = f.getModifiers();
System.out.println("field is final:"+Modifier.isFinal(m));; // true
System.out.println("field is Public:"+Modifier.isPublic(m));; // true
System.out.println("field is Protected:"+Modifier.isProtected(m));; // true
System.out.println("field is Private:"+Modifier.isPrivate(m));; // true
System.out.println("field is Static:"+Modifier.isStatic(m));; // true
System.out.println("//------------字段信息 end");
}
1.3.3 动态访问和修改对象实例的字段
@Test
public void t3() throws NoSuchFieldException, IllegalAccessException {
Food food=new Food(1, "火锅");
System.out.println("food.getName:"+food.getName());
Class cls=food.getClass();
Field f=cls.getDeclaredField("name");
f.setAccessible(true); //不设置这个private 字段要抛出异常
//通过field获得值
Object name= f.get(food);
System.out.println("name:"+name);
//通过field设置值
f.set(food, "西北风");
System.out.println("name:"+food.getName());
}
1.3.4 获得类方法-Method
示例代码
@Data
public class Father extends Man{
private Integer age;
public String job;
public String play(int type){
System.out.println("param value:"+type);
return type+"";
}
public String sleep(String type, Date date){
System.out.println("param value type:"+type+";date:"+date);
return type+"";
}
private void self(){
System.out.println("this is private method");
}
public void dd(){
System.out.println("没有参数方法()");
}
}
@Data
public class Man{
private Integer id;
private String name;
public String address;
@Override
public Food eat() {
return new Food(1,"火锅");
}
}
测试代码
@Test
public void t4() throws IllegalAccessException, NoSuchMethodException {
Class cls= Father.class;
System.out.println("1. 获取所有public的Method(包括父类)");
Method[] methods=cls.getMethods();
for (Method method : methods) {
printMethod(method);
}
System.out.println("2. 获取某个public的Method(包括父类)");
//第一个参数为方法名,第二参数是可变参数,传递方法的参数类型,如果无参数则不传递
Method m= cls.getMethod("getName");
printMethod(m);
//方法有1个参数
//注意int foat double这些基本类型对用的类是int.class,不是Integer.class
m=cls.getMethod("play", int.class);
printMethod(m);
//方法有多个参数
m=cls.getMethod("sleep", String.class, Date.class);
printMethod(m);
System.out.println("3. 获取当前类的的Method(不包括父类)");
m= cls.getDeclaredMethod("getJob");
printMethod(m);
//私有方法也可以
m= cls.getDeclaredMethod("self");
//父类方法不可以,要报java.lang.NoSuchMethodException
m= cls.getDeclaredMethod("eat");
printMethod(m);
printMethod(m);
}
public void printMethod(Method addMethod){
System.out.println("---------方法名称: 【" + addMethod.getName()+"】---相关属性------------");
System.out.println("修饰符: " + Modifier.toString(addMethod.getModifiers())); //public private等
System.out.println("返回值: " + addMethod.getReturnType()); //返回值类型的class数组
Class[] paramsCls= addMethod.getParameterTypes(); //参数值类型对应的class数组
}
1.3.5 调用方法.invoke
代码
@Test
public void t5() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Father father=new Father();
Class cls= father.getClass();
Method m=null;
//无参无返回方法
m=cls.getMethod("dd");
m.invoke(father);
//有参数,有返回值
m=cls.getMethod("sleep", String.class,Date.class);
String ret=(String)m.invoke(father, "1",new Date());
System.out.println("返回值:"+ret);
//private方法调用
m=cls.getDeclaredMethod("self");
//私有方法必须设置为m.setAccessible(true);
m.setAccessible(true);
m.invoke(father);
}
jdk内置对象的方法调用的另外一种写法
@Test
public void t6() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String s="abcde";
String sub=s.substring(0,2);
System.out.println(sub);
//通过类加载
Class cls=String.class;
Method method=cls.getMethod("substring",int.class,int.class);
sub=(String) method.invoke(s, 0,2);
System.out.println(sub);
}
其实就相当于
1.3.6 类实例化-构造函数Constructor
准备类
public class Const {
private Integer id;
private String name;
public Const() {
System.out.println("无参数构造方法");
}
public Const(Integer id, String name) {
System.out.println("有参数构造方法");
this.id = id;
this.name = name;
}
}
Class.newInstance()
@Test
public void t7() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class cls =Class.forName("com.jsoft.reflection.Const");
Const aConst = (Const) cls.newInstance();
}
Class.newInstance()最大的问题是只能实例化无参的构造方法,如果我们把无参构造方法屏蔽掉,代码会出错,所以我们需要使用Constructor类来实现有参的构造方法
代码示例
@Test
public void t8() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls =Class.forName("com.jsoft.reflection.Const");
//得到所有构造函数
Constructor[] constructors= cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("修饰符: " + Modifier.toString(constructor.getModifiers()));
System.out.println("构造函数名: " + constructor.getName());
System.out.println("参数列表: " );
Class[] cs= constructor.getParameterTypes();
for (Class c : cs) {
System.out.println(c.getName());
}
}
//获得具体一个(无参)
Constructor cst=cls.getConstructor();
//newInstance实例化
Const const1=(Const) cst.newInstance();
//获得具体一个(有参)
cst=cls.getConstructor(Integer.class,String.class);//传入构造函数的参数的类型
//newInstance实例化
const1=(Const) cst.newInstance(1,"蒋增奎");
}
1.3.7 instanceof
class Person{
}
class Teacher extends Person{
}
class Student extends Person{
}
测试
public class test01 {
public static void main(String[] args) {
Object obj = new Student(); // 主要看这个对象是什么类型与实例化的类名
System.out.println(obj instanceof Student); // true
System.out.println(obj instanceof Person); // true
System.out.println(obj instanceof Object); // true
System.out.println(obj instanceof String); // false
System.out.println(obj instanceof Teacher); // false 无关系
System.out.println("========================");
Person person = new Student();
System.out.println(person instanceof Person); // true
System.out.println(person instanceof Object); // true
// System.out.println(person instanceof String); // 编译错误
System.out.println(person instanceof Teacher); // false 无关系
}
}
1.3.8 利用反射来解析spring配置
<bean id="employee" class="com.jsoft.po.Employee">
<property name="id" value="1" />
<property name="name" value="蒋增奎" />
<property name="deptId" value="001" />
</bean>
解析思路
1.4 代理-proxy
1.4.1 代理模式
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
举个例子:
boy向girl求婚
(1)现代:boy和girl直接发生通信,girl迫不及待的同意了
(2)古代:boy不能直接和girl发生关系,需要通过媒婆,boy向媒婆发起请求,媒婆转告girl,gilr同意,媒婆觉得女孩如果同意,婚前应该收彩礼10万,婚后不能家暴,媒婆就是代理,在原始的反馈上增加了自己的要求,提升了框架的健壮性。
代理分为静态代理和动态代理两种类型
1.4.2 准备工作
public interface Love {
/***
* @Description: 结婚请求
*/
public void marray();
/***
* @Description: 睡觉请求
*/
public void sleep();
public String like(int type);
public int saveMoney(int money);
}
/**
接口实现类
**/
public class LoveImpl implements Love {
@Override
public void marray() {
System.out.println("我同意");
}
public void sleep() {
System.out.println("我同意");
}
public String like(int type){
if(type==1)
return "69";
return "96";
}
@Override
public int saveMoney(int money) {
return money*10;
}
}
1.4.3 静态代理
代码:
//代理类要实现接口
public class LoveProxy implements Love {
private Love target;//代理的接口
/***
* @Description: 构造方法注入代理的接口
*/
public LoveProxy(Love love){
target=love;
}
@Override
public void marray(){
System.out.println("============代理marray方法");
System.out.println("要彩礼10万");
target.marray();
System.out.println("不能家暴");
}
public void sleep(){
System.out.println("============代理sleep方法");
System.out.println("安全措施");
target.sleep();
}
public String like(int type){
System.out.println("============代理like方法");
System.out.println("注意身体");
return target.like(type);
}
public static void main(String[] args) {
//实际应用:代理的应用
Love love=new LoveImpl();
LoveProxy proxy=new LoveProxy(love);
proxy.marray();
proxy.sleep();
System.out.println( proxy.like(1));
}
}
效果:在以前的需求上扩展了功能
============代理marray方法
要彩礼10万
我同意
不能家暴
============代理sleep方法
安全措施
我同意
============代理like方法
注意身体
69
总结:代理模式的实现
1.5 动态代理
静态代理:通过手动编写代理类来实现,需要为每个目标对象编写一个代理类。
动态代理:通过Java提供的相关接口和类,在运行时动态生成代理类,无需手动编写代理类。
我们仍然先定义了接口,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
动态代理三个重要的java类
1.5.1 InvocationHandler介绍
代理实例的调用处理器需要实现InvocationHandler接口,并且每个代理实例都有一个关联的调用处理器。当一个方法在代理实例上被调用时,这个方法调用将被编码并分派到其调用处理器的invoke方法上。
接口里最重要的方法invoke
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
其有三个参数,分别为:
proxy:是调用该方法的代理实例,是java在编译环节自动生成的,对开发者没有意义,如:jdk.proxy1.$Proxy0,注意不是代理的目标类,也不是接口。
method:是在代理实例上调用的接口方法对应的Method实例。
args:一个对象数组,其中包含在代理实例上的方法调用中传递的参数值,如果接口方法不带任何参数,则为 null。基元类型的参数包装在相应基元包装类的实例中,例如 java.lang.Integer 或 java.lang.Boolean
返回值:调用代理实例上的方法的返回值。
如果接口方法声明的返回类型是基元类型,那么该方法返回的值必须是对应基元包装类的实例;
否则,它必须是可分配给声明的返回类型的类型。如果此方法返回的值为 null,并且接口方法的返回类型为基元,则代理实例上的方法调用将引发 NullPointerException。如果此方法返回的值与接口方法的声明返回类型不兼容(如上所述),则代理实例上的方法调用将引发 ClassCastException
这里最重要的参数是method,可以通过moth.invote调用目标对象的方法
1.5.2 Proxy类
Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类,其最主要的方法是一个newProxyInstance的静态方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
{......}
参数说明:
ClassLoader :
对应代理目标类对应的类加载器
Class<?>[] interfaces
目标代理类的接口类对应的CLASS,代理目标类的接口可以是多个
InvocationHandler h
要调用的处理器
返回对象:
返回实例,Object可以转化成对应的目标代理类的接口
1.5.3动态代理类的实现
准备代码:为了增加效果,我们在增加一个类和接口
public interface LifeService {
public String eat(String name);
public String drink(String name);
public int like(int type);
}
public class Life implements LifeService {
public String eat(String name){
String ret="I like eat:"+name;
System.out.println(ret);
return ret;
}
public String drink(String name){
String ret="I like drink:"+name;
System.out.println(ret);
return ret;
}
public int like(int type){
String ret="mylike:"+type;
System.out.println(ret);
return type;
}
}
自己做一个调度类,继承InvocationHandler
//自己编写处理器
public class MyHandler implements InvocationHandler {
private Object target;//代理的目标类
//通过构造方法注入代理的目标
public MyHandler(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//给所有方法加上日志
System.out.println("给大家增加一个log日志...........");
String mName=method.getName();
//给以save开头的方法名添加事务
if(mName.length()>=4 && "save".equals(mName.substring(0,4))){
System.out.println("开启事务...............");
}
//方法名是sleep的增加安全措施
if("sleep".equals(mName)){
System.out.println("注意安全措施...............");
}
//调用目标代理类的方法并返回方法返回值
return method.invoke(target,args);
}
}
封装一个调度工厂
public class ProxyFactory {
/***
* @Description: 动态代理工厂
* @Create:2023/12/24 12:43
* @Param: [target:代理的对象实例
* @Return: java.lang.Object 代理接口对象
*/
public static Object creatProxy(Object target){
//动态调用代理
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标的加载
target.getClass().getInterfaces(), //目标类的接口的class,可以是多个,数组,如果指定,则要使用new Class[...]
new MyHandler(target));
}
public static void main(String[] args) {
//应用1
Love love=(Love)creatProxy(new LoveImpl() );
love.marray();
love.sleep();
int money=love.saveMoney(10);
System.out.println("存钱:"+money);
//应用2
LifeService lifeService=(LifeService)creatProxy(new Life());
lifeService.eat("火锅");
lifeService.drink("可乐");
}
}
执行效果:
给大家增加一个log日志...........
我同意
给大家增加一个log日志...........
注意安全措施...............
我同意
给大家增加一个log日志...........
开启事务...............
存钱:100
给大家增加一个log日志...........
I like eat:火锅
给大家增加一个log日志...........
I like drink:可乐
1.5.4动态实现接口
有时候,我们不想为接口单独做一个实现类,我们可以通过动态代理来实现,这个在springboot里面非常常见的编程思想
public class DynamicProxy {
public static void main(String[] args) {
//定义一个InvocationHandler对象,但不传入实现类,相当于
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("marray")) { //这个方法去实现,和做一个实现类原理一致
System.out.println("我同意个锤子");
}
return null;
}
};
Love love = (Love) Proxy.newProxyInstance(
Love.class.getClassLoader(), // 传入ClassLoader
new Class[] { Love.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
System.out.println("3333");
love.marray();
}
}
1.5.4替换某个方法
在大量的java框架中,某种功能有默认实现,我们想替换掉,自己定义实现,其他方法不变,我们也可以使用动态代理技术实现。
public class ProxyReplcae {
public static void main(String[] args) {
LoveImpl loveImp=new LoveImpl();//默认实现,在spring里一般采用注入
//定义一个InvocationHandler对象
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这个对象不想使用以前实现,自定义实现
if (method.getName().equals("marray")) {
System.out.println("不同意结婚");
return null;
}
else
return method.invoke(loveImp,args); //其他的依然使用默认实现
}
};
Love love = (Love) Proxy.newProxyInstance(
Love.class.getClassLoader(), // 传入ClassLoader
new Class[] { Love.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
love.marray(); //已经使用自定义
love.sleep();//依然是默认实现
}
}
1.5 动态代理框架CGLIB
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
1.5.1 使用介绍
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class MyDay {
public void work(int hour){
System.out.println(hour+"点开始工作");
}
public void sleep(int hour){
System.out.println(hour+"点开始睡觉");
}
}
代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @class: com.jsoft.reflection.CglibProxyFacory
* @description:
* @author: jiangzengkui
* @company: 教育家
* @create: 2023-12-24 15:29
*/
public class CglibProxyFacory {
/***
clazz:代理目标类的class
*/
public static Object getProxy(Class<?> clazz) {
//自定义一个拦截器
MethodInterceptor me=new MethodInterceptor(){
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object; // 方法的返回值,如果void则为null
}
};
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(me);
// 创建代理类
return enhancer.create();
}
public static void main(String[] args) {
MyDay myDay= (MyDay) CglibProxyFacory.getProxy(MyDay.class);
myDay.work("早上8");
myDay.sleep("晚上23");
}
}
效果:
before method work
早上8点开始工作
after method work
before method sleep
晚上23点开始睡觉
after method sleep
1.5.2 cglib说明
主要要用到的3个类
这个就是拦截器处理接口,其重要接口方法
/**
* @param obj 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param proxy 用于调用原始方法
* renturn 方法的返回值,如果void则为null
*/
public Object intercept(
Object obj,
java.lang.reflect.Method method,
Object[] args,
MethodProxy proxy
) throws Throwable;
这个方法为调用代理目标类的原始方法,其调用函数:MethodProxy.invokeSuperr(Object obj, Object[] args) ,其自动为代理目标类创建一个接口
/**
Object obj:传入代理目标类对象实例
Object[] args:代理目标类的方法参数
**/
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
这个类用于调用拦截器,创建增强的代理目标类
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(me);
// 创建代理类-增强后的目标代理类
Object obj=enhancer.create();
1.6 总结
反射机制:
- 根据类的名称或者class本身,可以进行实例化对象、构造方法、获得和修改字段、获得方法名和调用方法
代理机制:
- 代理机制就是代理本身目标类,增强其功能
- 静态代理,就是继承目标类对应的接口,实现所有的接口方法,通过接口的封装,增强目标类
- 动态代理:就是创造一个拦截器,拦截器里去增强目标类的方法,前提:必须有接口类
- Cglib:增对目标类没有接口这种情况,自动创建一个接口,实现没有接口类也可以做代理