反射
Java 反射,可以获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器、现实接口等,并且可以调用任意方法和实例化任意一个类的对象,通过反射我们可以实现动态装配,降低代码的耦合度、实现动态代理等,不过反射的过度使用会严重消耗系统资源。
- 反射的优缺点
- 可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
- 缺点:对性能有影响,这类操作总是慢于直接执行java代码。
- 如何解决反射的性能问题
- 聊到性能,记得想好怎么回答优化方面的问题,否则就是自己搬起石头砸自己的脚。
-
静态代理
想对方法进行一个增强, 需要修改源代码. 假如有100个方法, 我们需要修改100个方法的源代码来实现方法增强.
/**
* 增删改查方法增强例子
* @author : Shen Hanbo
* @date : 2020/11/9 16:01
*/
public class CrudServiceImpl implements CrudService {
public void insert() {
System.out.println("增强新增前置处理");
System.out.println("新增");
System.out.println("增强新增后置处理");
}
public void delete() {
System.out.println("增强删除前置处理");
System.out.println("删除");
System.out.println("增强删除后置处理");
}
public void update() {
System.out.println("增强更新前置处理");
System.out.println("更新");
System.out.println("增强更新后置处理");
}
public void query() {
System.out.println("增强查询前置处理");
System.out.println("查询");
System.out.println("增强查询后置处理");
}
//...假如下面还有一堆方法的话, 每个方法都需要去手动改源代码
}
解决思路
生成一个代理类, 代理类要和被代理类拥有一样方法(实现同一个接口), 然后调用方直接调用代理类的方法, 而非被代理类的方法. 在代理类的实现方法中, 增强被代理类的方法, 然后客户端请求代理类. 代码
用房东/中介/租客
首先房东和中介都需要有”租房”这个方法, 中介来增强房东的”租房”方法,因此需要定义一个租房接口. ```java /**
- 租房接口
- @author : Shen Hanbo
@date : 2020/11/9 15:52 */ public interface ZuFang {
/**
- 房东和中介都需要实现租房接口,因为他们都有租房的功能 */ void zuFang();
}
2. 房东的"租房"方法, 即被代理的方法
```java
/**
* 房东类
* @author : Shen Hanbo
* @date : 2020/11/9 15:54
*/
public class FangDong implements ZuFang {
/**
* 房东出租房子(被代理方法)
*/
public void zuFang() {
System.out.println("房东出租房子");
}
}
中介的”租房”方法, 即代理方法, 在代理方法中对原方法进行增强 ```java /**
- 中介(代理类)
- @author : Shen Hanbo
@date : 2020/11/9 16:10 */ public class ZhongJie implements ZuFang {
/**
- 需要一个房东 */ private FangDong fangDong;
public ZhongJie(FangDong fangDong) { this.fangDong = fangDong; }
/**
- 中介代理房东出租房子(代理方法) */ public void zuFang() { //前置增强 System.out.println(“前置增强”); //原方法 fangDong.zuFang(); //后置增强 System.out.println(“后置增强”); } }
4. main
```java
public static void main(String[] args) {
//房东
FangDong fangDong = new FangDong();
//中介代理房东
ZhongJie zhongJie = new ZhongJie(fangDong);
//调用中介的租房方法
zhongJie.zuFang();
}
/*
打印结果:
前置增强
房东出租房子
后置增强
*/
动态代理
- 痛点
- 静态代理需要手动编写代理类, 比如上个例子中的ZhongJie类.
- 静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类, **每个接口都需要自己的代理类**.
- 静态代理需要手动编写代理类, 比如上个例子中的ZhongJie类.
- 解决思路
在上面的示例中**,一个代理只能代理一种类型,而且是在编译期就已经确定被代理的对象。
而动态代理是在运行时**,通过反射机制实现动态代理,并且能够代理各种类型的对象
使用InvocationHandler接口
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
Proxy类 ```java //用于获取代理类 //CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
3. 实现
目前动态代理可以提供两种,分为
- JDK 原生动态代理
- 基于接口实现
- 当目标类有接口的时候才会使用JDK动态代理,其实是因为JDK动态代理无法代理一个没有接口的类,因为JDK动态代理是利用反射机制生成一个实现代理接口的匿名类
![](https://cdn.nlark.com/yuque/0/2021/webp/8429887/1610976255297-e2844563-5896-4616-a624-efe3f51ba990.webp#align=left&display=inline&height=280&margin=%5Bobject%20Object%5D&originHeight=280&originWidth=455&size=0&status=done&style=none&width=455)
```java
public interface Target {
public String execute();
}
========================
public class TargetImpl implements Target {
@Override
public String execute() {
System.out.println("TargetImpl execute!");
return "execute";
}
}
=======================
public class DynamicProxyHandler implements InvocationHandler{
private Target target;
public DynamicProxyHandler(Target target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("========before==========");
Object result = method.invoke(target,args);
System.out.println("========after===========");
return result;
}
}
============================
public class DynamicProxyTest {
public static void main(String[] args) {
Target target = new TargetImpl();
DynamicProxyHandler handler = new DynamicProxyHandler(target);
Target proxySubject = (Target) Proxy.newProxyInstance(
TargetImpl.class.getClassLoader(),
TargetImpl.class.getInterfaces(),
handler);
// 通过原来的方法名调用
String result = proxySubject.execute(); // 返回被代理对象的方法返回值
System.out.println(result);
}
}
结果:
========before==========
TargetImpl execute!
========after===========
execute
- CGLIB动态代理
- 基于继承当前类的子类实现的
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,并且覆盖其中的方法。
Spring中默认使用的是JDK动态代理,除非目标类没有实现接口,才会转为CGLIB代理,如果想要强行使用CGLIB代理免责需要在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true" />
,
然而在SpringBoot中,从2.0开始就默认使用CGLIB代理。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用(当方法为final修饰时 无法实现代理),顺势织入横切逻辑。
- 目标类 是一个类而不是接口了 ```java package com.test.cglib;
public class Target {
public String execute() {
String message = "-----------test------------";
System.out.println(message);
return message;
}
}
2. 代理类
代理对象的生成过程由Enhancer类实现,大概步骤如下:<br /> 1、生成代理类Class的二进制字节码;<br /> 2、通过Class.forName加载二进制字节码,生成Class对象;<br /> 3、通过反射机制获取实例构造,并初始化代理类对象。
```java
package com.test.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(">>>>MethodInterceptor start...");
Object result = proxy.invokeSuper(obj,args);
System.out.println(">>>>MethodInterceptor ending...");
return "result";
}
}
- 测试类 ```java package com.test.cglib;
import net.sf.cglib.proxy.Enhancer;
public class CglibTest {
public static void main(String ... args) {
System.out.println("***************");
Target target = new Target();
CglibTest test = new CglibTest();
Target proxyTarget = (Target) test.createProxy(Target.class);
String res = proxyTarget.execute();
System.out.println(res);
}
/**
代理对象的生成过程由Enhancer类实现,大概步骤如下:
1、生成代理类Class的二进制字节码;
2、通过Class.forName加载二进制字节码,生成Class对象;
3、通过反射机制获取实例构造,并初始化代理类对象。
作者:冬天里的懒喵 链接:https://www.jianshu.com/p/37d0ac9233b9 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 **/
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MyMethodInterceptor());
return enhancer.create();
}
} ```