百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Springboot使用过程中总结的一些问题及技巧应用(一)

lipiwang 2024-10-31 15:24 16 浏览 0 评论

环境:springboot2.3.10


1 @Configuration注解

该注解有个proxyBeanMethods属性默认值为true。

当为true时:所有方法上带有@Bean注解的Bean会被CGLIB代理,在该类中或者是在外部类中都可以直接调用该方法,返回的都是共享的同一个Bean对象。

2 @Import注解

当注解上添加有@Import注解,该注解上配置的类如下:

Bash
@Import({RWImportSelector.class})
Bash
public class RWImportSelector implements ImportSelector, EnvironmentAware {

    private static final String RW_CONFIG_ENABLED = "rw.config.enabled" ;
    private static final Boolean RW_CONFIG_ENABLED_DEFAULE = Boolean.TRUE ;
    
    private Environment env ;
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributesMap = importingClassMetadata.getAnnotationAttributes(EnableRW.class.getName());
        AnnotationAttributes attrs = AnnotationAttributes.fromMap(attributesMap);
        System.out.println(attrs.get("value")) ;
        if (!env.getProperty(RW_CONFIG_ENABLED, Boolean.class, RW_CONFIG_ENABLED_DEFAULE)) {
            return new String[] {} ;
        }
        return new String[] {RWConfig.class.getName()} ;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment ;
    }

}
  • ImportSelector可以实现相应的*Aware接口。
  • selectImports返回的结果类中,在类上配置的@ConditionOn*注解同样会生效。 如下@ConditionalOnProperty(prefix = "rw.config", name = "enabled", havingValue = "true", matchIfMissing = false)
    public class RWConfig implements ApplicationContextAware, EnvironmentAware {
    }

3 自定义Advisor失效问题

现在有如下需求:类中的方法只要标有@DS注解的都进行拦截(代理)。

// 注解类
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DS {
}
// DAO接口
public interface CustomDAO {
  public void update() ;
}
// DAO接口实现类
@Component
public class CustomerDAOImpl implements CustomDAO { 
  @DS
  public void update() {
    System.out.println("更新数据...") ;
  } 
}

自定义Advisor(管理Advice和Pointcut)

@Component
public class CustomAdvisor implements PointcutAdvisor {

  @Override
  public Advice getAdvice() {
    return new MethodInterceptor() {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
          System.out.println("我被调用了...") ;
          return invocation.proceed() ;
        }
    } ;
  }

  @Override
  public boolean isPerInstance() {
    return true ;
  }

  @Override
  public Pointcut getPointcut() {
    return new Pointcut() {
        @Override
        public MethodMatcher getMethodMatcher() {
          return new MethodMatcher() {
            @Override
            public boolean matches(Method method, Class<?> targetClass, Object... args) {
              return false;
            }
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
              return method.isAnnotationPresent(DS.class) ;
            }
            @Override
            public boolean isRuntime() {
              return false ;
            }
          };
       }
       @Override
       public ClassFilter getClassFilter() {
         return ClassFilter.TRUE;
       }
    } ;
  }

}

开启AOP功能

@Configuration
@EnableAspectJAutoProxy
public class AnnoConfig {
}

测试

public class App {
  public static void main(String[] args) {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "d://cglib");  
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.pack.anno", "com.pack.dao", "com.pack.advisor") ;
    ctx.getBean(CustomDAO.class).update() ; 
    ctx.close();
  }
}

运行结果

class com.pack.dao.CustomerDAOImpl  update  false
更新数据...

只打印了update中的输出,我们定义的Advice并没有执行。

当我们需要代理的类实现了接口那么Spring默认会使用JDK的代理。那我们这里的CustomerDAOImpl会使用JDK的Proxy进行代理。而使用JDK的代理应用的InvocationHandler是:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
      // other code
      Object retVal;
      if (this.advised.exposeProxy) {
        // Make invocation available if necessary.
        oldProxy = AopContext.setCurrentProxy(proxy);
        setProxyContext = true;
      }
      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);
      // Get the interception chain for this method.
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      if (chain.isEmpty()) {
        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      } else {
        // We need to create a method invocation...
        MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        // Proceed to the joinpoint through the interceptor chain.
        retVal = invocation.proceed();
      }
      // other code
      return retVal;
    }
  }
}

关键是下面这行代码:

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

动态获取我们的拦截器,请注意此时我们的Method对象是谁?

接下来我们进入上面的那行关键代码

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
  MethodCacheKey cacheKey = new MethodCacheKey(method);
  List<Object> cached = this.methodCache.get(cacheKey);
  if (cached == null) {
    cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);
    this.methodCache.put(cacheKey, cached);
  }
  return cached;
}

继续进入getInterceptorsAndDynamicInterceptionAdvice方法,该方法通过名称知道获取通知Advice

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, @Nullable Class<?> targetClass) {
  AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
  Advisor[] advisors = config.getAdvisors();
  List<Object> interceptorList = new ArrayList<>(advisors.length);
  Class<?> actualClass = (targetClass != null ? targetClass :method.getDeclaringClass());
  Boolean hasIntroductions = null;
  for (Advisor advisor : advisors) {
    if (advisor instanceof PointcutAdvisor) {
      PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
      if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
        MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
        boolean match;
        if (mm instanceof IntroductionAwareMethodMatcher) {
          if (hasIntroductions == null) {
            hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
          }
          match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
        } else {
          match = mm.matches(method, actualClass);
        }
        if (match) {
          MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
          if (mm.isRuntime()) {
            for (MethodInterceptor interceptor : interceptors) {
              interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
            }
          } else {
            interceptorList.addAll(Arrays.asList(interceptors));
          }
        }
      }  
    } else if (advisor instanceof IntroductionAdvisor) {
      IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
      if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
        Interceptor[] interceptors = registry.getInterceptors(advisor);
        interceptorList.addAll(Arrays.asList(interceptors));
      }
    } else {
      Interceptor[] interceptors = registry.getInterceptors(advisor);
      interceptorList.addAll(Arrays.asList(interceptors));
    }
  }
  return interceptorList;
}

查看该方法中的advisors属性值

有我们定义的切面CustomAdvisor

继续向下执行

在这里你应该能够知道了Method对象指向的接口的update,而在接口的update方法是我们并没有添加@DS注解,所以这里不会匹配。最终这个方法会返回一个空集合。

接下来回到InvocationHandler中的invoke方法中继续执行下面的代码


if (chain.isEmpty()) {
  Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
  retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}

也就是直接就带我们的target目标方法了。

如何解决这问题呢?有2种方法

第一种:

在接口方法上添加@DS注解

第二种:

强制使用cglib代码,如下配置:

@EnableAspectJAutoProxy(proxyTargetClass = true)

proxyTargetClass属性值是如何与AnnotationAwareAspectJAutoProxyCreator关联的?

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}
@Override
public void registerBeanDefinitions(
  AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

  // 注册AnnotationAwareAspectJAutoProxyCreator Bean
  AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

  AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata,EnableAspectJAutoProxy.class);
  if (enableAspectJAutoProxy != null) {
    // 如果proxyTargetClass设置为true,那么强制修改AnnotationAwareAspectJAutoProxyCreator的proxyTargetClass 属性值
    if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
      AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
    }
    if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
      AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
    }
  }
}
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
  if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
    BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
    definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
  }
}

4 引介增强的应用

含义:假设某个类没有实现A接口,在不修改源码的情况下使其具有接口A的功能;是不是很强大。

示例:

一个普通的类没有实现任何的接口

@Component
public class Apple {
  public void color() {
    System.out.println("red color...") ;
  }
}

使上面的Apple具有下面接口的能力

public interface FruitDAO {
  public void eat() ;   
}

方法拦截器(引介拦截器)

public class CustomIntroductionInterceptor implements IntroductionInterceptor, FruitDAO {

  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
      System.out.println("我是Introduction增强...") ;
      return invocation.getMethod().invoke(this, invocation.getArguments()) ;
    }
    return invocation.proceed() ;
  }

  @Override
  public boolean implementsInterface(Class<?> intf) {
    return FruitDAO.class.isAssignableFrom(intf) ;
  }

  @Override
  public void eat() {
    System.out.println("这是一个合格的水果") ;
  }

}

当前拦截器实现了FruitDAO接口,意味着当某个类不具有FruitDAO能力的时候,那么当它具有其能力的时候就是在当前拦截器中实现的方法。(也可理解为增强的部分)

implementsInterface 方法判断当前执行的方法所在的类是否实现了指定的接口。

创建代理的处理器

@Component
public class IntroductionAopProxy extends AbstractAutoProxyCreator {

  private static final long serialVersionUID = 1L;

  @Override
  protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
    return new Object[] {new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), FruitDAO.class)} ;
  }
    
  @Override
  protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    return !Apple.class.isAssignableFrom(beanClass) ;
  }

}

这里只对Apple进行处理,其它的都跳过。

未完待续

公众:Springboot实战案例锦集

SpringCloud Nacos 服务提供者

SpringCloud Nacos 服务动态配置

Spring Cloud Sentinel整合Feign

Spring Cloud Gateway应用详解1之谓词

Spring Cloud Nacos 开启权限验证

Spring Cloud全链路追踪SkyWalking及整合Elasticsearch

Spring Cloud 微服务日志收集管理Elastic Stack完整详细版

Spring Cloud Sentinel 热点参数限流

SpringCloud zuul 动态网关配置

SpringCloud Hystrix实现资源隔离应用

Spring Cloud Gateway应用详解2内置过滤器

Spring Cloud链路追踪zipkin及整合Elasticsearch存储

