Spring AOP源码探究

news2024/11/16 19:09:08

1. 前言

Spring除了IOC和DI,还有另一个杀手锏功能——Spring AOP。AOP是一种面向切面的编程思想,它的关注点是横向的,不同于OOP的纵向。面向对象编程时,如果我们要给多个类引入同一个行为,唯一的方式就是使用继承,否则就要在这些类里面加入大量重复的代码,如此一来程序将不利于维护。于是,AOP横空出世,它弥补了OOP编程的弊端。Spring内部也有大量特性是通过AOP来实现的,比如我们熟知的数据库事务。

2. 术语

查看源码前,先了解一下AOP的相关术语。

  • 连接点(Joinpoint)

Advice执行的位置,比如:方法前、方法后、发生异常时等等,Spring仅支持方法的连接点。

  • 切点(Pointcut)

连接点的过滤条件,AOP通过切点定位到具体的连接点。

  • 增强/通知(Advice)

应用在连接点的行为,增强的逻辑代码,分为:前置、后置、环绕、异常增强。

  • 增强器(Advisor)

通知器由一个切点(Pointcut)和一个增强(Advice)组成。

  • 切面(Aspect)

由切点(Pointcut)和增强(Advice)组成。

  • 织入(Weaving)

将增强应用到目标连接点的过程,可以静态织入,也可以运行时织入。

  • 目标(Target)

被增强的对象(方法)。

  • 代理(Proxy)

向Target应用Advice之后创建的代理对象。

3. 示例

Spring AOP使用起来非常简单,这里提供一个小示例。
1、定义切面

@Aspect
@Component
public class MyAspectConfig {

    @Pointcut("execution(* com.javap.aop.*.*(..))")
    public void pointCut() {

    }

    /**
     * 每一个增强方法,都会被封装成Advisor对象
     *
     * @see Advisor
     */
    @Before("pointCut()")
    public void before() {
        System.err.println("before");
    }

    @After("pointCut()")
    public void after() {
        System.err.println("after");
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.err.println("Around before");
        Object result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        System.err.println("Around after");
        return result;
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, String result) throws Throwable {
        System.err.println("afterReturning: " + result);
    }

    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void afterThrowing(Exception e) throws Throwable {
        System.err.println("afterThrowing: " + e.getMessage());
    }
}

2、定义bean,也就是需要被增强的目标对象

@Component
public class Person {

    public String say() {
        System.err.println("say...");
        return "aabbccdd";
    }

    /**
     * final方法,子类无法重写,因此不能被增强
     */
    public final void eat() {
        System.err.println("eat...");
    }
}

3、加上@EnableAspectJAutoProxy注解,就可以启用AOP了。

@Configuration
@ComponentScan("com.javap.aop")
@EnableAspectJAutoProxy
public class AopApplication {
    
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AopApplication.class);
        Person person = context.getBean(Person.class);
        person.say();
        person.eat();
    }
}

控制台输出:

Around before
before
say...
Around after
after
afterReturning: aabbccdd
eat...

4. @EnableAspectJAutoProxy

问题:为什么在启动类上,加上**@EnableAspectJAutoProxy**注解就可以开启AOP呢???
答案当然要在注解本身找了,该注解上有一个@Import注解,引入了AspectJAutoProxyRegistrar类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * 是否强制使用类代理模式:CGLIB代理
	 */
	boolean proxyTargetClass() default false;

	/**
	 * 是否暴露代理对象,以通过AopContext获取
	 */
	boolean exposeProxy() default false;
}

AspectJAutoProxyRegistrar类实现了Spring提供的ImportBeanDefinitionRegistrar接口并重写了registerBeanDefinitions()方法,如此一来Spring在启动时,就会触发AspectJAutoProxyRegistrar#registerBeanDefinitions()方法,该方法会向容器内注册一个特别重要的类AnnotationAwareAspectJAutoProxyCreator

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        /**
         * 注册AnnotationAwareAspectJAutoProxyCreator类
         */
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        // 获取注解信息
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            /**
             * 将注解属性写入到BeanDefinition
             * proxyTargetClass:是否通过CGLIB类代理模式
             * exposeProxy:是否暴露代理类,以通过AopContext获取
             */
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,属于Spring的扩展点之一,Spring在实例化bean实例后,会触发该扩展点,对bean做扩展和增强,也就是返回织入了Advice后的代理对象。
image.png

5. AbstractAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator既然实现了BeanPostProcessor接口,那么必然要重写postProcessAfterInitialization()方法来返回增强后的bean,重写方法在父类AbstractAutoProxyCreator里。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 循环依赖,提前暴露bean时,代理对象可能已经生成
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 生成增强后的代理对象,如果有需要
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

Spring会调用wrapIfNecessary()方法来返回增强后的bean。**所有的bean都会被BeanPostProcessor处理,但并不是所有的bean都需要增强,Spring会根据切点表达式判断beanClass是否需要被增强。**这个判断过程是比较耗时的,因为要解析beanClass所有的方法去和切点表达式进行匹配,所以Spring加了一个Map缓存解析过的beanClass。
判断bean是否要增强也很简单,通过getAdvicesAndAdvisorsForBean()方法去解析能应用到beanClass的所有增强器Advisor,如果没有增强器可用于该beanClass,也就不需要增强,直接原样返回bean即可,否则基于Advisor去创建增强后的代理对象。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        // 已增强
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        // 已经解析过beanClass,且判定为无需增强
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        // 无需增强
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    /**
     * 获取beanClass可用的Advisor
     */
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 基于Advisor,创建代理对象
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    // beanClass无可用的Advice,无需增强
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

6. 构建Advisor

bean是否要增强,是通过beanClass是否有可用的Advisor来判断的。那么,Advisor是怎么来的呢?
查找bean可用的Advisor的方法在AbstractAdvisorAutoProxyCreator#findEligibleAdvisors(),先从容器内找出所有的Advisor,也就是我们在@Aspect类里定义的各种增强方法,然后根据切点表达式过滤出可以应用到当前beanClass的Advisor。然后将一个特殊的拦截器ExposeInvocationInterceptor插入到首位,最后将拦截器排个序返回。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    /**
     * 查找容器内所有的Advisor:@Aspect类里定义的各种增强方法
     */
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    /**
     * 根据切点表达式,过滤出可以应用到beanClass的Advisor
     */
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    /**
     * 将ExposeInvocationInterceptor拦截器插入到第一个
     */
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

构建Advisor的职责交给了BeanFactoryAspectJAdvisorsBuilder类的buildAspectJAdvisors()方法,它首先从容器内找出所有加了@Aspect注解的bean,然后解析bean所有加了@Pointcut@Around@Before@After@AfterReturning@AfterThrowing注解的方法,将它们封装成Advisor对象。
image.png
容器内的Advisor对象并不能都应用到目标bean对象,所以Spring还会调用findAdvisorsThatCanApply()方法过滤出当前beanClass可用的Advisor。

7. 创建代理对象

如果当前bean有可用的Advisor,也就意味着需要对bean做增强,此时会调用createProxy()方法创建增强后的代理对象。
问题:使用JDK代理还是CGLIB代理?
我们可以通过属性proxyTargetClass强制使用CGLIB代理,如果不指定,Spring的规则是:如果beanClass实现了接口,且接口至少有一个自定义方法,那么就用JDK代理,否则用CGLIB代理。

protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
    // 获取beanClass实现的所有接口
    Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());
    boolean hasReasonableProxyInterface = false;
    /**
     * 遍历接口
     * 1.非配置的回调接口,即Spring内置的接口不算,例如InitializingBean、Aware等接口
     * 2.非内部语言接口,例如GroovyObject
     * 3.接口至少有一个自定义方法
     */
    for (Class<?> ifc : targetInterfaces) {
        if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&
                ifc.getMethods().length > 0) {
            hasReasonableProxyInterface = true;
            break;
        }
    }
    if (hasReasonableProxyInterface) {
        // Must allow for introductions; can't just set interfaces to the target's interfaces only.
        for (Class<?> ifc : targetInterfaces) {
            proxyFactory.addInterface(ifc);
        }
    } else {
        // 没有实现有效接口,使用CGLIB代理
        proxyFactory.setProxyTargetClass(true);
    }
}

最终,Spring会通过ProxyFactory去创建代理对象。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                             @Nullable Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    // 基于ProxyFactory创建代理对象
    ProxyFactory proxyFactory = new ProxyFactory();
    // 属性赋值给ProxyFactory
    proxyFactory.copyFrom(this);
    /**
     * 判断使用JDK代理还是CGLIB代理
     * 如果beanClass实现了接口,且接口至少有一个自定义方法,则使用JDK代理
     * 否则CGLIB代理
     */
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        } else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);
    // 是否冻结,也就是Advisor不可再变更
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
    // 创建动态代理  两种方式 JDK CGlib
    return proxyFactory.getProxy(getProxyClassLoader());
}

AopProxy有两种实现,分别是**JdkDynamicAopProxy****CglibAopProxy****,前者使用JDK动态代理的方式将Advice织入bean,后者通过CGLIB生成子类的方式将Advice织入bean。**我们以CGLIB为例,创建代理对象的方法是CglibAopProxy#getProxy()

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // Validate the class, writing log messages as necessary.
        validateClassIfNecessary(proxySuperClass, classLoader);

        // Configure CGLIB Enhancer...
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        /**
         * 会实现接口
         * @see org.springframework.aop.SpringProxy
         * @see org.springframework.aop.framework.Advised
         */
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
        /**
         * 获取方法回调,AOP主要看
         * @see DynamicAdvisedInterceptor
         */
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        // Generate the proxy class and create a proxy instance.
        return createProxyClassAndInstance(enhancer, callbacks);
    } catch (CodeGenerationException | IllegalArgumentException ex) {
        throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                ": Common causes of this problem include using a final class or a non-visible class",
                ex);
    } catch (Throwable ex) {
        // TargetSource.getTarget() failed
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}

Spring会通过Enhancer去创建子类对象来增强目标bean,生成的子类默认会实现org.springframework.aop.SpringProxy接口用来标记它是一个Spring生成的代理类。我们重点看给Enhancer对象设置的Callback,因为它会对方法做拦截,也就是说,我们调用子类的增强方法,其实就是在调用Callback#intercept()。Spring考虑的比较全面,会针对mainequalshashCode等方法做处理,针对AOP我们主要看AopInterceptor即可。
image.png
aopInterceptor是DynamicAdvisedInterceptor子类对象,用于处理AOP方法调用,也就是说,AOP增强逻辑就在DynamicAdvisedInterceptor#intercept()里。
image.png

8. DynamicAdvisedInterceptor

调用CGLIB增强子类的方法,其实会触发DynamicAdvisedInterceptor#intercept()方法,看看Spring增强的逻辑。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        /**
         * 是否要暴露代理对象,以通过AopContext获取
         * 写入ThreadLocal
         */
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        /**
         * 获取目标Bean实例
         * 如果是prototype 此时会创建新的实例
         */
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        /**
         * 获取方法能应用到的Advice,拦截器调用链
         * @see org.springframework.aop.interceptor.ExposeInvocationInterceptor
         * @see org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
         * @see org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor
         * @see org.springframework.aop.aspectj.AspectJAfterAdvice
         * @see org.springframework.aop.aspectj.AspectJAroundAdvice
         * @see org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor
         */
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            /**
             * 没有Advice,直接调用父类方法
             */
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        } else {
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        // 处理一下返回结果的类型
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

方法拦截器主要做了三件事:

  1. 判断是否要暴露代理对象,如果要则写入ThreadLocal
  2. 获取方法能应用到的Advice,构建拦截器调用链
  3. 触发拦截器调用链

如果方法没有可用的Advice,也就不需要增强,直接调用父类方法即可。反之方法需要增强,Spring会new一个CglibMethodInvocation对象,触发拦截器调用链。

9. CglibMethodInvocation

CglibMethodInvocation#proceed()方法会触发拦截器调用链,Spring通过一个int变量currentInterceptorIndex来记录当前执行的拦截器索引,通过和总的拦截器数量判断是否调用完所有的拦截器,如果是则通过invokeJoinpoint()方法去调用目标方法,反之则去执行Advice增强。

public Object proceed() throws Throwable {
    /**
     * currentInterceptorIndex 当前执行的拦截器索引
     * interceptorsAndDynamicMethodMatchers.size() 所有拦击器个数
     * 执行完最后一个拦截器,就要执行目标方法了
     */
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 最终反射调用目标方法
        return invokeJoinpoint();
    }
	// 触发Advice增强
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        } else {
            return proceed();
        }
    } else {
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

10. Advice子类

Spring AOP提供了五种Advice增强,分别是通过@Around@Before@After@AfterReturning@AfterThrowing注解标记的方法。这五种Advice分别对应五个Advice子类实现。
1、AspectJAfterThrowingAdvice
先走下一个拦截器,只有在发生异常时,才触发的Advice。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        // 先走下一个拦截器
        return mi.proceed();
    } catch (Throwable ex) {
        if (shouldInvokeOnThrowing(ex)) {
            /**
             * 只有发生异常了,才会执行@AfterThrowing Advice
             */
            invokeAdviceMethod(getJoinPointMatch(), null, ex);
        }
        throw ex;
    }
}

2、AfterReturningAdviceInterceptor
走剩下所有的拦截器,拿到返回结果。正常执行完毕才触发的Advice,如果发生异常则不触发。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    // 直接走剩下的拦截器,拿到返回结果
    Object retVal = mi.proceed();
    /**
     * 正常执行完毕,才触发 @AfterReturning Advice
     * 如果期间发生异常,则不触发
     */
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    return retVal;
}

3、AspectJAfterAdvice
先走下一个拦截器,执行完毕再最终调用的Advice,不论是否发生异常。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        /**
         * 先走下一个拦截器,执行完毕再最终调用@After Advice
         */
        return mi.proceed();
    } finally {
        /**
         * 执行@After Advice方法
         * @After Advice一定会被调用,不管是否发生异常,因为在finally
         */
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}

4、AspectJAroundAdvice
直接触发Advice,是否继续调用后续Advice由我们自己决定。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    if (!(mi instanceof ProxyMethodInvocation)) {
        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
    }
    /**
     * 直接触发Advice,是否继续调用后续Advice,由@Around Advice自己决定
     */
    ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
    ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
    JoinPointMatch jpm = getJoinPointMatch(pmi);
    return invokeAdviceMethod(pjp, jpm, null, null);
}

5、MethodBeforeAdviceInterceptor
先触发当前Advice,再调用下一个Advice。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    /**
     * 先触发 @Before Advice
     * 再调用下一个Advice
     */
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

了解SpringBoot自动配置原理

目录 1、SpringBoot特点 1.1、依赖管理 1.1.1 父项目做依赖管理 1.1.2 开发导入starter场景启动器 1.1.3 无需关注版本号&#xff0c;自动仲裁机制 1.1.4 可以修改默认版本号 1.2、自动配置 2、容器功能 2.1、组件添加 1、Configuration 2、Bean、Component、Contro…

医院室内定位导航,便捷、低成本智慧医院室内地图应用解决方案

医院布局作为公共建筑最复杂的结构之一&#xff0c;有规模大、功能复杂等特点&#xff0c;因而&#xff0c;面向医护人员、患者就诊、医院管理等一系列基础医疗服务就成了当下医院智慧化改善的首要问题。电子地图作为大家最喜闻乐见的高效应用形式&#xff0c;可高效为病患提供…

SpringMVC的学习

SpringMVC 文章目录SpringMVC学习目标SpringMVC简介优点入门案例springmvc入门程序开发流程入门案例工作流程分析SpringMVC对应bean加载与spring对应bean加载web配置类简化开发PostMan插件简介基本使用请求与响应请求映射路径请求参数请求方式请求参数(传递json数据)日期类型参…

小程序和公众号相互跳转

小程序跳转公众号 小程序跳转公众号目前只找到 微信暴露的 引导关注组件 official-account, 官方文档如下&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/component/official-account.html 且这个组件展示是有限制的&#xff0c;只有在扫码进入小程序的情况下…

【数据结构Java版】二叉树堆与优先级队列PriorityQueue

目录 一、优先级队列 &#xff08;1&#xff09;优先级队列的概念 &#xff08;2&#xff09;优先级队列的模拟实现 二、堆 &#xff08;1&#xff09;堆的概念 &#xff08;2&#xff09;堆的存储方式 &#xff08;3&#xff09;堆的创建 1.堆的向下调整 2.堆的创建 …

Nginx access.log日志详解及统计分析

Nginx access.log日志详解及统计分析一、nginx的access.log二、日志流量统计统计接口地址访问量PV统计UV统计独立IP统计三、配置access.log按天生成四、nginx.conf配置一、nginx的access.log 1.日志文件一般存放在 /var/log/nginx 下&#xff0c;若是docker启动则可以使用主机…

奇舞周刊 477 期:一文弄懂 React ref 原理

记得点击文章末尾的“ 阅读原文 ”查看哟~下面先一起看下本期周刊 摘要 吧~奇舞推荐■ ■ ■一文弄懂 React ref 原理对于 Ref 理解与使用&#xff0c;一些读者可能还停留在用 ref 获取真实 DOM 元素和获取类组件实例层面上 其实 ref 除了这两项常用功能之外&#xff0c;还有很…

简单通过Sentinel监控请求

文章目录一&#xff1a;运行Sentinel服务二&#xff1a;安装Sentinel依赖2.1&#xff1a;Sentinel Pom依赖2.2&#xff1a;Sentinel YML 配置注意&#xff1a;clientIp.localhost不同sentinel版本依赖缩进层级结构可能不一样&#xff0c;可根据idea提示回车三&#xff1a;启动服…

年终给您提个醒:明年的分区表创建好了吗?

2022年某月&#xff0c;某运营商客户突然出现部分业务办理失败&#xff0c;数据无法入库的现象......经过查询&#xff0c;应用进程insert提示&#xff1a;“ORA-14400”错误。由此诊断&#xff0c;故障出现是由于上月部分表分区未提前创建&#xff0c;导致本月前端业务在导入数…

130道基础OJ编程题之: 47 ~ 57 道

130道基础OJ编程题之: 47 ~ 57 道 文章目录130道基础OJ编程题之: 47 ~ 57 道0. 昔日OJ编程题:47. BC50 计算单位阶跃函数48. BC51 三角形判断49. BC52 衡量人体胖瘦程度50. BC53 计算一元二次方程51. BC54 获得月份天数52. BC55 简单计算器53. BC56 线段图案54. BC57 正方形图案…

【生信】初探蛋白质性质和结构分析

实验目的 熟悉蛋白质序列和结构的主要分析内容在实践中逐步理解蛋白质序列和结构的主要分析算法的基本原理 实验内容 综合使用多种在线工具&#xff0c;对蛋白质的一级、二级和三级结构进行分析和预测综合使用多种在线工具&#xff0c;对蛋白质的跨膜结构、翻译后修饰、亚细…

第二十八讲:神州路由器地址转换的配置

实验拓扑图如下所示 设置内网地址段为192.168.0.0,S0/1为外网出口。通过nat访问192.168.2.0网段。 操作步骤&#xff1a; 步骤1&#xff1a;连接网络拓扑结构图。 步骤2&#xff1a;配置计算机的IP地址、子网掩码和网关。 步骤3&#xff1a;设置Router-B的接口IP地址和DCE的…

linux0.11+Bochs环境搭建和使用

Linux 系统的创始人在一篇新闻组投稿上所说的&#xff0c;要理解一个软件系统的真正运行机制&#xff0c;一定 要阅读其源代码&#xff08;RTFSC – Read The Fucking Source Code&#xff09;。系统本身是一个完整的整体&#xff0c;具有很多看似 不重要的细节存在&#xff0c…

声明式事物的属性之只读、超时、回滚策略

声明式事物的属性之只读、超时、回滚策略 1. 事务属性&#xff1a;只读 ①介绍 对一个查询操作来说&#xff0c;如果我们把它设置成只读&#xff0c;就能够明确告诉数据库&#xff0c;这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。 ②使用方式 Overri…

zabbix报警方式,邮件报警和微信报警。

整理csdn时候发现了一篇2016年整理zabbix报警不知道当时啥情况没有发布出去&#xff0c;凑个数重新发布 最近这些天都在弄Zabbix不再只是简单的监控物理硬件&#xff0c;服务端口&#xff0c;流量图等。让Zabbix的功能发挥到极致。 本篇博客只做笔记介绍zabbix的报警&#xff…

【数据结构与算法】顺序队列与环形队列

文章目录一 顺序队列1 应用场景2 基本概念&#xff08;1&#xff09;基本介绍&#xff08;2&#xff09;队列的顺序实现&#xff08;3&#xff09;队列的入队和出队操作&#xff08;4&#xff09;使用数组模拟队列3 代码实现&#xff08;1&#xff09;初始化队列&#xff08;2&…

React Native windows环境搭建

1.首先准备下载必须的依赖&#xff1a;Node、JDK 、Android Studio、夜神模拟器 ①Node可以直接到 官网 下载&#xff0c;版本必须大于14&#xff0c;我这边用的是v16.15.1 ②Jave JDK&#xff0c;我直接在360软件管家安装的&#xff0c;搜的是JDK 11&#xff0c;React Nativ…

【算法】双指针、位运算、离散化、合并区间

文章目录1.双指针2.位运算3.离散化4.区间合并1.双指针 双指针的算法可以优化时间复杂度,双指针&#xff0c;指的是在遍历对象的过程中&#xff0c;不是普通的使用单个指针进行访问&#xff0c;而是使用两个相同方向&#xff08; 快慢指针 &#xff09;或者相反方向&#xff08…

SQL调优SQLSERVER 数据页

1. 什么是数据页 一般来说&#xff0c;对大块资源或者数据进行高效管理都会按照一定粒度来划分的&#xff0c;比如说 Windows 对内存的管理就是按照 内存页 (4k) 来进行划分&#xff0c;言外之意就是 SQLSERVER 对 mdf 的管理也是按照 数据页 &#xff08;8k) 来划分的&#x…

(小记)matlab散点图

Matlab散点图两种画法一、plot画散点图二、scatter画散点图三、matlab工具画散点图plot画我可以设置坐标轴之类的&#xff0c;方便论文使用&#xff1b;scatter没设置成功。一、plot画散点图 参考&#xff1a;matlab中二维散点图,MATLAB实例&#xff1a;二维散点图 自用代码 …