一文详解:血虐了无数面试者的Spring-Aop原来这么简单,jvm优化面试题

public T getBean(String beanName) {
return (T) beanCache.get(beanName);
}

/**

  • 获取对应类的接口
  • @param classes 要获取的类对象
  • @return 该类对象的接口
    */
    private Class<?>[] getInterfaces(Class classes) {
    return classes.getInterfaces();
    }

/**

  • 内部是否存在需要被拦截的方法对象
  • @param targetClass 目标类对象
  • @return 返回是否需要代理
    */
    private boolean hasProxy(Class targetClass) {
    //获取所有的方法
    Method[] declaredMethods = targetClass.getDeclaredMethods();
    for (Method method : Arrays.asList(declaredMethods)) {
    //获取方法上的注解
    Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
    //当方法存在注释的时候 开始判断是否方法需要被代理
    if (declaredAnnotations != null && declaredAnnotations.length > 0) {
    for (Annotation annotation : Arrays.asList(declaredAnnotations)) {
    //如果解析规则存在这个注解就返回true
    if (proxyRule.containsKey(annotation.annotationType().getName())) {
    return true;
    }
    }
    }
    }
    return false;
    }

/**

  • 解析切面类
    */
    private void parseAspectjClass() throws IllegalAccessException, InstantiationException {
    for (Class aClass : aspectjClassCache) {
    //获取切面的所有方法
    Method[] declaredMethods = aClass.getDeclaredMethods();
    for (Method method : Arrays.asList(declaredMethods)) {
    //寻找携带MyAround注解的切面方法
    MyAround myAroundAnntation = method.getAnnotation(MyAround.class);
    if (myAroundAnntation != null) {
    //拿到切点标志 也就是未来切点的方法
    Class<? extends Annotation> targetClassAnnotation
    = myAroundAnntation.targetClass();
    //实例化切面
    Object proxyTarget = aClass.newInstance();
    //创建调用链实体
    ProxyChain proxyChain = new ProxyChain(method, proxyTarget);
    //构建对应规则的调用链
    String classAnnotationName = targetClassAnnotation.getName();
    if (proxyRule.containsKey(classAnnotationName)) {
    proxyRule.get(classAnnotationName).add(proxyChain);
    } else {
    List proxyChains = new ArrayList<>();
    proxyChains.add(proxyChain);
    proxyRule.put(classAnnotationName, proxyChains);
    }
    }
    }
    }
    }

}

具体的逻辑介绍,看文中注释,此时我们的AOP就开发完了,赶紧,去测一下!

6.开发一个通知注解

这个注解是为了标识那些方法需要被拦截的!

package simulation.aop.system;

import java.lang.annotation.*;

/**

  • 模拟AOP通过注解方式添加拦截器
  • @author huangfu
    */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAopAnnotation {
    }

7.开发一个切面

package simulation.aop.my;

import simulation.aop.system.MyAopAnnotation;
import simulation.aop.system.MyAround;
import simulation.aop.system.MyProceedingJoinPoint;

import java.lang.reflect.InvocationTargetException;

/**

  • 定义一个切面
  • @author huangfu
    */
    public class MyAspect {

/**

  • 拦截所有方法上携带 MyAopAnnotation 注解的方法
  • @param joinPoint
  • @return
  • @throws InvocationTargetException
  • @throws IllegalAccessException
    */
    @MyAround(targetClass = MyAopAnnotation.class)
    public Object testAspect(MyProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
    long startTime = System.currentTimeMillis();
    //方法放行
    Object proceed = joinPoint.proceed(joinPoint.getArgs());
    long endTime = System.currentTimeMillis();
    System.out.println(“总共用时:”+(endTime - startTime));
    return proceed;
    }

/**

  • 拦截所有方法上携带 MyAopAnnotation 注解的方法
  • @param joinPoint
  • @return
  • @throws InvocationTargetException
  • @throws IllegalAccessException
    */
    @MyAround(targetClass = MyAopAnnotation.class)
    public Object testAspect2(MyProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
    long startTime = System.currentTimeMillis();
    //方法放行
    Object proceed = joinPoint.proceed(joinPoint.getArgs());
    long endTime = System.currentTimeMillis();
    System.out.println(“总共用时:”+(endTime - startTime));
    return proceed;
    }
    }

