mx250最新驱动版本是多少mx250最新驱动版本是MX250 30.0.14.7304显卡驱动官方正式版,英特尔目前已向用户发布了最新的31.0.101.2115显卡驱动,此次更新不仅支持6-10代酷睿,而
spring 多线程 子线程出异常太多 导致程序停止(用的@Async注解)
软件
2024-05-02
Spring Boot中异步线程池@Async详解
1、消息队列MQ2、线程池处理。
我们来看看Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理。
在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。
Spring异步线程池的接口类,其实质是java.util.concurrent.Executor
Spring 已经实现的异常线程池:
Spring中用@Async注解标记的方法,称为异步方法。在spring boot应用中使用@Async很简单:
1、调用异步方法类上或者启动类加上注解@EnableAsync
2、在需要被异步调用的方法外加上@Async
3、所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;
启动类加上注解@EnableAsync:
在需要被异步调用的方法外加上@Async,同时类AsyncService加上注解@Service或者@Component,使其对象成为Spring容器管理的bean对象;
这里需要注意的是:
1、同一个类里面调用异步方法不生效:原因默认类内的方法调用不会被aop拦截,即调用方和被调用方是在同一个类中,是无法产生切面的,该对象没有被Spring容器管理。即@Async方法不生效。
解决办法:如果要使同一个类中的方法之间调用也被拦截,需要使用spring容器中的实例对象,而不是使用默认的this,因为通过bean实例的调用才会被spring的aop拦截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用这个引用调用本地的方法即可达到被拦截的目的
备注:这种方法只能拦截protected,default,public方法,private方法无法拦截。这个是spring aop的一个机制。
2、如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。
3、异步方法返回类型只能有两种:void和java.util.concurrent.Future。
1)当返回类型为void的时候,方法调用过程产生的异常不会抛到调用者层面,
可以通过注AsyncUncaughtExceptionHandler来捕获此类异常
2)当返回类型为Future的时候,方法调用过程产生的异常会抛到调用者层面
在Spring Boot主类中定义一个线程池,public Executor taskExecutor() 方法用于自定义自己的线程池,线程池前缀”taskExecutor-”。如果不定义,则使用系统默认的线程池。
上面我们通过ThreadPoolTaskExecutor创建了一个线程池,同时设置了如下参数:
核心线程数10:线程池创建时初始化的线程数
最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
缓冲队列200:用来缓冲执行任务的队列
允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁
线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
线程池对拒绝任务的处理策略:此处采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已被关闭,则会丢弃该任务
设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
也可以单独类来配置线程池:
只需要在@Async注解中指定线程池名即可
Bean文件配置: spring_async.xml
1. 线程的前缀为xmlExecutor
2. 启动异步线程池配置
启动类导入xml文件:
线 程池参数说明
1. ‘id’ : 线程名称的前缀
2. ‘pool-size’:线程池的大小。支持范围”min-max”和固定值(此时线程池core和max sizes相同)
3. ‘queue-capacity’ :排队队列长度
4. ‘rejection-policy’: 对方拒绝的任务处理策略
5. ‘keep-alive’ : 线程保护时间(单位秒)
上面也提到:在调用方法时,可能出现方法中抛出异常的情况。在异步中主要有有两种异常处理方法:
a) 、一种是在调用future的get时捕获异常;
b)、 在异常方法中直接捕获异常
实现AsyncConfigurer接口对异常线程池更加细粒度的控制
a) 创建线程自己的线程池
b) 对void方法抛出的异常处理的类AsyncUncaughtExceptionHandler
上面也提到:如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。
一般的错误OOM:OutOfMemoryError:unable to create new native thread,创建线程数量太多,占用内存过大.
解决办法:一般最好使用自定义线程池,做一些特殊策略, 比如自定义拒绝策略,如果队列满了,则拒绝处理该任务。
原文链接:https://blog.csdn.net/hguisu/article/details/106671893
spring异步方法@Async
在Spring Boot中,我们只需要使用@Async注解就能简单的将原来的同步函数变为异步函数。开启方法:启动类或者配置类上加@EnableAsync注解,然后在方法或者类上加@Async,类上加注解表示类中方法都实现异步调用。
工作原理:
spring 在扫描bean的时候会扫描方法上是否包含@async的注解,如果包含的,spring会为这个bean动态的生成一个子类,我们称之为代理类,代理类是继承我们所写的bean的,然后把代理类注入进来,那此时,在执行此方法的时候,会到代理类中,代理类判断了此方法需要异步执行,就不会调用父类的对应方法。spring自己维护了一个队列,他会把需要执行的方法,放入队列中,等待线程池去读取这个队列,完成方法的执行,从而完成了异步的功能。spring提供了默认线程池ThreadPoolTaskExecutor,支持手动配置属性。
需要注意的地方:
1.在同一个类中的方法调用,添加@async注解是失效的。原因是当你在同一个类中的时候,方法调用是在类中执行的,spring无法截获这个方法调用,也就不会在代理类里执行。
2.可能会导致循环依赖,spring本身会解决循环依赖,但是因为@Async使用代理模式,spring在检查第二级缓存和原始对象是否相等时发现不相等,会抛出异常。
3.无法获取请求上下文。
记一次因@Async引发的程序bug
事情的起因是微服务A通过feign调用微服务B的某个接口,报了形如下的异常负责微服务A的工程师小张就找到负责提供该接口的工程师小李,问小李是不是改动了接口,小李一脸无辜说他最近没对这个接口做任何改动,不过小李还是说道他排查一下。
小李排查的过程如下,他先通过swagger查看他提供给A服务接口是否存在,他一查发现他在swagger上看不到他提供给A服务的接口。于是他怀疑是不是有人动了他的代码,他就去查找最近的git提交记录,发现没人动他的代码,因为项目还没发布,都在测试阶段,他就根据项目集成的git-commit-id-maven-plugin插件定位到测试目前发布具体是哪个版本。(ps:对
git-commit-id-maven-plugin感兴趣的朋友,可以查看之前的文章聊聊如何验证线上的版本是符合预期的版本)。然后他将该版本的代码下到本地进行调试,他发现代码中提供给A的接口还在,target下的class也有提供给A的接口class,但诡异的是swagger就是没显示他提供出去的接口,他一度以为是swagger出了问题,于是他用postman直接请求他提供A的接口,发现报了404。然后他就叫负责同个微服务B的同事小王,也帮忙试一下,发现结果就是404。后面没招,小李就去求助他们项目资深同事小林。
小林的排查思路如下,他先走查一下小李的接口代码,发现他提供的接口实现层的方法上加了一个@Async,示例形如下
小林凭多年的经验直觉告诉小李说,应该是@Async引起。小李很斩钉截铁的说不可能啊,他@Async很早就加了,之前接口都可以访问的,小林一看小李说得那么肯定,他也不好打击小李。于是他接下来做了如下操作,先在项目中yml配置如下参数,开启springweb日志
然后在项目中加了形如下代码,来跟踪接口bean的类型
启动控制台,看日志形如下
发现确实没打印出相关requestMapping映射信息,这可以说明一点就是小李那个接口没有绑定到springmvc映射,也就是出现404的原因。接着观察控制台打印的bean,内容形如下
这很明显这个接口bean已经被jdk动态代理给替换。小李看到控制台打印的信息,若有所思,然后说,我把@Async去掉试下。小李把@Async去掉后,再观察下控制台
通过控制台可以发现,此时接口已经绑定到springmvc映射,而且打印出bean类型是真实对象bean。小李看到这个现象,也百思不得其解,他说道他之前确实是加了@Async,接口也能正常访问。于是小林就问一句,你确定你加了@Async,异步生效了吗,小李说开启spring异步,不都是加@Async吗。小林又问了一句,你在项目中开启异步,除了加@Async,还有做什么处理吗,小李说没了,他之前在项目使用异步就都是加了@Async,也能用了好好的,小林一听,基本上知道为什么小李之前@Async,接口还能正常访问了,小林为了验证想法,就问同负责该项目的小王,说你最近有加什么异步操作吗,小王说有,小林进一步问,你是怎么做的,小王说,他先加@EnabledAsyn,开启异步,然后在业务逻辑层上的方法上加@Async注解。小李一听,说原来使用@Async还要配合@EnabledAsyn啊,他之前都不知道
接着小李说 那在controller是不是就不能使用@Async注解了? ,小林说最好是把加@Async的逻辑挪到service层去处理,不过也不是controller就不能使用@Async注解了,接着小林为了验证这个想法,他把原来实现的接口类去掉,形如下
启动后,查看控制台
此时bean的类型如下
访问接口,打印内容如下
从控制台可以发现,都是http-nio-8080-exec-1线程触发,说明异步没生效,即@Async失效。后面对controller做了如下改造
访问接口,打印内容如下
这说明在controller其实也是可以用@Async,只是要额外做处理。所以建议是把@Async从controller中抽离出去,在新类中进行处理,示例如下
访问接口,打印内容
说明异步生效
从mvc日志
我们可以知道,controller映射处理是在RequestMappingHandlerMapping 这个类中,但具体是哪个方法进行处理呢,我们可以通过日志打印的信息,进行倒推,也可以基于spring的特性加断点调试,比如通过afterPropertiesSet这一启动扩展点调试起,就会发现RequestMappingHandlerMapping的映射处理是在
进行处理,具体是通过processCandidateBean进行处理
最终是通过detectHandlerMethods进行处理
这个里面就是做了实际注册。而执行detectHandlerMethods的前提是
即只有加了@Controller或者@RequestMapping的类会进行处理,而@RestController为啥也处理,点击
@RestController发现
他本质就是@Controller。但我们通过反射查找注解,正常只会查找一层,比如
他找到@RestController这一层,而不会找继续再找@RestController里面的@Controller,而AnnotatedElementUtils.hasAnnotation,这个注解方法就不一样,他是可以找到合并注解,即使是使用
@RestController,他还会继续找到里面的@Controller。因此这个方法对于找复合型注解很有用
当我们使用jdk动态代理时,因为父类上没加@Controller或者@RequestMapping,因此他不会被mvc进行映射处理,导致404。而使用cglib时,因为他是作为子类继承了目标类,因此他会继承目标类上的注解,因此当为cglib代理时,他会正常被mvc进行映射处理
这是因为加了@Async后,controller变成代理了,而当要异步处理方法,用this时,他使用的是目标对象,而非代理对象。这跟现在面试事务为啥事务失效的八股文基本是一个套路
本文主要讲@Async导致controller 404,同时也使@Async失效的原因。解决的推荐方法就是将@Async抽离出controller,新建一个service类进行处理。
Spring循环依赖引出的问题(转)
源起在开发过程中,遇到需要把方法调用改为异步的情况,本来以为简单得加个@Asyn在方法上就行了,没想到项目启动的时候报了如下的错误:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'customerServiceImpl':
Bean with name 'customerServiceImpl' has been injected into other beans [customerServiceImpl,followServiceImpl,cupidService] in its raw version as part of a circular reference,
but has eventually been wrapped. This means that said other beans do not use the final version of the bean.
This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
看了下好像报的是循环依赖的错误,但是Spring单例是支持循环依赖的,当时一脸懵逼。
拿着报错去百度了下,说是多个动态代理导致的循环依赖报错,也找到了报错的地点,但是还是不明白为什么会这样,所以打算深入源码探个究竟,顺便回顾下Bean的获取流程和循环依赖的内容。
模拟场景
用SpringBoot新建一个demo项目,因为原项目是有定义切面的,这里也定义一个切面:
@Aspect
@Component
public class TestAspect {
@Pointcut("execution(public * com.example.demo.service.CyclicDependencyService.sameClassMethod(..))")
private void testPointcut() {}
@AfterReturning("testPointcut()")
public void after(JoinPoint point) {
System.out.println("在" + point.getSignature() + "之后干点事情");
}
}
然后新建一个注入自己的Service构成循环依赖,然后提供一个方法满足切点要求,并且加上@Async注解:
@Service
public class CyclicDependencyService {
@Autowired
private CyclicDependencyService cyclicDependencyService;
public void test() {
System.out.println("调用同类方法");
cyclicDependencyService.sameClassMethod();
}
@Async
public void sameClassMethod() {
System.out.println("循环依赖中的异步方法");
System.out.println("方法线程:" + Thread.currentThread().getName());
}
}
还有别忘了给Application启动类加上@EnableAsync和@EnableAspectJAutoProxy:
@EnableAsync
@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
最后打好断点,开始debug。
debug
从Bean创建的的起点--AbstractBeanFactory#getBean开始
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
首先会在缓存中查找,DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference):
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这里一共有三级缓存:
singletonObjects,保存初始化完成的单例bean实例;
earlySingletonObjects,保存提前曝光的单例bean实例;
singletonFactories,保存单例bean的工厂函数对象;
后面两级都是为了解决循环依赖设置的,具体查找逻辑在后续其他情况下调用会说明。
缓存中找不到,就要创建单例:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
调用DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory):
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
beforeSingletonCreation(beanName);
...
singletonObject = singletonFactory.getObject();
...
afterSingletonCreation(beanName);
...
addSingleton(beanName, singletonObject);
...
}
创建前后分别做了这几件事:
前,beanName放入singletonsCurrentlyInCreation,表示单例正在创建中
后,从singletonsCurrentlyInCreation中移除beanName
后,将创建好的bean放入singletonObjects,移除在singletonFactories和earlySingletonObjects的对象
创建单例调用getSingleton时传入的工厂函数对象的getObject方法,实际上就是createBean方法,主要逻辑在AbstractAutowireCapableBeanFactory#doCreateBean中:
...
instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
可以看到报错就是在这个方法里抛出的,那么这个方法就是重点中的重点。
首先实例化单例,instantiate,只是实例化获取对象引用,还没有注入依赖。我debug时记录的bean对象是 CyclicDependencyService@4509 ;
然后判断bean是否需要提前暴露,需要满足三个条件:1、是单例;2、支持循环依赖;3、bean正在创建中,也就是到前面提到的singletonsCurrentlyInCreation中能查找到,全满足的话就会调用DefaultSingletonBeanRegistry#addSingletonFactory把beanName和单例工厂函数对象(匿名实现调用AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法)放入singletonFactories;
接着就是注入依赖,填充属性,具体怎么注入这里就不展开了,最后会为属性cyclicDependencyService调用DefaultSingletonBeanRegistry.getSingleton(beanName, true),注意这里和最开始的那次调用不一样,isSingletonCurrentlyInCreation为true,就会在singletonFactories中找到bean的单例工厂函数对象,也就是在上一步提前暴露时放入的,然后调用它的匿名实现AbstractAutowireCapableBeanFactory#getEarlyBeanReference:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
方法逻辑就是挨个调用实现了SmartInstantiationAwareBeanPostProcessor接口的后置处理器(以下简称BBP)的getEarlyBeanReference方法。一个一个debug下来,其他都是原样返回bean,只有AnnotationAwareAspectJAutoProxyCreator会把原bean( CyclicDependencyService@4509 )存在earlyProxyReferences,然后将bean的代理返回(debug时记录的返回对象是 CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740 )并放入earlySingletonObjects,再赋给属性cyclicDependencyService。
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
属性填充完成后就是调用初始化方法AbstractAutowireCapableBeanFactory#initializeBean:
...
invokeAwareMethods(beanName, bean);
...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
...
invokeInitMethods(beanName, wrappedBean, mbd);
...
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
...
初始化主要分为这几步:
如果bean实现了BeanNameAware、BeanClassLoaderAware或BeanFactoryAware,把相应的资源放入bean;
顺序执行BBP的postProcessBeforeInitialization方法;
如果实现了InitializingBean就执行afterPropertiesSet方法,然后执行自己的init-method;
顺序执行BBP的postProcessAfterInitialization。
debug的时候发现是第4步改变了bean,先执行AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
这里会获取并移除之前存在earlyProxyReferences的bean( CyclicDependencyService@4509 ),因为和当前bean是同一个对象,所以什么都没做直接返回。随后会执行AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization:
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}
先判断bean是否有需要代理,因为CyclicDependencyService有方法带有@Async注解就需要代理,返回代理对象是 CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273 。
返回的代理对象赋值给AbstractAutowireCapableBeanFactory#doCreateBean方法内的exposedObject,接下来就到了检查循环依赖的地方了:
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
首先从earlySingletonObjects里拿到前面属性填充时放入的bean代理( CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740 ),不为空的话就比较bean和exposedObject,分别是 CyclicDependencyService@4509 和 CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273 ,很明显不是同一个对象,然后会判断allowRawInjectionDespiteWrapping属性和是否有依赖的bean,然后判断这些bean是否是真实依赖的,一旦存在真实依赖的bean,就会抛出BeanCurrentlyInCreationException。
总结
总结下Spring解决循环依赖的思路:在创建bean时,对于满足提前曝光条件的单例,会把该单例的工厂函数对象放入三级缓存中的singletonFactories中;然后在填充属性时,如果存在循环依赖,必然会尝试获取该单例,也就是执行之前放入的工厂函数的匿名实现,这时候拿到的有可能是原bean对象,也有可能是被某些BBP处理过返回的代理对象,会放入三级缓存中的earlySingletonObjects中;接着bean开始初始化,结果返回的有可能是原bean对象,也有可能是代理对象;最后对于满足提前曝光的单例,如果真的有提前曝光的动作,就会去检查初始化后的bean对象是不是原bean对象是同一个对象,只有不是的情况下才可能抛出异常。重点就在于 存在循环依赖的情况下,初始化过的bean对象是不是跟原bean是同一个对象 。
从以上的debug过程可以看出,是AsyncAnnotationBeanPostProcessor这个BBP在初始化过程中改变了bean,使得结果bean和原bean不是一个对象,而AnnotationAwareAspectJAutoProxyCreator则是在填充属性获取提前曝光的对象时把原始bean缓存起来,返回代理的bean。然后在初始化时执行它的postProcessAfterInitialization方法时如果传入的bean是之前缓存的原始bean,就直接返回,不进行代理。如果其他BBP也都没有改变bean的话,初始化过后的bean就是跟原始bean是同一个对象,这时就会把提前曝光的对象(代理过的)作为最终生成的bean。
https://segmentfault.com/a/1190000018835760
spring常用注解
一、组件注解1、 @Component(“xxx”)
指定某个类是容器的bean, @Component(value="xx") 相当于 ,其中 value 可以不写。
用于标注类为spring容器bean的注解有四个, 主要用于区别不同的组件类,提高代码的可读性:
a、 @Component, 用于标注一个普通的bean
b、 @Controller 用于标注一个控制器类(控制层 controller)
c、 @Service 用于标注业务逻辑类(业务逻辑层 service)
d、 @Repository 用于标注DAO数据访问类 (数据访问层 dao)
对于上面四种注解的解析可能是相同的,尽量使用不同的注解提高代码可读性。
注解用于修饰类,当不写value属性值时,默认值为类名首字母小写。
2、 @Scope(“prototype”)
该注解和 @Component 这一类注解联合使用,用于标记该类的作用域,默认 singleton 。
也可以和 @Bean 一起使用,此时 @Scope 修饰一个方法。关于@Bean稍后有说明
3、 @Lazy(true)
指定bean是否延时初始化,相当于 ,默认false。@Lazy可以和@Component这一类注解联合使用修饰类,也可以和@Bean一起使用修饰方法
注 :此处初始化不是指不执行 init-method ,而是不创建bean实例和依赖注入。只有当该bean(被@Lazy修饰的类或方法)被其他bean引用(可以是自动注入的方式)或者执行getBean方法获取,才会真正的创建该bean实例,其实这也是BeanFactory的执行方式。
4、 @DepondsOn({“aa”,“bb”})
该注解也是配合 @Component 这类注解使用,用于强制初始化其他bean
上面的代码指定,初始化bean “userAction"之前需要先初始化“aa”和“bb”两个bean,但是使用了@Lazy(true)所以spring容器初始化时不会初始化"userAction” bean。
5、 @PostConstructor和@PreDestroy
@PostConstructor 和 @PreDestroy 这两个注解是j2ee规范下的注解。这两个注解用于修饰方法,spring用这两个注解管理容器中spring生命周期行为。
a、 @PostConstructor 从名字可以看出构造器之后调用,相当于 。就是在依赖注入之后执行
b、 @PreDestroy 容器销毁之前bean调用的方法,相当于
6、 @Resource(name=“xx”)
@Resource 可以修饰成员变量也可以修饰set方法。当修饰成员变量时可以不写set方法,此时spring会直接使用j2ee规范的Field注入。
@Resource有两个比较重要的属性,name和type
a、 如果指定了name和type,则从Spring容器中找到唯一匹配的bean进行装配,找不到则抛出异常;
b、 如果指定了name,则从spring容器查找名称(id)匹配的bean进行装配,找不到则抛出异常;
c、 如果指定了type,则从spring容器中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
d、 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配
如果没有写name属性值时
a、 修饰成员变量,此时name为成员变量名称
b、 修饰set方法,此时name 为set方法的去掉set后首字母小写得到的字符串
7、 @Autowired(required=false)
@Autowired可以修饰构造器,成员变量,set方法,普通方法。@Autowired默认使用byType方式自动装配。required标记该类型的bean是否是必须的,默认为必须存在(true)。
可以配合 @Qualifier(value="xx") ,实现按beanName注入:
a、 required=true(默认),为true时,从spring容器查找和指定类型匹配的bean,匹配不到或匹配多个则抛出异常
b、 使用 @Qualifier("xx") ,则会从spring容器匹配类型和 id 一致的bean,匹配不到则抛出异常
@Autowired会根据修饰的成员选取不同的类型:
a、 修饰成员变量。该类型为成员变量类型
b、 修饰方法,构造器。注入类型为参数的数据类型,当然可以有多个参数
8、demo
业务逻辑层:
数据访问层:
测试类:
输出结果:
可以看到虽然UserDao 使用@Lazy,但是还是在spring容器初始化的时候还是创建了UserDao实例。原因很简单,因为在UserService中需要注入UserDao,所以在此时创建的UserDao实例也属于延时初始化。
在上面我们还使用了两个接口InitializingBean 和DisposableBean,这两个接口用于管理 singleton 作用域的bean的生命周期,类似init-method和destroy-method。不同之处就是调用的循序不一致:
a、 初始化调用顺序 :@PostConstructor > InitializingBean > init-method 用于指定bean依赖注入后的行为
b、 销毁调用顺序 @PreDestroy > DisposableBean > destroy-method 用于定制bean销毁之前的行为
该注解是AspectJ中的注解,并不是spring提供的,所以还需要导入aspectjweaver.jar,aspectjrt.jar,除此之外还需要依赖aopalliance.jar
依赖包:
UserDao.java
配置文件 applicationContext.xml:
测试类:
1、 @Aspect
修饰Java类,指定该类为切面类。当spring容器检测到某个bean被@Aspect修饰时,spring容器不会对该bean做增强处理(bean后处理器增强,代理增强)
2、 @Before
修饰方法,before增强处理。用于对目标方法(切入点表达式表示方法)执行前做增强处理。可以用于权限检查,登陆检查。
常用属性:
value: 指定切入点表达式 或者引用一个切入点
对com.example.aop 包下所有的类的所有方法做 before增强处理:
结果:
如果同一条切入点表达式被使用多次,可以使用更友好的方式。定义一个切入点:
增强方法可以接受一个JoinPoint 类型的参数,用于获取被执行目标方法的一下属性。
结果:
3、 @AfterReturning
修饰方法,afterreturning增强处理。目标方法正常结束后做增强处理。
常用属性:
a、 pointcut/value:指定切入点表达式
b、 returning:指定一个参数名,用于接受目标方法正常结束时返回的值。参数名称需要在增强方法中定义同名的参数。
注意:
a、 如果使用了returning 。那么增强方法中的数据类型必须是返回结果的类型或者父类型,否则不会调用该增强处理。
b、 使用了returning 还可以用来 修改返回结果 。
以上面的例子来说,目标方法返回结果类型应该满足下面的条件
修改返回值:
结果:
可以看到 AfterReturning 修改了返回结果。
4、 @AfterThrowing
修饰方法,afterthrowing增强处理。当目标程序方法抛出 异常或者异常无法捕获时,做增强处理。
常用属性:
a、 pointcut/value :指定切入点表达式
b、 throwing:指定一个形参,在增强方法中定义同名形参,用于访问目标方法抛出的异常
参数类型必须是 Throwable 的子类,同样也会有上面@AfterReturning 参数类型匹配的问题。
5、 @After
修饰方法 ,after增强处理。无论方法是否正常结束,都会调用该增强处理(@After= @AfterReturning+@AfterThrowing)。但是该增强方式无法获取目标方法的返回结果,也获取目标方法抛出的异常。所以一般用于进行释放资源,功能类似于 finally。
常用属性:
a、 value :指定切入点表达式
结果:
从上面的结果来看 After 增加处理 ,因为不能接受返回结果作为参数,所以不能修改返回结果。
6、 @Around
修饰方法, around增强处理。该处理可以目标方法执行之前和执行之后织入增强处理(@Before+@AfterReturning)。
Around增强处理通常需要在线程安全的环境下使用,如果@Before和@AfterReturning可以处理就没必要使用@Around。
常用属性:
a、 value :指定切入点表达式
当定义一个Aound增前处理时,增强方法第一形参需要时ProceedingJoinPoint类型。ProceedingJoinPoint有一个Object proceed()方法,用于执行目标方法。当然也可以为目标方法传递数组参数,来修改目前方法的传入参数。
around小结:
a、 Around增强处理通常需要 在线程安全 的环境下使用
b、 调用 proceed()可以获取返回结果,所以可以修改目标方法的返回值
c、 proceed(Object[] var1) 可以修改入参,修改目标方法的入参
d、 可以进行目标方法执行之前和执行之后织入增强处理
around 和 afterReturning 都可以修改返回结果。不过两者的原理不同:
a、 around:可以任意修改,或者返回不相关的值。这个返回值完全可以自主控制
b、 afterReturning,通过方法参数 ,使用对象引用的方式来修改对象。修改对象引用地址那么修改时无效的
除此之外从输出结果来看,增强处理是有序的:
around 和 afterReturning小结:
a、 只有 around 和 afterReturning 可以获取并修改返回结果。需要注意两种方式修改的区别。
b、 around 需要线程安全
c、 虽然增强处理都需要 切入点表达式,并不是都支持 pointcut 属性,所以最好都是用value 属性指定。当注解只需要value属性时,value可以省略
7、 @Pointcut
修饰方法,定义一个切入点表达式用于被其他增强调用。使用该方式定义切入点方便管理,易复用。
切入点方法定义和测试方法定义类似,具有以下特点:
a、 无返回值 (void)
b、 无参数
c、 方法体为空
d、 方法名就是切入点名称
e、 方法名不能为 execution
切入点表达式
切入点表达式可以通过 && 、 || 、 ! 连接
1)、execution 表达式:
2)、within 表达式:
a、匹配指定类下的所有方法。
b、匹配执行包及其子包下所有类的所有方法。
所以within可以看做execution的简写,不需要指定返回类型、方法名、参数( 最小作用单位是类 )
3)、 @annotation:匹配使用指定注解修饰的目标方法;
匹配使用@CustomMethodAnnotation注解的目标方法。
4)、 @within: 用于匹配使用指定注解修饰的类下的所有方法
within 作用范围是类,@within的作用范围与其一致。不同的是@within 指定的不是类而是注解
匹配使用@ResponseBody 注解的类 下的所有方法。
AOP小结:
1)、 Around增强处理通常需要 在线程安全 的环境下使用
2)、 使用 around 和 afterReturning 可以获取并修改返回结果
3)、 增强处理指定 切入点表达式时,最好使用value 属性
4)、 切入点 名称(方法名)不能为 execution
5)、 AfterReturning 指定了 returning 属性接受目标方法返回结果,注意 参数类型需要和返回结果类型一致(满足 resutType instanceof argsType )
增强方式的顺序:
1、 @Bean(name=“xxx”)
修饰方法,该方法的返回值为spring容器中管理的bean。当然该注解和上面的@Component效果一样,主要用于做区分。
@Bean 通常使用在 @Configuration 修饰的配置类中,该注解功能相当于 元素
常用的属性:
a、 name:bean id 。name可以省略,省略时bean名称为方法名。也可以指定多个名称(逗号隔开)。
b、 autowire: 是否自动注入,默认Autowire.NO
c、 initMethod:bean的初始化方法。在依赖注入之后执行
d、 destroyMethod: spring容器关闭时bean调用的方法
当然 @Bean 还可以配合 @Scope 指定bean的作用域
2、 @ConfigurationProperties
用于从属性文件中获取值 application.properties 或者 application.yml 。当然了 如果在配置文件中引入其他配置文件,也可以获取到属性值。
包含的属性:
a、 value | prefix 两者互为别名。指定前缀,默认为""
b、 ignoreUnknownFields:默认为true。是否忽略未知字段,当实体中的字段在配置文件中不存在时,是忽略还是抛出异常
c、 ignoreInvalidFields: 默认false。 是否忽略不合法的字段,此处的不合法是指类型不合适,配置文件中存在改配置但是无法转化为指定的字段类型。
Mybatis属性配置
application.properties:
ConfigurationProperties 可以配置前缀,然后会根据实体的变量名拼接前缀,去配置文件中查询配置。
3、 @Configuration
修饰一个Java类,被修饰的类相当于一个xml配置文件。功能类似于 。在springboot中大量使用了该注解,该注解提供了一种使用Java类方式配置bean。
可以发现 @Configuration使用了@Component 注解修饰。
实例 :
配置Mybatis会话工厂
4、 @Import
功能和 类似,修饰Java类,用于向当前类导入其他配置类。 可以导入多个配置文件,通常用于导入不在包扫描范围内的配置文件。可以被扫描的配置类可以直接访问,没有必要使用@Import 导入。
比如 SpringBoot的启动类指定的包扫描路径为 com.example
数据库的配置文件在 com包下。
在MyBatisConfig 中引入 DataSourceConfig, 就会解析DataSourceConfig。将解析出的Bean交给容器管理
5、 @ImportResource
修饰Java类,用于向类引入xml配置文件。
用于导入包含bean定义的配置文件,功能和 类似。默认情况下可以处理后缀为 .groovy 和.xml 的配置文件
6、 @Value("${expression}")
修饰成员变量或者 方法、构造器的参数,用于属性值注入(在配置文件中配置的值)。
注意: @Value不能对 static 属性注入。
如果的确需要注入到静态变量,可以通过以下方式间接进行注入:
1)、设置一个私有静态 实例
2)、通过构造函数或者 @PostConstruct 注解为 静态实例 赋值,指向本身(this)
3)、对成员属性注入内容
4)、提供静态方法,使用静态实例获取成员属性
7、@PropertySource(value=“classpath:jdbc.properties”)
该注解用来加载属性文件。
常用属性:
a、 ignoreResourceNotFound: 当资源文件找不到的时候是否会忽略该配置,而不是抛出错误。一般用于可选项
b、 encoding : 资源文件使用什么编码方式
c、 value : 指定属性文件位置。可以配置多个属性文件,不可以使用通配符。
在 PropertySource 中可以指定多个路径,并且会将属性文件中的值加载到 Environment 中。
@ConfigurationProperties 和 @PropertySource
它们的使用有一些差异:
1)、 @PropertySource 使用该注解加载的是 相对独立的属性文件,可以同时加载多个文件 (xxx.properties),而且 不支持自动注入 , 不支持前缀注入
2)、 @ConfigurationProperties 用于加载配置文件(application.properties | application.yml)。该注解功能更强大:
a、 支持前缀注入 ( prefix )
b、 相同属性名的自动注入
c、 $("") 支持EL表达式注入
应用实例:
在以往的开发中通常会将数据库连接信息存放在单独的属性文件中(jdbc.properties)。而在spring boot 中我们会将数据库的信息存放在配置文件中,这会极大便利开发工作。
jdbc.properties:
可以通过 @Value 注解将配置文件的值注入到实体类中
也可以注入Environment ,通过Environment 获取值
1、 @ResponseBody
控制器方法返回值会使用 HttpMessageConverter 进行数据格式化,转化为JSON字符串。
同样的 ResponseBodyAdvice: 针对使用@ResponseBody的注解的类,方法做增强处理。
2、 @RestController
@RestController = @Controller + @ResponseBody , 所以通常直接使用@RestController 注解
3、 @RequestBody
从Reuqest请求体中获取内容,绑定到方法的指定参数上。 SpringMVC 使用HttpMessageConverter 接口将请求体中的数据转化为方法参数类型。
SpringMVC 给用户对参数的处理提供了很大支配权。 我们可以使用 接口RequestBodyAdvice 来实现对参数进行拦截处理。
注意
1)、 RequestBodyAdvice : 针对所有以@RequestBody的参数做处理
2)、 自定义的处理对象类上必须得加上@ControllerAdvice注解!
利用此功能我们可以做以下处理工作:
1)、参数做解密处理。
2)、修改接受的参数数据。
4、 @RequestParam
从Request请求中获取指定的参数。
可以设置的属性:
1)、 required : 默认为true 参数必须存在 。参数不存在时抛出异常(MissingServletRequestParameterException). 提示信息
2)、 defaultValue : 设置参数默认值。 当参数没有提供或者为空值时生效, 包含隐式定义 required=false
3)、 name | value , 互为别名的属性, 绑定请求中的参数名。 request.getParameter(name);
5、 @RequestMapping
用于设置 请求 和 Method 的映射关系。指明何种请求可以和方法匹配
可配置属性值:
1)、 path、value、 name, 互为别名,设置可以处理的url。
2)、 consumes,字符串数组。 指定可以处理的 媒资类型,仅当请求头中的 Content-Type 与其中一种媒体类型匹配时,才会映射请求。所以该配置会缩小可匹配的请求。 当url 匹配但是consumes不匹配时, 状态码415。 不设置的话,表示不限制媒资类型,参数的具体使用何种方式解析,SpringMVC会选择合适的处理器处理。
3)、 produces,字符串数组。 生成的媒资类型,该属性会影响实际的输出类型。和consumes一样,改配置会缩小匹配的范围。 只有当请求头中的 Accept 与 配置的任意一个媒资类型匹配时,才会映射请求。 当url 匹配与consumes不匹配时, 状态码406 。 比如:为了生成UTF-8编码的JSON响应,应使用 MediaType.APPLICATION_JSON_UTF8_VALUE。
相关文章
- 详细阅读
-
芸惠优选这个小程序是什么平台?详细阅读
云货优选是什么平台演示机型:Iphone 12系统版本:iOS14APP版本:云货优选2.0.0 云货优选是广州微革网络科技有限公司自主研发的新型移动电商平台,于2018年5月正式上线。网络购物小
-
编写一个vb程序详细阅读
VB 编写一个程序?dim a(2) as integer,tmp as integer a(0)=2:a(1)=3:a(2)=1 print a(0);a(1);a(2) if a(0)
-
求大神帮忙,这个怎么编程,或者怎么用详细阅读
求excel大神帮忙录制个宏或者用公式实现下面的问题非常高兴为您解答问题,其实您这个问题不用录制宏,当排完序后,设置一个公式就可以了。还没有想到更好的办法,需要加一列进行辅
- 详细阅读
-
bluereporter是什么程序详细阅读
Blue Ripple sound是什么这个是一个音效模拟软件,为游戏提供“更加真实”的音效(然而并不不真实)。软件申明不会改变游戏以外的声音,但是网上很多人说会改变系统音效。不喜欢可
-
net显示另一个程序正在使用此文件,详细阅读
解压文件的时候出现“另一个程序正在使用此文件,进程无法访问”是什么意思因为安装所需要调用的文件在被另一个程序正在使用,所以无法调用,也就无法正常安装,建议重新启动电脑,或
-
为什么我的3Dmax打开3D文件时有的详细阅读
3d打开时显示发生错误,应用程序将立即关闭怎么办?1.检查电脑是否存在病毒,请使用百度卫士进行木马查杀。
2.系统文件损坏或丢失,盗版系统或Ghost版本系统,很容易出现该问题。建 -
C++编程答题遇到问题-图灵编程(求助详细阅读
求答案,c语言C++和java都是后来出现的,都不是纯OO,第一个纯OO语言是1972年出现的Smalltalk。 诸如“对象”和“对象的属性”这样的概念,可以一直追溯到1950年代初。它们首先出
-
编程题8255芯片中的灯泡题他的取反详细阅读
单片机 思考题与习题 急 谢谢。。。。1、分4组,分别由PSW中RS1、RS0控制,每组8个单元,复位后的寄存器是第0组。能被8整除的都可以进行位寻址。EA:片内外程序存储器片选端,ALE: