前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >接口方法上的注解无法被 @Aspect 声明的切面拦截的原因分析

接口方法上的注解无法被 @Aspect 声明的切面拦截的原因分析

作者头像
用户1257393
发布于 2018-07-30 07:38:20
发布于 2018-07-30 07:38:20
4.5K00
代码可运行
举报
文章被收录于专栏:精讲JAVA精讲JAVA
运行总次数:0
代码可运行

来源:光闪 , my.oschina.net/guangshan/blog/1808373

前言

在Spring中使用MyBatis的Mapper接口自动生成时,用一个自定义的注解标记在Mapper接口的方法中,再利用@Aspect定义一个切面,拦截这个注解以记录日志或者执行时长。但是惊奇的发现这样做之后,在Spring Boot 1.X(Spring Framework 4.x)中,并不能生效,而在Spring Boot 2.X(Spring Framework 5.X)中却能生效。

这究竟是为什么呢?Spring做了哪些更新产生了这样的变化?此文将带领你探索这个秘密。

案例

核心代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SpringBootApplication
public class Starter {
  public static void main(String[] args) {
    SpringApplication.run(DynamicApplication.class, args);
  }
}
 
@Service
public class DemoService {
 
    @Autowired
    DemoMapper demoMapper;
 
    public List<Map<String, Object>> selectAll() {
        return demoMapper.selectAll();
    }
}
 
/**
 * mapper类
 */
@Mapper
public interface DemoMapper {
 
  @Select("SELECT * FROM demo")
  @Demo
  List<Map<String, Object>> selectAll();
 
}
 
/**
 * 切入的注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Demo {
  String value() default "";
}
 
/**
 * aspect切面,用于测试是否成功切入
 */
@Aspect
@Order(-10)
@Component
public 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");
  }
}

测试类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RunWith(SpringRunner.class) 
@SpringBootTest(classes = Starter.class)
public class BaseTest {
 
    @Autowired
    DemoService demoService;
 
    @Test
    public 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”)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中则不是空,那么这里就是问题的核心点了,查看源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        // 如果是空,则不代理
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 找到当前BeanFactory中的Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 遍历Advisor,根据Advisor中的PointCut判断,返回所有合适的Advisor
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    // 扩展advisor列表,这里会默认加入一个ExposeInvocationInterceptor用于暴露动态代理对象,之前文章有解释过
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // 根据@Order或者接口Ordered排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}
protected List<Advisor> findAdvisorsThatCanApply(
        List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        // 真正的查找方法  
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}

这里的核心问题在于AopUtils.findAdvisorsThatCanApply方法,这里的返回在两个版本是不一样的,由于这里代码过多就不贴上来了,说明下核心问题代码是这段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// AopProxyUtils.java
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> 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 doesn't 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中源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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,这里两个版本都为true
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }
 
    MethodMatcher methodMatcher = pc.getMethodMatcher();
    // 如果method是固定true,即拦截所有method,则返回true。这里当然为false
    if (methodMatcher == MethodMatcher.TRUE) {
        // No need to iterate the methods if we're matching any method anyway...
        return true;
    }
 
    // 特殊类型,做下转换,Aspect生成的属于这个类型
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    }
 
    // 取到目标class的所有接口
    Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    // 再把目标calss加入遍历列表
    classes.add(targetClass);
    for (Class<?> clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        // 遍历每个类的每个方法,尝试判断是否match
        for (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中源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 we're matching any method anyway...
        return true;
    }
 
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    }
 
    Set<Class<?>> 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是同一个对象,两个方法逻辑相同。看代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 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 case
        if (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)的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1.X
public 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.X
public 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版本修复了这个问题。

影响范围

原因已经查明,那么根据原因我们推算一下影响范围

  1. Bean是接口动态代理对象时,且该动态代理对象不是Spring体系生成的,接口中的切面注解无法被拦截
  2. Bean是CGLIB动态代理对象时,该动态代理对象不是Spring体系生成的,原始类方法上的切面注解无法被拦截。
  3. 可能也影响基于类名和方法名的拦截体系,因为生成的动态代理类路径和类名是不同的。

