Spring Cloud (thirteen): Spring extension

  • Spring extension points
    • BeanPostProcessor extension point in the bean life cycle
    • Spring extension point combing
  • Spring extension point application scenarios
    • Integrate Nacos service registration
      • ApplicationListener extension scenario - listening to events published in the container
      • Lifecycle Nacos Publish Subscribe & Eureka Service Startup, Synchronization, Rejection
      • Lifecycle extension scenario - manage objects with start and stop lifecycle requirements
    • Integrate Nacos publish and subscribe NacosWatch
    • Integrate Eureka registry
      • Extension: The initialization of the Eureka Server context is implemented in SmartLifecycle#start
    • Integration Ribbon
      • SmartInitializingSingleton extension scenario - custom processing of Bean objects in the container
    • Integrate Feign
      • FactoryBean extension scenario - hand over the proxy object generated by the interface to Spring management
    • Integrate sentinel
      • HandlerInterceptor extension scenario - enhancement of mvc request
      • SmartInitializingSingleton&FactoryBean combined scene - dynamically assemble objects according to type
    • integrate seata
      • AbstractAutoProxyCreator&MethodInterceptor Combination Scenario - Implementation Method Enhancement

Spring extension points

BeanPostProcessor extension point in the bean life cycle

  1. bean create before
    resolveBeforeInstantiation(beanName, mbdToUse); => InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(beanClass, beanName);

  2. bean instance before
    determineConstructorsFromBeanPostProcessors(beanClass, beanName); => SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors(beanClass, beanName); @Autowired 构造器

  3. bean instance after

    • bean instance after merge bean query
      applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); => MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition(mbd, beanType, beanName);

      1. AutowiredAnnotationBeanPostProcessor @Autowired @Value
      2. CommonAnnotationBeanPostProcessor @Resource @PostConstruct @PreDestroy
    • bean instance after earlybean
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); => SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(exposedObject, beanName);

  4. bean field setting
    attribute populate populateBean(beanName, mbd, instanceWrapper);

    • Property filled before => InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)
    • Property filling after => field property and setter method
      InstantiationAwareBeanPostProcessor.postProcessProperties(pvs, bw.getWrappedInstance(), beanName)
      InstantiationAwareBeanPostProcessor.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
  5. bean initialize

    • bean initialize before
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); =>

      1. InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(result, beanName); initmethod @PostConstruct
      2. ApplicationContextAwareProcessor.postProcessBeforeInitialization(result, beanName); Aware: ApplicationContextAware、ApplicationEventPublisherAware、EnvironmentAware
    • bean initialize before
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); =>

      1. AbstractAutoProxyCreator.postProcessAfterInitialization(result, beanName); AOP (wrapIfNecessary->createAopProxy().getProxy(classLoader); Cglib/Jdk)
    • bean initialize register distroy
      registerDisposableBeanIfNecessary(beanName, bean, mbd); => DestructionAwareBeanPostProcessor

  6. bean create after
    destroySingleton(beanName) => DestructionAwareBeanPostProcessor#postProcessBeforeDestruction(this.bean, this.beanName);