SpringCloud Gateway 应用Hystrix 限流功能 自定义Filter详解

SpringCloud Sentinel 整合 zuul

SpringCloud Zookeeper配置中心详解

Spring Security 自定义登录成功后的逻辑

Spring Security记住我功能实现及源码分析

相关推荐

《每日电讯报》研发数字工具,教你更有效率地报道新闻

为鼓励新闻编辑部持续创新,《每日电讯报》正在尝试有战略地研发数字工具。网站的数字媒体主任马尔科姆o科尔斯(MalcolmColes)表示,《每日电讯报》正试图去“创建一些可持续资产”,以便于让记者们...

html5学得好不好,看掌握多少标签

html5你了解了多少?如果你还是入门阶段的话,或者还是一知半解的话,那么我们专门为你们收集的html5常用的标签大全对你就很有帮助了,你需要了解了html5有哪些标签你才能够更好的。驾驭html5...

前端分享-少年了解过iframe么(我想了解少年)

iframe就像是HTML的「内嵌画布」,允许在页面中加载独立网页,如同在画布上叠加另一幅动态画卷。核心特性包括:独立上下文:每个iframe都拥有独立的DOM/CSS/JS环境(类似浏...

做SEO要知道什么是AJAX(人能看到但搜索引擎看不到的内容)

一个明显的,人能看到但搜索引擎不能看到的内容是AJAX。那么什么是AJAX呢?其实,了解过的基本上也都清楚,AJAX不是新的编程语言,而是一种使用现有标准的新方法。AJAX最大的优点是在不重新加...

介绍最前沿的人工智能创新,‘无反向传播’神经网络训练方法?

图像由GoogleImageFX生成前言:本文整理自NoProp原始论文与实践代码,并结合多个公开实现细节进行了全流程复现。对神经网络训练机制的探索仍在不断演进,如果你也在研究反向传播之...

说说我们对HTML6的期许(对html的看法)

HTML5概述HTML5是HTML语言最受欢迎的版本之一,它支持音频和视频、离线存储、移动端、和标签属性等等。还提供了article,section,header这样的标签来帮助开发者更好...

浏览器中在线预览pdf文件,pdf.mjs插件实现web预览pdf

背景:本来只是淘宝上卖卖袜子,想着扩展一下业务,准备做同名“来家居”海外袜子馆外贸项目,碰到pdf在线预览的需求,就找了pdf.js插件进行实践后把此方法记录下来,可以通过多种方法来实现,每种方法都有...

SVG 在前端的7种使用方法,你还知道哪几种?

本文简介点赞+关注+收藏=学会了技术一直在演变,在网页中使用SVG的方法也层出不穷。每个时期都有对应的最优解。所以我打算把我知道的7种SVG的使用方法列举出来,有备无患~如果你还...

HTML5常用标签大全(html5em标签)

HTML前端开发最终取决于掌握标签的多少HTML大概有七八百个标签楼主这里给大家总结了下HTML常用标签标签描述<!--...-->定义注释。<!DOCTYPE>定义文档类型...

&quot;伪君子Snoop Dogg!&quot;... WHAT?| MetroDaily 24/7

TUE.01-新作品-虽说年纪大了会有点糊涂,但是最近SnoopDogg的这波操作实在是让粉丝们有点迷,甚至有人表示没想到他是这样的"伪君子"......而这一切都源于他近日在IG上Po出的一...

史努比snoopy卡通手机壁纸屏保(史努比壁纸无水印)

...

莎夏·班克斯盼望表哥Snoop Dogg为其作出场曲

NXT女子冠军莎夏·班克斯(SashaBanks)近日接受了迈阿密先驱报采访,访谈纪要如下:关于她出众的形象:“我一向喜欢与众不同。为了能让人眼前一亮,我的装束总是非常前卫、非常抢眼,这样才能让观众...

喜欢Snoop!全球第一间「史努比博物馆」海外分馆在东京!

1950年起,由美國漫畫家CharlesM.Schulz創作的作品《Snoopy》史努比,其鮮明的可愛角色與幽默的劇情內容,至今仍成為許多大朋友與小朋友心中的最愛。為了紀念作者所設立的全球首...

Vetements 推出 Snoop Dogg 肖像「天价」T-Shirt

Vetements的CEOGuramGvasalia早前才透露品牌经营策略的秘密–Vetements如何成为人人热议的话题品牌。但似乎他仍有更多需要解释的东西–这个法国奢侈品牌最新...

狗爷Snoop Dogg的《I Wanna Thank Me》巡回演唱会旧金山站

西海岸匪帮说唱歌手SnoopDogg在《IWannaThankMe》巡回演唱会旧金山站表演(图片来自ICphoto)西海岸匪帮说唱歌手SnoopDogg(图片来自ICphoto)西海...

取消回复欢迎 发表评论: