感受:
其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。
特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。
也祝愿各位同学,都能找到自己心动的offer。
分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档
package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;
import com.zwx.design.pattern.proxy.Travel;
import javafx.beans.binding.ObjectExpression;
import javax.sound.midi.Track;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkTravelAgency implements InvocationHandler {
private Object target;//被代理对象
public Object getInstance(Object target){//动态获取代理对象
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj = method.invoke(this.target,args);//调用代理对象方法
after();
return obj;
}
private void before() {
System.out.println(“付定金”);
}
private void after() {
System.out.println(“付尾款”);
}
}
3、创建测试类:
package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;
import com.zwx.design.pattern.proxy.Travel;
public class TestJdkProxy {
public static void main(String[] args){
Travel travel = (Travel) new JdkTravelAgency().getInstance(new JdkTravelPerson());
travel.buyTrainTickey();
}
}
运行结果:
付定金
北京到上海
早上9:00出发
付尾款
首先来看一下上面示例中的类图关系:
动态代理最核心的就是如何生成代理类,所以最核心的逻辑是在JdkTravelAgency 中的getInstance方法调用的Proxy.newProxyInstance方法。
我们先把上面示例中的代理类通过ProxyGenerator类提供的方法generateProxyClass,生成出来后反编译看一下代理类究竟是什么样子的一个类:
package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;
import com.zwx.design.pattern.proxy.Travel;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestJdkProxy {
public static void main(String[] args){
try{
byte[] bytes = ProxyGenerator.generateProxyClass(“$proxy0”,new Class[]{Travel.class});//生成代理类
FileOutputStream out = new FileOutputStream(“G:\$proxy0.class”);
out.write(bytes);
out.flush();
out.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
然后将$proxy0反编译(反编译工具用的是jad,jd-gui反编译出来的有点小问题):
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
import com.zwx.design.pattern.proxy.Travel;
import java.lang.reflect.*;
public final class $proxy0 extends Proxy
implements Travel
{
public $proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void buyTrainTickey()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
static
{
try
{
m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, new Class[] {
Class.forName(“java.lang.Object”)
});
m2 = Class.forName(“java.lang.Object”).getMethod(“toString”, new Class[0]);
m3 = Class.forName(“com.zwx.design.pattern.proxy.Travel”).getMethod(“buyTrainTickey”, new Class[0]);
m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”, new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
我们可以看到,生成的代理类 $proxy0(原类为TravelPerson),自动帮我们继承了Proxy类,然后定义了m0-m4中的4个方法,其中,m0,m1,m4这三个方法是Object类提供的,而m3方法是我们自己定义的接口中的方法。
我们看到buyTrainTickey方法中,调用了super.h.invoke(this, m3, null)方法。
h是什么呢?h实际上是其父类Proxy中的持有的InvocationHandler对象:
而Proxy里面的h就是我们传进去的JdkTravelAgency对象,所以最终就是调用了我们自己的方法。
Proxy.newProxyInstance
@CallerSensitive
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() {
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);
}
}
这个方法就是三步:
-
通过传入的接口,调用getProxyClass0方法获得当前代理类的Class文件
-
获得代理类的构造器
-
通过构造器,初始化当前代理类对象
而这三步当中,最重要的就是第1步,通过上面的反编译文件我们知道,代理类已经被重写了,那么代理类是如何被重写的?
getProxyClass0
getProxyClass0方法获得代理类的字节码文件,会先去WeakCache缓存中获取,如果获取不到,则通过Proxy类中的内部工厂类ProxyClassFactory获取:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = “$Proxy”;//代理类前缀
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();//通过原子类实现代理类计数
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
- 验证类加载器是否可以将接口名称解析为相同的Class对象
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
- 校验一下加载出来的Class对象是否是一个接口
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
- 校验接口是否重复
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null;// 代理类的包名
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//访问标志
/*
-
记录非public类型接口的包,所以代理类将会被定义在同一个包下
-
校验所有的非public代理接口是否在同一个包下
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {//如果不是public类型
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf(‘.’);
String pkg = ((n == -1) ? “” : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;//第一次循环
} else if (!pkg.equals(proxyPkg)) {//如果不在一个包下会报错
throw new IllegalArgumentException(
“non-public interfaces from different packages”);
}
}
}
if (proxyPkg == null) {//默认包名
// public static final String PROXY_PACKAGE = “com.sun.proxy”;
proxyPkg = ReflectUtil.PROXY_PACKAGE + “.”;
}
/*
- 生成代理类名:包名+$proxy+num
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
- 生成代理类的byte数组
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//将byte数组通过类加载器加载成为Class字节码文件返回
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
通过上面的源码,可以得到JDK动态代理分为以下几步:
-
拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
-
通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
-
动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
-
编译新生成的 Java 代码.class。
-
将新生成的Class文件重新加载到 JVM 中运行。
所以说JDK动态代理的核心是通过重写被代理对象所实现的接口中的方法来重新生成代理类来实现的,那么假如被代理对象没有实现接口呢?那么这时候就需要CGLIB动态代理了。
======================================================================
JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。
注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。
1、引入依赖:
cglib
cglib-nodep
2.2
2、创建一个被代理对象(不需要实现接口):
package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;
public class CglibTravelPerson{
public void buyTrainTickey() {
System.out.println(“cglib:北京到上海”);
System.out.println(“cglib:早上9:00出发”);
}
}
3、建立一个代理对象实现MethodInterceptor接口:
package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTravelAgency implements MethodInterceptor {
public Object getInstance(Class<?> clazz){
Enhancer enhancer = new Enhancer();//相当于JDK动态代理中的Proxy类
enhancer.setSuperclass(clazz);//设置为即将生成的代理类的父类
enhancer.setCallback(this);//设置回调对象
return enhancer.create();//相当于JDK动态代理的Proxy.newProxyInstance方法,生成新的字节码文件,并加载到JVM中
}
/**
1200页Java架构面试专题及答案
小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞
百度、字节、美团等大厂常见面试题
terceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTravelAgency implements MethodInterceptor {
public Object getInstance(Class<?> clazz){
Enhancer enhancer = new Enhancer();//相当于JDK动态代理中的Proxy类
enhancer.setSuperclass(clazz);//设置为即将生成的代理类的父类
enhancer.setCallback(this);//设置回调对象
return enhancer.create();//相当于JDK动态代理的Proxy.newProxyInstance方法,生成新的字节码文件,并加载到JVM中
}
/**
1200页Java架构面试专题及答案
小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞
[外链图片转存中…(img-CzwQXmk4-1715488857727)]
[外链图片转存中…(img-23N9oUxQ-1715488857728)]
百度、字节、美团等大厂常见面试题
[外链图片转存中…(img-8ShTmyYM-1715488857728)]