Spring extension point combing

  • BeanFactoryPostProcessor
    • BeanDefinitionRegistryPostProcessor
      • postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) # Register Bean Definition
  • BeanPostProcessor
    • InstantiationAwareBeanPostProcessor
      • postProcessBeforeInstantiation
      • postProcessAfterInstantiation
      • postProcessProperties
      • postProcessPropertyValues
    • AbstractAutoProxyCreator
      • postProcessAfterInitialization # AOP support after instantiation
  • @Import
    • ImportBeanDefinitionRegistrar
      • registerBeanDefinitions
    • ImportSelector
      • String[] selectImports(AnnotationMetadata importingClassMetadata)
      • default Predicate getExclusionFilter() { return null;}
  • Aware
    • ApplicationContextAware
      • setApplicationContext(ApplicationContext applicationContext)
    • BeanFactoryAware
      • setBeanFactory(BeanFactory beanFactory)
  • InitializingBean || @PostConstruct
  • FactoryBean (dynamically create Bean) (& beanName factoryBean.getObject | beanName beanFactory.getBean) (Mybatis#mapper, Openfeign)
    • T getObject() throws Exception;
    • Class<?> getObjectType();
    • default boolean isSingleton() { return true; }
  • SmartInitializingSingleton Bean
    • void afterSingletonsInstantiated(); # Call filter after initialization...
  • ApplicationListener
    • void onApplicationEvent(E event);
  • Lifecycle
    • SmartLifecycle
      • default boolean isAutoStartup() { return true; }
      • default void stop(Runnable callback)
    • LifecycleProcessor
  • HandlerInterceptor
    • default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    • default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception
    • default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception
  • MethodInterceptor
    • Object invoke(MethodInvocation invocation) throws Throwable;

Spring extension point application scenarios

Integrate Nacos service registration

ApplicationListener extension scenario - listening to events published in the container

NamingService -> NacosNamingService => registerInstance

Thinking: Why does the service start to register automatically after the integration of the Nacos registration center? How does Nacos realize automatic service registration?

  1. After Tomcat starts, in ServletWebServerApplicationContext, publish event ServletWebServerInitializedEvent

  2. spring-cloud-commons 包 AbstractAutoServiceRegistration 监听 WebServerInitializedEvent

public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware,
		ApplicationListener<WebServerInitializedEvent> {
    
    

	public void onApplicationEvent(WebServerInitializedEvent event) {
    
    
		bind(event); // .... register();
	}
}
  1. Nacos implements ServiceRegistry.register

Lifecycle Nacos Publish Subscribe & Eureka Service Startup, Synchronization, Rejection

Lifecycle extension scenario - manage objects with start and stop lifecycle requirements

start => AbstractApplicationContext.refresh -> finishRefresh() -> getLifecycleProcessor().onRefresh(); -> start()

Integrate Nacos publish and subscribe NacosWatch

public class NacosWatch
		implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean {
    
    


	@Override
	public void start() {
    
    
			...
			NamingService namingService = nacosServiceManager
					.getNamingService(properties.getNacosProperties());
			try {
    
    
				namingService.subscribe(properties.getService(), properties.getGroup(),
						Arrays.asList(properties.getClusterName()), eventListener);
			}
			catch (Exception e) {
    
    
				log.error("namingService subscribe failed, properties:{}", properties, e);
			}

			this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
					this::nacosServicesWatch, this.properties.getWatchDelay());
		}
	}

}

Integrate Eureka registry

Extension: The initialization of the Eureka Server context is implemented in SmartLifecycle#start

EurekaServerInitializerConfiguration

Integration Ribbon

SmartInitializingSingleton extension scenario - custom processing of Bean objects in the container

Called after all singleton beans are instantiated

Thinking: Why can @Bean modified RestTemplate plus @LoadBalanced achieve load balancing?

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    
    
    return new RestTemplate();
}

The extension of LoadBalancerAutoConfiguration
to SmartInitializingSingleton binds the load balancing logic interceptor LoadBalancerInterceptor for all restTemplates decorated with @LoadBalanced (using the @Qualifier qualifier)

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
		final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    
    
	return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    
    
		for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    
    
			for (RestTemplateCustomizer customizer : customizers) {
    
    
				customizer.customize(restTemplate);
			}
		}
	});
}
/**
 * Annotation to mark a RestTemplate or WebClient bean to be configured to use a
 * LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({
    
     ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier //Qualifier
public @interface LoadBalanced {
    
    
}

LoadBalancerInterceptor

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
    
    

		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
    
    
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
    
    
			return restTemplate -> {
    
    
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

Integrate Feign

FactoryBean extension scenario - hand over the proxy object generated by the interface to Spring management

Thinking: Why can the Feign interface be injected directly through @Autowired? How is the Feign interface handed over to Spring management?

@FeignClient(value = "order-service", path = "/order")
public interface OrderFeignService {
    
    

    @GetMapping(value = "/info/{productId}")
    public String findOrderInfoByProductId(@PathVariable("productId") Integer productId);
}
@SpringBootApplication
@EnableFeignClients(basePackages = "com.mx.use.feign")
@EnableHystrix
@EnableHystrixDashboard //开启 Hystrix 监控功能
public class OrderServiceApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    
    
	...
}

FactoryBean => FeignClientsRegistrar -> scan(FeignClient) -> registerFeignClient -> FeignClientFactoryBean

insert image description here

@Override
public Object getObject() {
    
    
	return getTarget(); //
}

Integrate sentinel

HandlerInterceptor extension scenario - enhancement of mvc request

AbstractSentinelInterceptor#preHandle

public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
    
    
    ...
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    
    
        try {
    
    
            String resourceName = getResourceName(request);

            if (StringUtil.isEmpty(resourceName)) {
    
    
                return true;
            }
            
            if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
    
    
                return true;
            }
            
            // Parse the request origin using registered origin parser.
            String origin = parseOrigin(request);
            String contextName = getContextName(request);
            ContextUtil.enter(contextName, origin);
            //Sentinel入口
            Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
            request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
            return true;
        } catch (BlockException e) {
    
    
            try {
    
    
                handleBlockException(request, response, e);
            } finally {
    
    
                ContextUtil.exit();
            }
            return false;
        }
    }
    ...
}

SmartInitializingSingleton&FactoryBean combined scene - dynamically assemble objects according to type

SentinelDataSourceHandler

#Sentinel持久化读数据源设计,利用了SmartInitializingSingleton扩展点
SentinelDataSourceHandler#afterSingletonsInstantiated
# 注册一个FactoryBean类型的数据源 
》SentinelDataSourceHandler#registerBean
》》NacosDataSourceFactoryBean#getObject
# 利用FactoryBean获取到读数据源
》》new NacosDataSource(properties, groupId, dataId, converter)

NacosDataSourceFactoryBean

public class NacosDataSourceFactoryBean implements FactoryBean<NacosDataSource> {
    
    
	...
	@Override
	public NacosDataSource getObject() throws Exception {
    
    
		...
		return new NacosDataSource(properties, groupId, dataId, converter);
	}
	...
}

SentinelDataSourceHandler

public class SentinelDataSourceHandler implements SmartInitializingSingleton {
    
    
	...
	@Override
	public void afterSingletonsInstantiated() {
    
    
		...
		//sentinel.nacos.config.serverAddr=${sentinel.nacos.config.serverAddr}
		//NacosDataSource
		AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties.getValidDataSourceProperties();
		abstractDataSourceProperties.setEnv(env);
		abstractDataSourceProperties.preCheck(dataSourceName);
		registerBean(abstractDataSourceProperties, dataSourceName+ "-sentinel-" + validFields.get(0) + "-datasource"); //NacosDataSource
		...
	}
	...
}

Integrating seata (Spring Aop)

AbstractAutoProxyCreator&MethodInterceptor Combination Scenario - Implementation Method Enhancement

GlobalTransactionScanner

insert image description here

GlobalTransactionalInterceptor

insert image description here

public class GlobalTransactionalInterceptor implements ConfigurationChangeListener, MethodInterceptor, SeataInterceptor {
    
    
	...

    @Override
    public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
    
    
        Class<?> targetClass =
            methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
        Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
        if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
    
    
            final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
            final GlobalTransactional globalTransactionalAnnotation =
                getAnnotation(method, targetClass, GlobalTransactional.class);
            final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
            boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
            if (!localDisable) {
    
    
                if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {
    
    
                    AspectTransactional transactional;
                    if (globalTransactionalAnnotation != null) {
    
    
                        transactional = new AspectTransactional(globalTransactionalAnnotation.timeoutMills(),
                            globalTransactionalAnnotation.name(), globalTransactionalAnnotation.rollbackFor(),
                            globalTransactionalAnnotation.noRollbackForClassName(),
                            globalTransactionalAnnotation.noRollbackFor(),
                            globalTransactionalAnnotation.noRollbackForClassName(),
                            globalTransactionalAnnotation.propagation(),
                            globalTransactionalAnnotation.lockRetryInterval(),
                            globalTransactionalAnnotation.lockRetryTimes());
                    } else {
    
    
                        transactional = this.aspectTransactional;
                    }
                    // 执行事务 transactionalTemplate.execute()
                    return handleGlobalTransaction(methodInvocation, transactional);
                } else if (globalLockAnnotation != null) {
    
    
                    return handleGlobalLock(methodInvocation, globalLockAnnotation);
                }
            }
        }
        return methodInvocation.proceed();
    }
	...
}
public class TransactionalTemplate {
    
    
    public Object execute(TransactionalExecutor business) throws Throwable {
    
    
        // 1. Get transactionInfo
        TransactionInfo txInfo = business.getTransactionInfo();
        if (txInfo == null) {
    
    
            throw new ShouldNeverHappenException("transactionInfo does not exist");
        }
        // 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
        GlobalTransaction tx = GlobalTransactionContext.getCurrent();

        // 1.2 Handle the transaction propagation.
        Propagation propagation = txInfo.getPropagation();
        SuspendedResourcesHolder suspendedResourcesHolder = null;
        try {
    
    
            switch (propagation) {
    
    
                case NOT_SUPPORTED:
                    // If transaction is existing, suspend it.
                    if (existingTransaction(tx)) {
    
    
                        suspendedResourcesHolder = tx.suspend();
                    }
                    // Execute without transaction and return.
                    return business.execute();
                case REQUIRES_NEW:
                    // If transaction is existing, suspend it, and then begin new transaction.
                    if (existingTransaction(tx)) {
    
    
                        suspendedResourcesHolder = tx.suspend();
                        tx = GlobalTransactionContext.createNew();
                    }
                    // Continue and execute with new transaction
                    break;
                case SUPPORTS:
                    // If transaction is not existing, execute without transaction.
                    if (notExistingTransaction(tx)) {
    
    
                        return business.execute();
                    }
                    // Continue and execute with new transaction
                    break;
                case REQUIRED:
                    // If current transaction is existing, execute with current transaction,
                    // else continue and execute with new transaction.
                    break;
                case NEVER:
                    // If transaction is existing, throw exception.
                    if (existingTransaction(tx)) {
    
    
                        throw new TransactionException(
                            String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
                                    , tx.getXid()));
                    } else {
    
    
                        // Execute without transaction and return.
                        return business.execute();
                    }
                case MANDATORY:
                    // If transaction is not existing, throw exception.
                    if (notExistingTransaction(tx)) {
    
    
                        throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
                    }
                    // Continue and execute with current transaction.
                    break;
                default:
                    throw new TransactionException("Not Supported Propagation:" + propagation);
            }

            // 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
            if (tx == null) {
    
    
                tx = GlobalTransactionContext.createNew();
            }

            // set current tx config to holder
            GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);

            try {
    
    
                // 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
                //    else do nothing. Of course, the hooks will still be triggered.
                beginTransaction(txInfo, tx);

                Object rs;
                try {
    
    
                    // Do Your Business
                    rs = business.execute();
                } catch (Throwable ex) {
    
    
                    // 3. The needed business exception to rollback.
                    completeTransactionAfterThrowing(txInfo, tx, ex);
                    throw ex;
                }

                // 4. everything is fine, commit.
                commitTransaction(tx);

                return rs;
            } finally {
    
    
                //5. clear
                resumeGlobalLockConfig(previousConfig);
                triggerAfterCompletion();
                cleanUp();
            }
        } finally {
    
    
            // If the transaction is suspended, resume it.
            if (suspendedResourcesHolder != null) {
    
    
                tx.resume(suspendedResourcesHolder);
            }
        }
    }
}

Guess you like

Origin blog.csdn.net/menxu_work/article/details/128000900