8.开发一个接口和一个实现类

package simulation.aop.my;

/**

  • @author huangfu
    /
    public interface TestService {
    /
    *
  • 打印一句话 拦截方法
  • @param msg 返回信息
    /
    String print(String msg) throws InterruptedException;
    /
    *
  • 普通方法 不拦截
    **/
    void sendUser();
    }

package simulation.aop.my;

import simulation.aop.system.MyAopAnnotation;

/**

  • 实现类
  • @author huangfu
    */
    public class TestServiceImpl implements TestService {

@MyAopAnnotation
@Override
public String print(String msg) throws InterruptedException {
System.out.println(“我执行了,参数是:”+msg);
Thread.sleep(5000);
return msg;
}

@Override
public void sendUser() {
System.out.println(“----发送信息—”);
}
}

9.最终测试

package simulation.aop;

import simulation.aop.my.TestService;
import simulation.aop.system.BeanUtil;

public class TestMyProxy {

public static void main(String[] args) throws IllegalAccessException, InstantiationException, InterruptedException {
BeanUtil beanUtil = new BeanUtil();
beanUtil.initBean();
TestService testService = beanUtil.getBean(“testService”);
System.out.println(testService.print(“wqewqeqw”));
testService.sendUser();
}
}

10.结果

当然,肯定是成功的,但是写到这我慌了,为什么?现在就已经超过1w字了,源码部分还一个没动,天哪,我得去前面加个:不想看自己实现部分就跳过的提示!

三、Spring AOP源码学习

先看一张图,找到入口方法,我们好继续看源码:

第一列【万字长文,助你深度遨游Spring循环依赖源码实现!】说的很详细,我么从第二列开始说起:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
…忽略不必要代码…
if (mbd == null || !mbd.isSynthetic()) {
//调用这个bean 后置处理器的前置处理器 {@link BeanPostProcessor#postProcessBeforeInitialization()}
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//回调初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, “Invocation of init method failed”, ex);
}
if (mbd == null || !mbd.isSynthetic()) {
//回调bean后置处理器的后置方法 {@link BeanPostProcessor#postProcessAfterInitialization()}
//这里也是AOP完成装载的地方,这一步也就到达了一个类从正常的类变成bean的最后一步
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

return wrappedBean;
}

在调用初始化方法后,会回调BeanPostProcessorspostProcessAfterInitialization方法完成AOP的加载,我们进入到:applyBeanPostProcessorsAfterInitialization方法:

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
//AOP的后置处理器被
//{@link org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization} 拦截
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}

在这里,Spring会扫描所有的BeanPostProcessor实现类,然后调用全部的postProcessAfterInitialization方法,而AOP就是再一个叫做AbstractAutoProxyCreator的类处理的,我们进入到org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization方法:

/**

  • 如果bean被子类标识为要代理的bean,则使用配置的拦截器创建代理。
  • @see #getAdvicesAndAdvisorsForBean
    */
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
    //这里获取缓存的key然后从下面去取 什么时候缓存的呢?
    //还记得为了解决循环依赖而引进的三级缓存不,明明二级缓存就能够解决,但是偏偏使用了三级缓存,而且三级缓存还是使用了一个工厂
    //org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getEarlyBeanReference 没错这个方法再
    //使用工厂返回对应的代理对象的时候,
    //会调用org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.getEarlyBeanReference
    //缓存一份自己的对象,这里就直接获取了
    //这样再三级缓存进行返回了动态代理之后这里就不进行AOP的逻辑了 直接返回已经被三级缓存处理好额bean
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    //这里是判断之前存储的AOP代理类是不是和创建好的bean不一致,如果一致就证明这个bean就已经是代理类了不需要走后续的AOP逻辑了
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    //如果判断需要代理则执行AOP代理的包装逻辑
    return wrapIfNecessary(bean, beanName, cacheKey);
    }
    }
    return bean;
    }

当判断该类需要被代理了,就进入到wrapIfNecessary方法:

/**

  • 必要时包装给定的bean,即是否有资格被代理。
  • @param bean 原始bean实例
  • @param beanName 豆的名字
  • @param cacheKey 用于元数据访问的缓存键
  • @return 包装Bean的代理,或按原样封装Raw Bean实例
    */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    //如果已经处理过(targetSourcedBeans存放已经增强过的bean)
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    return bean;
    }
    ////advisedBeans的key为cacheKey,value为boolean类型,表示是否进行过代理
    //如果设置了不允许代理,就直接返回
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    return bean;
    }
    //如果是本身就是AOP类 比如加了 @Asptj的类等一些基础设置会跳过不做代理,同时会将该类标注为不允许代理
    //Advice、Pointcut、Advisor、AopInfrastructureBean
    //设置了跳过 但是这个我需要后续取看
    // TODO 不是这次看到主要代码 以后看
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
    }

//这里就是寻找这个bean的切点的 寻找对应的AOP代理
//获取当前对象所有适用的Advisor.找到所有切点是他的对应的@Aspect注解的类
//注意这里会返回该bean对应的所有的切面
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
//如果是允许代理的话
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//这一步是主要逻辑,创建一个代理对象 参数为:类的类对象 bean的名称 代理类的信息(位置,切点等信息) bean对象
Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
//如果查询出该类不允许被代理,将该bean 修改为不可代理!
this.advisedBeans.put(cacheKey, Boolean.FALSE);
//返回原始的Bean对象
return bean;
}

我们寻找到这个bean对应所有的切面方法,然后进入到createProxy方法却具体的创建代理对象:

/**

  • 为给定的bean创建一个AOP代理。
  • @param beanClass bean的类型
  • @param beanName bean的名字
  • @param specificInterceptors 一组拦截器信息
  • 特定于此bean(可以为空,但不能为null)
  • @param targetSource 代理的对象
  • 已经预先配置为访问Bean
  • @return Bean的AOP代理
  • @see #buildAdvisors
    */
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
    @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    //判断beanFactory的类型
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
    //提前暴露这个bean是一个代理的类 如何设置呢?
    //就是再该bean的bd下面设置一个代理信息
    AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    //创建一个代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    //设置初始化参数
    proxyFactory.copyFrom(this);

if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
//正常的代理逻辑 判断设置一些代理参数
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
//包装代理信息 切点信息包装
//这里是把所有的切面信息包装成了Advisor方便设置进工厂
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//向工厂设置代理切点信息
proxyFactory.addAdvisors(advisors);
//设置代理的目标类的包装类 嘿嘿嘿
proxyFactory.setTargetSource(targetSource);
//空方法 Spring没做实现 扩展点
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//真正代理逻辑 这里主要是获取一个真正代理 参数是类加载器
return proxyFactory.getProxy(getProxyClassLoader());
}

进入到getProxy方法:

/**

  • 根据此工厂中的设置创建一个新的代理。
  • 可以反复调用。如果我们添加了效果会有所不同 或删除的接口。可以添加和删除拦截器。

  • 使用给定的类加载器(如果需要创建代理)。

  • @param classLoader 类加载器以创建代理 (或{@code null}为低级代理工具的默认值)
  • @return 代理对象
    */
    public Object getProxy(@Nullable ClassLoader classLoader) {
    //createAopProxy返回使用的代理类型
    //getProxy使用返回的代理类型创建代理对象
    return createAopProxy().getProxy(classLoader);
    }

这里实际上就是我们上述图示上说的判断代理类型是jdk还是cglib的地方,首先我们分成两部分看:进入到createAopProxy()方法:

protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
//使用之前创建的工厂选取一个代理方式 究竟是jdk还是cglib
return getAopProxyFactory().createAopProxy(this);
}

不做太多解释,直接到createAopProxy(this)方法:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//三个条件
//1.设置了优化
//2.proxyTargetClass 前面设置的
//{@link org.springframework.aop.framework.ProxyProcessorSupport.evaluateProxyInterfaces} 设置的是否有接口 没有接口就为true

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。

我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。

不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-gtlr0v56-1712694042373)]

最后

很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。

我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。

不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-ESUbj6HB-1712694042373)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值