当前位置: 首页 > news >正文

网站开发是哪个职位微信公众平台使用方法

网站开发是哪个职位,微信公众平台使用方法,门户网站源码入驻,开发app和网站建设那个好些转载自 接口方法上的注解无法被Aspect声明的切面拦截的原因分析 前言 在Spring中使用MyBatis的Mapper接口自动生成时#xff0c;用一个自定义的注解标记在Mapper接口的方法中#xff0c;再利用Aspect定义一个切面#xff0c;拦截这个注解以记录日志或者执行时长。但是惊奇…转载自  接口方法上的注解无法被Aspect声明的切面拦截的原因分析 前言 在Spring中使用MyBatis的Mapper接口自动生成时用一个自定义的注解标记在Mapper接口的方法中再利用Aspect定义一个切面拦截这个注解以记录日志或者执行时长。但是惊奇的发现这样做之后在Spring Boot 1.X(Spring Framework 4.x)中并不能生效而在Spring Boot 2.X(Spring Framework 5.X)中却能生效。 这究竟是为什么呢Spring做了哪些更新产生了这样的变化此文将带领你探索这个秘密。 案例 核心代码 SpringBootApplicationpublic class Starter {public static void main(String[] args) {SpringApplication.run(DynamicApplication.class, args);}}Servicepublic class DemoService {AutowiredDemoMapper demoMapper;public ListMapString, Object selectAll() {return demoMapper.selectAll();}}/*** mapper类*/Mapperpublic interface DemoMapper {Select(SELECT * FROM demo)DemoListMapString, Object selectAll();}/*** 切入的注解*/Target({ElementType.METHOD})Retention(RetentionPolicy.RUNTIME)Documentedpublic interface Demo {String value() default ;}/*** aspect切面用于测试是否成功切入*/AspectOrder(-10)Componentpublic class DemoAspect {Before(annotation(demo))public void beforeDemo(JoinPoint point, Demo demo) {System.out.println(before demo);}AfterDemo(annotation(demo))public void afterDemo(JoinPoint point, Demo demo) {System.out.println(after demo);}} 测试类 RunWith(SpringRunner.class) SpringBootTest(classes Starter.class)public class BaseTest {AutowiredDemoService demoService;Testpublic void testDemo() {demoService.selectAll();} } 在Spring Boot 1.X中Aspect里的两个println都没有正常打印而在Spring Boot 2.X中都打印了出来。 调试研究 已知Aspect注解声明的拦截器会自动切入符合其拦截条件的Bean。这个功能是通过EnableAspectJAutoProxy注解来启用和配置的(默认是启用的通过AopAutoConfiguration)由EnableAspectJAutoProxy中的Import(AspectJAutoProxyRegistrar.class)可知Aspect相关注解自动切入的依赖是AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor。在这个类的postProcessAfterInitialization方法中打上条件断点beanName.equals(“demoMapper”) public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean ! null) {// 缓存中尝试获取没有则尝试包装Object cacheKey getCacheKey(bean.getClass(), beanName);if (!this.earlyProxyReferences.contains(cacheKey)) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;} 在wrapIfNecessary方法中有自动包装Proxy的逻辑 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果是声明的需要原始Bean则直接返回if (beanName ! null this.targetSourcedBeans.contains(beanName)) {return bean;}// 如果不需要代理则直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 如果是Proxy的基础组件如Advice、Pointcut、Advisor、AopInfrastructureBean则跳过if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.// 根据相关条件查找interceptor包括Aspect生成的相关Interceptor。// 这里是问题的关键点Spring Boot 1.X中这里返回为空而Spring Boot 2.X中则不是空Object[] specificInterceptors getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors ! DO_NOT_PROXY) {// 返回不是null则需要代理this.advisedBeans.put(cacheKey, Boolean.TRUE);// 放入缓存Object proxy createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 自动生成代理实例this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;} 调试发现Spring Boot 1.X中specificInterceptors返回为空而Spring Boot 2.X中则不是空那么这里就是问题的核心点了查看源码 protected Object[] getAdvicesAndAdvisorsForBean(Class? beanClass, String beanName, TargetSource targetSource) {ListAdvisor advisors findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {// 如果是空则不代理return DO_NOT_PROXY;}return advisors.toArray();}protected ListAdvisor findEligibleAdvisors(Class? beanClass, String beanName) {// 找到当前BeanFactory中的AdvisorListAdvisor candidateAdvisors findCandidateAdvisors();// 遍历Advisor根据Advisor中的PointCut判断返回所有合适的AdvisorListAdvisor eligibleAdvisors findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 扩展advisor列表这里会默认加入一个ExposeInvocationInterceptor用于暴露动态代理对象之前文章有解释过extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 根据Order或者接口Ordered排序eligibleAdvisors sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}protected ListAdvisor findAdvisorsThatCanApply(ListAdvisor candidateAdvisors, Class? beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {// 真正的查找方法  return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}} 这里的核心问题在于AopUtils.findAdvisorsThatCanApply方法这里的返回在两个版本是不一样的由于这里代码过多就不贴上来了说明下核心问题代码是这段 // AopProxyUtils.javapublic static ListAdvisor findAdvisorsThatCanApply(ListAdvisor candidateAdvisors, Class? clazz) {// ... 省略for (Advisor candidate : candidateAdvisors) {if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}// ... 省略}public static boolean canApply(Advisor advisor, Class? targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {// 对于Aspect的切面是这段代码在生效PointcutAdvisor pca (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesnt have a pointcut so we assume it applies.return true;}} 基本定位了问题点看下最终调用的canApply方法Spring Boot 1.X与2.X这里的代码是不一样的 Spring Boot 1.X中源码即Spring AOP 4.X中源码 /*** targetClass是com.sun.proxy.$Proxy??即JDK动态代理生成的类* hasIntroductions是false先不管*/public static boolean canApply(Pointcut pc, Class? targetClass, boolean hasIntroductions) {Assert.notNull(pc, Pointcut must not be null);// 先判断class这里两个版本都为trueif (!pc.getClassFilter().matches(targetClass)) {return false;}MethodMatcher methodMatcher pc.getMethodMatcher();// 如果method是固定true即拦截所有method则返回true。这里当然为falseif (methodMatcher MethodMatcher.TRUE) {// No need to iterate the methods if were matching any method anyway...return true;}// 特殊类型做下转换Aspect生成的属于这个类型IntroductionAwareMethodMatcher introductionAwareMethodMatcher null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher (IntroductionAwareMethodMatcher) methodMatcher;}// 取到目标class的所有接口SetClass? classes new LinkedHashSetClass?(ClassUtils.getAllInterfacesForClassAsSet(targetClass));// 再把目标calss加入遍历列表classes.add(targetClass);for (Class? clazz : classes) {Method[] methods ReflectionUtils.getAllDeclaredMethods(clazz);// 遍历每个类的每个方法尝试判断是否matchfor (Method method : methods) {if ((introductionAwareMethodMatcher ! null introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||methodMatcher.matches(method, targetClass)) {return true;}}}return false;} Spring Boot 2.X中源码即Spring AOP 5.X中源码 public static boolean canApply(Pointcut pc, Class? targetClass, boolean hasIntroductions) {Assert.notNull(pc, Pointcut must not be null);if (!pc.getClassFilter().matches(targetClass)) {return false;}MethodMatcher methodMatcher pc.getMethodMatcher();if (methodMatcher MethodMatcher.TRUE) {// No need to iterate the methods if were matching any method anyway...return true;}IntroductionAwareMethodMatcher introductionAwareMethodMatcher null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher (IntroductionAwareMethodMatcher) methodMatcher;}SetClass? classes new LinkedHashSet();// 这里与1.X版本不同使用Jdk动态代理Proxy先判断是否是Proxy如果不是则加入用户Class即被动态代理的class以便查找真正的Class中是否符合判断条件// 因为动态代理可能只把被代理类的方法实现了被代理类的注解之类的没有复制到生成的子类中故要使用原始的类进行判断// JDK动态代理一样不会为动态代理生成类上加入接口的注解// 如果是JDK动态代理不需要把动态代理生成的类方法遍历列表中因为实现的接口中真实的被代理接口。if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));for (Class? clazz : classes) {Method[] methods ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {// 比1.X版本少遍历了Proxy生成的动态代理类但是遍历内容都包含了真实的接口其实是相同的为什么结果不一样呢if ((introductionAwareMethodMatcher ! null introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||methodMatcher.matches(method, targetClass)) {return true;}}}return false;} 调试信息图 上面的代码执行结果不同但是区别只是少个动态代理生成的类进行遍历为什么少一个遍历内容结果却是true呢肯定是introductionAwareMethodMatcher或者methodMatcher的逻辑有改动其中methodMatcher和introductionAwareMethodMatcher是同一个对象两个方法逻辑相同。看代码 /** AspectJExpressionPointcut.java* method是上面接口中遍历的方法targetClass是目标class即生成的动态代理class*/public boolean matches(Method method, Nullable Class? targetClass, boolean beanHasIntroductions) {obtainPointcutExpression();Method targetMethod AopUtils.getMostSpecificMethod(method, targetClass);ShadowMatch shadowMatch getShadowMatch(targetMethod, method);// Special handling for this, target, this, target, annotation// in Spring - we can optimize since we know we have exactly this class,// and there will never be matching subclass at runtime.if (shadowMatch.alwaysMatches()) {return true;}else if (shadowMatch.neverMatches()) {return false;}else {// the maybe caseif (beanHasIntroductions) {return true;}// A match test returned maybe - if there are any subtype sensitive variables// involved in the test (this, target, at_this, at_target, at_annotation) then// we say this is not a match as in Spring there will never be a different// runtime subtype.RuntimeTestWalker walker getRuntimeTestWalker(shadowMatch);return (!walker.testsSubtypeSensitiveVars() ||(targetClass ! null walker.testTargetInstanceOfResidue(targetClass)));}} 这段代码在Spring Boot 1.X和2.X中基本是相同的但是在AopUtils.getMostSpecificMethod(method, targetClass);这一句的执行结果上两者是不同的1.X返回的是动态代理生成的Class中重写的接口中的方法2.X返回的是原始接口中的方法。 而在动态代理生成的Class中重写的接口方法里是不会包含接口中的注解信息的所以Aspect中条件使用注解在这里是拿不到匹配信息的所以返回了false。 而在2.X中因为返回的是原始接口的方法故可以成功匹配。 问题就在于AopUtils.getMostSpecificMethod(method, targetClass)的逻辑 // 1.Xpublic static Method getMostSpecificMethod(Method method, Class? targetClass) {// 这里返回了targetClass上的重写的method方法。Method resolvedMethod ClassUtils.getMostSpecificMethod(method, targetClass);// If we are dealing with method with generic parameters, find the original method.return BridgeMethodResolver.findBridgedMethod(resolvedMethod);}// 2.Xpublic static Method getMostSpecificMethod(Method method, Nullable Class? targetClass) {// 比1.X多了个逻辑判断如果是JDK的Proxy则specificTargetClass为null否则取被代理的Class。Class? specificTargetClass (targetClass ! null !Proxy.isProxyClass(targetClass) ?ClassUtils.getUserClass(targetClass) : null);// 如果specificTargetClass为空直接返回原始method。// 如果不为空返回被代理的Class上的方法Method resolvedMethod ClassUtils.getMostSpecificMethod(method, specificTargetClass);// If we are dealing with method with generic parameters, find the original method.// 获取真实桥接的方法泛型支持return BridgeMethodResolver.findBridgedMethod(resolvedMethod);} 至此原因已经完全明了Spring在AOP的5.X版本修复了这个问题。 影响范围 原因已经查明那么根据原因我们推算一下影响范围 Bean是接口动态代理对象时且该动态代理对象不是Spring体系生成的接口中的切面注解无法被拦截 Bean是CGLIB动态代理对象时该动态代理对象不是Spring体系生成的原始类方法上的切面注解无法被拦截。 可能也影响基于类名和方法名的拦截体系因为生成的动态代理类路径和类名是不同的。 如果是Spring体系生成的之前拿到的都是真实类或者接口只有在生成动态代理后才是新的类。所以在创建动态代理时获取的是真实的类。 接口动态代理多见于ORM框架的Mapper、RPC框架的SPI等所以在这两种情况下使用注解要尤为小心。 有些同学比较关心Cacheable注解放在Mapper中是否生效。答案是生效因为Cacheable注解中使用的不是Aspect的PointCut而是CacheOperationSourcePointcut其中虽然也使用了getMostSpecificMethod来获取method但是最终其实又从原始方法上尝试获取了注解 // AbstractFallbackCacheOperationSource.computeCacheOperationsif (specificMethod ! method) {//  Fallback is to look at the original methodopDef findCacheOperations(method);if (opDef ! null) {return opDef;}// Last fallback is the class of the original method.opDef findCacheOperations(method.getDeclaringClass());if (opDef ! null ClassUtils.isUserLevelMethod(method)) {return opDef;}} 看似不受影响其实是做了兼容。 可以参考后面的内容有提到Spring相关的issue 解决方案 如何解决这个问题呢答案是在Spring Boot 1.X中没有解决方案。。因为这个类太基础了除非切换版本。 使用其他Aspect表达式也可以解决此问题使用注解方式在1.X版本是无解的。 表达式参考如下链接 Spring 之AOP AspectJ切入点语法详解最全面、最详细。 https://blog.csdn.net/zhengchao1991/article/details/53391244 Spring Aspect的Execution表达式 https://blog.csdn.net/lang_niu/article/details/51559994 本来以为在注解Demo中加入Inherited可解决的结果发现不行因为这个Inherited只在类注解有效在接口中或者方法上都是不能被子类或者实现类继承的看这个Inherited上面的注释 /*** Indicates that an annotation type is automatically inherited.  If* an Inherited meta-annotation is present on an annotation type* declaration, and the user queries the annotation type on a class* declaration, and the class declaration has no annotation for this type,* then the classs superclass will automatically be queried for the* annotation type.  This process will be repeated until an annotation for this* type is found, or the top of the class hierarchy (Object)* is reached.  If no superclass has an annotation for this type, then* the query will indicate that the class in question has no such annotation.** pNote that this meta-annotation type has no effect if the annotated* type is used to annotate anything other than a class.  Note also* that this meta-annotation only causes annotations to be inherited* from superclasses; annotations on implemented interfaces have no* effect.* 上面这句话说明了只在父类上的注解可被继承接口上的都是无效的** author  Joshua Bloch* since 1.5*/DocumentedRetention(RetentionPolicy.RUNTIME)Target(ElementType.ANNOTATION_TYPE)public interface Inherited {} 扩展阅读 问题及可能的影响范围已经详细分析完了下面我们好奇一下这个核心问题类AopUtils.java的提交记录中作者有写什么吗 AopUtils.java类GitHub页面 https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java 查看这个类的历史记录注意Commits on Apr 3, 2018这个日期的提交其中提到 Consistent treatment of proxy classes and interfaces for introspection Issue: SPR-16675 Issue: SPR-16677 针对proxy classes做了内省配置相关issue是SPR-16677我们看下这个issue。 Spring Framework/SPR-16677 https://jira.spring.io/browse/SPR-16677 这个issue详细描述了这次提交的原因及目的。 读者感兴趣的话可以详细的阅读。 注意AopUtils.java的最新提交又做了一些优化可以研究一下。 扩展知识 上面的示例代码依赖于数据库现做一个模拟Mapper类的改进可以直接无任何依赖的重现该问题 已知Mybatis的Mapper接口是通过JDK动态代理生成的逻辑而Mapper接口相关的Bean生成是通过AutoConfiguredMapperScannerRegistrar自动注册到BeanFactory中的注册进去的是MapperFactoryBean这个工厂Bean类型。 而MapperFactoryBean的getObject方法则是通过getSqlSession().getMapper(this.mapperInterface)生成的mapperInterfact是mapper接口。 底层是通过Configuration.getMapper生成的再底层是mapperRegistry.getMapper方法代码如下 public T T getMapper(ClassT type, SqlSession sqlSession) {final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type);if (mapperProxyFactory null) {throw new BindingException(Type type is not known to the MapperRegistry.);}try {// 调用下面的方法生成代理实例return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException(Error getting mapper instance. Cause: e, e);}}public T newInstance(SqlSession sqlSession) {// 创建MapperProxy这个InvocationHandler实例final MapperProxyT mapperProxy new MapperProxyT(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxyT mapperProxy) {// 调用jdk动态代理生成实例代理的InvocationHandler是MapperProxyreturn (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);} 可以看到底层是通过JDK动态代理Proxy生成的InvocationHandler是MapperProxy类。 清楚原理之后我们对上面的实例做下改造把Mybatis的引用简化。 Configurationpublic class DemoConfiguraion {Beanpublic FactoryBeanDemoMapper getDemoMapper() {return new FactoryBeanDemoMapper() {Overridepublic DemoMapper getObject() throws Exception {InvocationHandler invocationHandler (proxy, method, args) - {System.out.println(调用动态代理方法 method.getName());return Collections.singletonList(new HashMapString, Object());};return (DemoMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {DemoMapper.class}, invocationHandler);}Overridepublic Class? getObjectType() {return DemoMapper.class;}Overridepublic boolean isSingleton() {return true;}};}} 上面的代码可达到与Mapper同样的效果大家可以本地随便玩哈。
http://www.zqtcl.cn/news/422950/

相关文章:

  • 心馨人生珠海网站建设外贸型企业网站建设
  • 好网站建设公司昆明乐清网站优化推广
  • 哪些网站用天平做logo站长工具app官方下载
  • 做餐厅logo用什么软件网站手机自适应网站源码
  • 股票网站模板辽宁工程建设信息网站
  • 毕业设计某网站开发的开题报告范文广西建设教育网站
  • 浏览小城镇建设的网站商丘网站公司
  • python学习网站做好网络推广的技巧
  • 网站有几种类型小说网站开发源码
  • 给城市建设提议献策的网站网站建设可研报告
  • 常德论坛网站陕西建设官方网站
  • 怎么做网站访问量上海网站排名提升
  • 新乡企业网站建设胶州做网站公司
  • 网站后台权限分配说明什么网站是做家教的
  • 网站备案 空间备案 域名备案网站制作与管理技术标准实训教程
  • 东莞免费企业网站模板推广有没有专门做线下活动的网站
  • 驾校网站制作郑州手机网站建设多少钱
  • c2c网站建设策划书怎么看网站关键词密度
  • 网站在线支付方案网站建设 sam大叔排名三天上首页
  • 温岭新站seo网站免费进入窗口软件有哪些
  • 网站未备案什么意思网站 php .net
  • 网站开发第三方登录设计七牛图床 wordpress
  • 大连网站设计案例宁波品牌网站设计价格
  • 响应式表白网站源码黑龙江建设网电话
  • wordpress企业建站生产企业做网站的费用怎么做账
  • 天都城网站建设wordpress pluings
  • 惠州做网站的公司有哪些wordpress主动推送
  • jsp做的网站带数据库新手网站设计定价
  • 做网站公司需要什么条件不锈钢公司网站源码 网站建设 产品3级分类asp源码
  • 经营网站挣钱网络运维工程师证书怎么考