如果是Spring体系生成的,之前拿到的都是真实类或者接口,只有在生成动态代理后,才是新的类。所以在创建动态代理时,获取的是真实的类。

接口动态代理多见于ORM框架的Mapper、RPC框架的SPI等,所以在这两种情况下使用注解要尤为小心。

有些同学比较关心@Cacheable注解,放在Mapper中是否生效。答案是生效,因为@Cacheable注解中使用的不是@Aspect的PointCut,而是CacheOperationSourcePointcut,其中虽然也使用了getMostSpecificMethod来获取method,但是最终其实又从原始方法上尝试获取了注解:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// AbstractFallbackCacheOperationSource.computeCacheOperations
if (specificMethod != method) {
    //  Fallback is to look at the original method
    opDef = 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版本是无解的。

表达式参考如下链接:

  1. Spring 之AOP AspectJ切入点语法详解(最全面、最详细。) https://blog.csdn.net/zhengchao1991/article/details/53391244
  2. Spring Aspect的Execution表达式 https://blog.csdn.net/lang_niu/article/details/51559994

本来以为在注解Demo中加入@Inherited可解决的,结果发现不行,因为这个@Inherited只在类注解有效,在接口中或者方法上,都是不能被子类或者实现类继承的,看这个@Inherited上面的注释

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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 class's 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.
 *
 * <p>Note 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
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

扩展阅读

问题及可能的影响范围已经详细分析完了,下面我们好奇一下,这个核心问题类AopUtils.java的提交记录中,作者有写什么吗

AopUtils.java类GitHub页面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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这个日期的提交,其中提到:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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 MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 调用jdk动态代理生成实例,代理的InvocationHandler是MapperProxy
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

可以看到底层是通过JDK动态代理Proxy生成的,InvocationHandler是MapperProxy类。

清楚原理之后,我们对上面的实例做下改造,把Mybatis的引用简化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class DemoConfiguraion {
 
    @Bean
    public FactoryBean<DemoMapper> getDemoMapper() {
        return new FactoryBean<DemoMapper>() {
            @Override
            public DemoMapper getObject() throws Exception {
                InvocationHandler invocationHandler = (proxy, method, args) -> {
                    System.out.println("调用动态代理方法" + method.getName());
                    return Collections.singletonList(new HashMap<String, Object>());
                };
                return (DemoMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {DemoMapper.class}, invocationHandler);
            }
            @Override
            public Class<?> getObjectType() {
                return DemoMapper.class;
            }
            @Override
            public boolean isSingleton() {
                return true;
            }
        };
    }
}

上面的代码可达到与Mapper同样的效果,大家可以本地随便玩哈。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精讲JAVA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
ASP.NET Core MVC应用模型的构建[3]: Controller的收集
从编程的角度来看,一个MVC应用是由一系列Controller类型构建而成的,所以对于一个代表应用模型的ApplicationModel对象来说,它的核心就是Controllers属性返回的一组ControllerModel对象,每个ControllerModel对象是应用模型针对Controller类型的描述。
蒋金楠
2024/02/29
1280
ASP.NET Core MVC应用模型的构建[3]: Controller的收集
ASP.NET Core MVC应用模型的构建[4]: Action的选择
ControllerModel类型的Actions属性包含一组描述有效Action方法的ActionModel对象。对于定义在Controller类型中的所有方法,究竟哪些方法才能成为有效的Action方法呢?所以在正式介绍ActionModel类型之前,我们先来聊聊Action方法的选择规则。
蒋金楠
2024/03/01
1650
ASP.NET Core MVC应用模型的构建[4]: Action的选择
5. abp集成asp.net core
参照前篇《4. abp中的asp.net core模块剖析》,首先放张图,这也是asp.net core框架上MVC模块的扩展点
Ryan_OVO
2023/10/19
4520
5. abp集成asp.net core
ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图
我个人觉得这是ASP.NET Core MVC框架体系最核心的部分。原因很简单,MVC框架建立在ASP.NET Core路由终结点上,它最终的目的就是将每个Action方法映射为一个或者多个路由终结点,路由终结点根据附加在Action上的若干元数据构建而成。为了构建描述当前应用所有Action的元数据,MVC框架会提取出定义在当前应用范围内的所有Controller类型,并进一步构建出基于Controller的应用模型。应用模型不仅仅是构建Action元数据的基础,承载API的应用还可以利用它自动生成API开发文档,一些工具甚至可以利用应用模型自动生成消费API的客户端代码。这篇文章大概是两年之前写的,可能一些技术细节在最新版本的ASP.NET Core MVC已经发生了改变,但总体设计依然如此。
蒋金楠
2024/02/27
2080
ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图
【asp.net core】实现动态 Web API
远程工作已经一个月了,最近也算是比较闲,每天早上起床打个卡,快速弄完当天要做的工作之后就快乐摸鱼去了。之前在用 ABP 框架(旧版)的时候就觉得应用服务层写起来真的爽,为什么实现了个 IApplicationService 的空接口就可以变成 Web API,可惜的是之前一直没空去研究这一块的原理及其实现,园子里也找不到相关实现原理的文章(旧版 ABP 的倒是有,但是 asp.net core 无法参考)。最近闲起来,就看了一下 abp vnext 的源码,并且也参考了一下晓晨Master 介绍的 Panda.DynamicWebApi。我自己也简单实现了一遍动态 Web API,不禁感叹 asp.net core 设计之精妙。
李明成
2020/03/18
2.8K0
【asp.net core】实现动态 Web API
[ASP.NET Core MVC] 如何实现运行时动态定义Controller类型?
昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是否有好解决方案。我当时在外边,回复不太方便,所以只给他说了两个接口/类型:IActionDescriptorProvider和ApplicationPartManager。这是一个挺有意思的问题,所以回家后通过两种方案实现了这个需求。源代码从这里下载。
蒋金楠
2020/04/08
1.5K1
[ASP.NET Core MVC] 如何实现运行时动态定义Controller类型?
在Asp.Net Core中使用ModelConvention实现全局过滤器隔离
这来自于我把项目迁移到Asp.Net Core的过程中碰到一个问题。在一个web程序中同时包含了MVC和WebAPI,现在需要给WebAPI部分单独添加一个接口验证过滤器IActionFilter,常规做法一般是写好过滤器后给需要的控制器挂上这个标签,高级点的做法是注册一个全局过滤器,这样可以避免每次手动添加同时代码也更好管理。注册全局过滤器的方式为:
HOHO
2020/04/27
1.2K0
4. abp中的asp.net core模块剖析
abp通过这三个模块加载并配置了 asp.net core。,最主要的就是AbpAspNetCoreMvcModule模块类,abp如何基于aspnet core构建自己的控制器和AppServices,就是在这个类中。
Ryan_OVO
2023/10/19
2900
深入解析ASP.NET Core MVC应用的模块化设计[上篇]
ASP.NET Core MVC的“模块化”设计使我们可以构成应用的基本单元Controller定义在任意的模块(程序集)中,并在运行时动态加载和卸载。这种为“飞行中的飞机加油”的方案是如何实现的呢?该系列的两篇文章将关注于这个主题,本篇着重介绍“模块化”的总体设计,下篇我们将演示将介绍“分散定义Controller”的N种实现方案。
蒋金楠
2024/03/06
3300
深入解析ASP.NET Core MVC应用的模块化设计[上篇]
[ASP.NET Core 3框架揭秘] 跨平台开发体验: Windows [下篇]
由于ASP.NET Core框架在本质上就是由服务器和中间件构建的消息处理管道,所以在它上面构建的应用开发框架都是建立在某种类型的中间件上,整个ASP.NET Core MVC开发框架就是建立在用来实现路由的EndpointRoutingMiddleware和EndpointMiddleware中间件上。ASP.NET Core MVC利用路由系统为它分发请求,并在此基础上实现针对目标Controller的激活、Action方法的选择和执行,以及最终对于执行结果的响应。在介绍的实例演示中,我们将对上面创建的ASP.NET Core作进一步改造,使之转变成一个MVC应用。
蒋金楠
2019/10/11
2K0
[ASP.NET Core 3框架揭秘] 跨平台开发体验: Windows [下篇]
asp.net core权限模块的快速构建
此处的自定义的特性标记不能继承Attribute,因无法在AdminAuthorizeAttribute中的上下文filterContext.Filters中获取到特性标记(不知道咋取特性标记,所以用这种方式代替,也更为简单 冏) !!!!!!!!!修改: 之前脑袋没有转过弯来,要使过滤器上下文的Filters中发现自定义过滤器需要继承 Attribute, IFilterMetadata
易墨
2018/09/14
1.9K0
asp.net core权限模块的快速构建
【愚公系列】2023年02月 WMS智能仓储系统-007.Swagger接口文档的配置
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
愚公搬代码
2023/03/16
9510
【愚公系列】2023年02月 WMS智能仓储系统-007.Swagger接口文档的配置
ASP.NET Core 错误处理(Handle Errors)
链接:cnblogs.com/xiaoxiaotank/p/15586706.html
郑子铭
2021/12/01
2.1K0
ASP.NET Core 错误处理(Handle Errors)
猿学-讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute
ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文章是18年2月份的,所以文章提到了core2.1还没发布)。
黑客联盟
2018/08/18
1K0
猿学-讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute
Asp.Net Core AuthorizeAttribute 和AuthorizeFilter 跟进及源码解读
IdentityServer4已经分享了一些应用实战的文章,从架构到授权中心的落地应用,也伴随着对IdentityServer4掌握了一些使用规则,但是很多原理性东西还是一知半解,故我这里持续性来带大家一起来解读它的相关源代码,本文先来看看为什么Controller或者Action中添加Authorize或者全局中添加AuthorizeFilter过滤器就可以实现该资源受到保护,需要通过access_token才能通过相关的授权呢?今天我带大家来了解AuthorizeAttribute和AuthorizeFilter的关系及代码解读。
Jlion
2022/04/07
3.7K0
Asp.Net Core AuthorizeAttribute 和AuthorizeFilter 跟进及源码解读
【asp.net core 系列】10 实战之ActionFilter
在上一篇中,我们提到了如何创建一个UnitOfWork并通过ActionFilter设置启用。这一篇我们将简单介绍一下ActionFilter以及如何利用ActionFilter,顺便补齐一下上一篇的工具类。
程序员小高
2020/06/18
6670
C# .NET面试系列七:ASP.NET Core
在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务:
GoodTime
2024/03/08
3730
C# .NET面试系列七:ASP.NET Core
如何使用Serilog.AspNetCore记录ASP.NET Core3.0的MVC属性
在我上篇文章中,我描述了如何配置Serilog的RequestLogging中间件以向Serilog的请求日志摘要中添加其他属性(例如请求主机名或选定的端点名称)。这些属性都在HttpContext中可用,因此可以由中间件本身直接添加。
依乐祝
2020/02/14
3.6K0
如何使用Serilog.AspNetCore记录ASP.NET Core3.0的MVC属性
ASP.NET Core应用的7种依赖注入方式
构成HostBuilderContext上下文的两个核心对象(表示配置的IConfiguration对象和表示承载环境的IHostEnvironment对象)可以直接注入Startup构造函数中进行消费。由于ASP.NET Core应用中的承载环境通过IWebHostEnvironment接口表示,IWebHostEnvironment接口派生于IHostEnvironment接口,所以也可以通过注入IWebHostEnvironment对象的方式得到当前承载环境相关的信息。
蒋金楠
2020/03/26
1.8K0
学习ASP.NET Core,你必须了解无处不在的“依赖注入”
ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要相应的服务提供支持,ASP.NET Core自身提供了一个DI容器来实现针对服务的注册和消费。换句话说,不只是ASP.NET Core底层框架使用的服务是由这个DI容器来注册和提供,应用级别的服务的注册和提供也需要以来这个DI容器,所以正如本文标题所说的——学习ASP.NET Core,你必须了解无处不在的“依
蒋金楠
2018/01/15
1.2K0
学习ASP.NET Core,你必须了解无处不在的“依赖注入”
推荐阅读
相关推荐
ASP.NET Core MVC应用模型的构建[3]: Controller的收集
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文