1 简介
之前我一直以为AOP核心后置处理器的前置处理方法并没有做什么具体工作,但在写《【spring事务源码学习】— spring事务核心组件创建过程》这篇文章时,跟踪源码发现事务的核心后置处理器同AOP的核心后置处理器一样也实现了InstantiationAwareBeanPostProcessor接口,并且会走同一段前置处理逻辑,这让我意识到该前置处理方法肯定不是像我想像的这么简单。。。
通过上篇文章《【bean的生命周期】— InstantiationAwareBeanPostProcessor接口简介》的铺垫可以知道,如果某个后置处理器实现了InstantiationAwareBeanPostProcessor接口,其实相当于它还实现了BeanPostProcessor接口,重写这两个接口的如下四个方法,可以对业务bean的创建前后和初始化前后分别进行拦截处理。
- BeanPostProcessor接口的两个抽象方法
- postProcessBeforeInitialization(…) — 作用于业务bean初识化前 — AOP+事务在这里都没做什么
- postProcessAfterInitialization(…) — 作用于业务bean初始化后
- InstantiationAwareBeanPostProcessor接口的两个抽象方法
- postProcessBeforeInstantiation(…) — 作用于业务bean创建之前
- postProcessAfterInstantiation(…) — 作用于业务bean创建之后 — AOP+事务在这里都没做什么
《【spring事务源码学习】— spring事务核心组件创建过程》那篇文章里给过AOP和事务核心后置处理器的继承关系图。从图中可以看到这两个核心后置处理器也用到了模版方法,并且一层层的跟踪源码可以知道,这两个核心后置处理器真正对业务bean创建前后和初始化前后进行拦截的代码其实在它们继承的同一个抽象类里,这个抽象类就是AbstractAutoProxyCreator。
翻看AbstractAutoProxyCreator 抽象类的源码还可以知道,其实 真正的拦截逻辑只有两块即业务bean创建之前和业务bean初始化之后。
这篇文章将简单讲解一下AOP和事务核心后置处理器的前置处理方法。
2 前置处理方法的入口
入口仍然从下面的代码开始:
所在类: AbstractAutowireCapableBeanFactory
所在方法: createBean
try {
//源码注释: Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
//实际上:一股情况下在此处不会生成代理对象--- 为什么呢???
//因为在此处对象其实根本还没有生成,所以不管是jdk代理还是cglib代理都无法代理一个没生成的对象。
//但是这一步并不是什么都不做,其实对于AOP和事务这一步都是比较关键的一步,
//--------------AOP:解析切面信息和通知并进行缓存,
//--------------事务:验证事务的几大组件是否被正确注入到IOC容器
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
3 AOP的前置处理方法
3.1 源码追踪
3.1.1 applyBeanPostProcessorsBeforeInstantiation
跟进resolveBeforeInstantiation(…)方法,可以进入到如下源码:
所在类:AbstractAutowireCapableBeanFactory
@Nullable
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
//遍历所有的BeanPostProcessor,并走实现了InstantiationAwareBeanPostProcessor接口的processor的前置处理方法
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}
需要注意的是:
其实它和BeanPostProcessor一样 — 《【bean的生命周期】BeanPostProcessor简介》,每一个单实例业务bean
创建的时候都会走这个方法,也就是说都会被其进行拦截。
3.1.2 postProcessBeforeInstantiation
继续跟进代码会走到下面的方法:
所在类:AbstractAutoProxyCreator
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
//判断是否是基础类型或者是否需要被跳过
//在AOP的逻辑下isInfrastructureClass其实就是判断是不是Aspect类,如果是肯定就直接进入if条件返回null了
//shouldSkip是一个比较重要的方法,AOP注解的解析其实就在这里
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
//下面的方法一般不会走,这里不再细究
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
3.1.3 AOP的shouldSkip方法 — AOP切面+通知解析的入口
继续跟进代码会走到下面的方法:
所在类:AspectJAwareAdvisorAutoProxyCreator
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
//利用核心后置处理器去解析所有的通知
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//如果解析到了通知,进行遍历
for (Advisor advisor : candidateAdvisors) {
//如果通知实现了AspectJPointcutAdvisor接口且通知的名字与传入的beanName一致返回true
if (advisor instanceof AspectJPointcutAdvisor &&
((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
//否则调用父类的方法
return super.shouldSkip(beanClass, beanName);
}
需要注意的是:
事务的核心后置处理器并没有继承AspectJAwareAdvisorAutoProxyCreator,自然也不会走这个方法,事务这一块的逻辑走的是抽象类AbstractAutoProxyCreator中的shouldSkip方法。
3.1.4 findCandidateAdvisors
继续跟进代码会走到下面的方法:
所在类:AnnotationAwareAspectJAutoProxyCreator(AOP真正的核心后置处理器)
@Override
protected List<Advisor> findCandidateAdvisors() {
//其实AOP通过这个方法返回的是一个空的list集合 --- 具体的不再细究
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
//AOP通过该方法找到所有的通知
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
3.1.5 buildAspectJAdvisors — AOP中真正解析切面+通知的方法
继续跟进代码,就进入到解析Aspect面获取通知的方法了:
所在类:BeanFactoryAspectJAdvisorsBuilder(AOP中真正解析切面+通知的方法)
/**
* Look for AspectJ-annotated aspect beans in the current bean factory,
* and return to a list of Spring AOP Advisors representing them.
* <p>Creates a Spring Advisor for each AspectJ advice method.
* @return the list of {@link org.springframework.aop.Advisor} beans
* @see #isEligibleBean
*/
public List<Advisor> buildAspectJAdvisors() {
//用于保存切面的名称,该aspectNames是类级别的缓存,用于缓存已经解析出来的切面信息
List<String> aspectNames = this.aspectBeanNames;
//第一次进该类的这个方法时,this.aspectBeanNames肯定为空,之后再进来就不会为空了
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
//用于保存所有解析出来的Advisor对象
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
//注意:
//aop功能中在这里传入的是0bject.class,代表去IOC容器中获取利所有的组件的名称,然后再经过一一的进行遍历
//这个过程肯定是十分消耗性能的。所以spring在这里加入了保存切面信息的缓存。
//事务也存在类似的逻辑,但是事务传入的不是Object.class,而是Advisor.class,因为事务的逻辑是直接去IOC里
//找Advisor类型的对象,不会耗费啥性能,所以事务并没有在这块逻辑里加缓存来保存事务相关的Advisor对象
但是事务功能不一样,事务模块的功能是直接去容器中获取Advisor类型的,选择范围小,切不消耗性能。所以
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
//遍历从IOC容器中获取到的所有bean的名称
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
// We must be careful not to instantiate beans eagerly as in this case they
// would be cached by the Spring container but would not have been weaved.
//通过beanName去容器中获取对应的class对象
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType == null) {
continue;
}
//根据class对象判断是不是切面
if (this.advisorFactory.isAspect(beanType)) {
//如果是切面, 将其加入到缓存中
aspectNames.add(beanName);
//把beanName和class对象构建成为一个AspectMetadata对象
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
//构建切面注解的实例工厂
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//解析当前切面类获取所有通知
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
//加入到缓存
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
//将获取到的通知加入到advisors集合
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
//给this.aspectBeanNames赋值,下次在进入该类的该方法,就不会进入这个if分支了。
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
//如果没找到切面类,返回一个空List
if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
List<Advisor> advisors = new ArrayList<>();
//能走到这里说明已经找到了所有的通知----其实AOP解析后的通知就缓存到了this.advisorsCache这个Map里
//AOP的后置处理器到时候还会调用该方法,并从这里获取到所有的通知,然后遍历这些缓存的通知并根据通知进行创建代理对象
//目标对象调用时,应该也是从这里获取缓存的通知,然后再将通知转换为方法拦截器 ---> 这里联系了一下前面的文章
for (String aspectName : aspectNames) {
//将所有的通知取出加入到advisors集合
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}
总之
通过该方法解析获得了所有的通知。
3.2 小结
这里就不做过多总结了,因为想接下来再讲解一下事务的前置处理方法。
- AOP核心后置处理器的前置处理方法一般并不会返回一个代理对象,而是 在这里对IOC容器中的所有bean定义进行遍历 ,获取到所有的切面,解析出所有的通知,并缓存起来;
- 当然这整个过程只会发生一次,其实应该很容易想到,这一次的触发时机正是创建第一个业务bean的过程中。
按照InstantiationAwareBeanPostProcessor的处理逻辑它会对每一个
单实例业务bean
的创建进行拦截 —> 第一个业务bean创建过程中会将所有的通知解析出来,并缓存起来,—> 然后再创建其他非Aspect业务bean时,每次都会从缓存里获取到所有的通知,如果当前bean对象正好是AOP的目标对象,那就可以直接拿着获取到的通知,调用动态代理机制对当前对象进行代理创建了。
4 事务的前置处理方法
反反复复跟了n多遍,发现事务的前置处理方法貌似确实没做什么。
事务和AOP都实现了InstantiationAwareBeanPostProcessor接口,共用抽象类AbstractAutoProxyCreator,其前置处理方法也会走3.1.1和3.1.2,但是事务的shouldSkip
方法采用的是AbstractAutoProxyCreator抽象类里的shouldSkip方法,而AOP对该方法在AspectJAwareAdvisorAutoProxyCreator里进行了重写,并且重写后的方法主要就是用来解析所有的切面+通知,并把解析结果缓存起来。
4.1 事务的shouldSkip方法
事务的shouldSkip方法代码如下:
所在类:AbstractAutoProxyCreator
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
//一般情况下都会返回false
return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
}
其实该方法一般返回的就是false,也就是说对事务来说,它貌似确实没做什么。。。
4.2 我对事务前置处理方法啥事都没干的猜想
这时候不知道你会不会想一个问题 — 事务代码干巴巴的转这么一圈的意义是什么呢???想了好久之后,我觉得它其实是想借用AbstractAutoProxyCreator的其他方法,然后又觉得转一圈转一圈呗反正又不真正消耗啥性能,所以才会如此 (自己瞎猜的☺)。
同时我还想到了另一个问题,就是既然AOP可以在第一个业务bean创建的时候进行切面+通知的解析并将其缓存起来,难道事务不能在这里做点什么吗??? —> 打断点进行调试,发现果然,它确实做了点什么,只是并不是在前置处理方法里,而是在后置处理方法里。
5 创建第一个业务bean时,事务干了什么???
5.1 源码跟踪
5.1.1 postProcessAfterInitialization
后置处理方法的入口其实就是AbstractAutoProxyCreator抽象类(事务和AOP都会走这块逻辑)的postProcessAfterInitialization方法,这块代码在《【Spring - AOP】 — 目标对象增强核心源码解读》里其实讲到过,其源码如下:
所在类:AbstractAutoProxyCreator
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
//从缓存中拿到目标bean --- 由于目标bean在执行初始化方法之前已经被创建了,所以肯定可以拿到
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//如果之前没对该目标bean创建过代理则包装该bean -> 也就是通过动态代理对该bean进行增强
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
5.1.2 wrapIfNecessary
跟进wrapIfNecessary源码,该方法的核心代码如下:
所在类AbstractAutoProxyCreator
// 获取当前bean对应的通知 或者说拦截器
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果当前bean需要被代理,则创建代理对象
if (specificInterceptors != DO_NOT_PROXY) {
//标记该目标对象已经增强过了,下次不需要再进行增强了
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//拿着获取到的通知对当前对象进行代理增强,并返回增强后的代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
5.1.3 getAdvicesAndAdvisorsForBean
跟进getAdvicesAndAdvisorsForBean方法,其源码如下:
所在类AbstractAdvisorAutoProxyCreator
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
//查找事务通知
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
5.1.4 findEligibleAdvisors — 事务通知解析的入口 — 下篇会细讲
跟进findEligibleAdvisors方法,其源码如下:
所在类AbstractAdvisorAutoProxyCreator
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//找到所有的通知方法
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//找到所有适用于本对象的通知方法 --- 这里是事务属性解析的关键(下篇文章会细讲)
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors); //应该是对通知进行扩展---这里不细究了
//如果适用于本对象的通知不为空的话,对各个通知方法进行排序
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
注意:
上面这写逻辑其实在《【Spring - AOP】 — 目标对象增强核心源码解读》那篇文章里都讲到过。spring事务也使用了该套逻辑。 — 下篇文章会详解findAdvisorsThatCanApply方法。
5.1.5 findCandidateAdvisors方法 — 获取事务通知的入口,AOP重写了此方法
跟进findCandidateAdvisors方法,这里是与AOP不一样的,AOP重写了该方法,会从这步调到3.1.4并获取AOP切面对应的通知
,而这里使用的是AbstractAdvisorAutoProxyCreator抽象类的findCandidateAdvisors方法,其源码如下:
所在类AbstractAdvisorAutoProxyCreator
protected List<Advisor> findCandidateAdvisors() {
Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
//获取事务通知
return this.advisorRetrievalHelper.findAdvisorBeans();
}
5.1.6 findAdvisorBeans — 真正获取事务通知的方法
findAdvisorBeans为真正获取事务通知的方法,其源码如下:
所在类BeanFactoryAdvisorRetrievalHelper
/**
* Find all eligible Advisor beans in the current bean factory,
* ignoring FactoryBeans and excluding beans that are currently in creation.
* @return the list of {@link org.springframework.aop.Advisor} beans
* @see #isEligibleBean
*/
public List<Advisor> findAdvisorBeans() {
//这里也是一个类级别的缓存,缓存下来的是事务通知的beanName
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = this.cachedAdvisorBeanNames;
//创建第一个业务bean时advisorNames肯定为空,但是在创建其他业务bean,就不会为空了
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
//通过类型获取到所有的事务通知 --- 这里并没有做缓存,可以看一下3.1.5种的注释
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
//将获取到的事务通知的名字加入到this.cachedAdvisorBeanNames缓存中
this.cachedAdvisorBeanNames = advisorNames;
}
if (advisorNames.length == 0) {
return new ArrayList<>();
}
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
//判断事务通知是否合法
if (isEligibleBean(name)) {
//判断是否是正在创建当前bean --- 事务通知其实也是一个业务bean,它的创建其实是在
//第一个业务bean(一般指的就是配置类)创建之后创建的
//这个判断的意思是如果当前正在创建事务通知bean时直接跳过
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping currently created advisor '" + name + "'");
}
}
else {
try {
//将创建的事务通知加入到advisors --- this.beanFactory.getBean其实就是在调用单实例bean的创建过程
//如果IOC容器里有从缓存中直接取出来,如果没有就会走单实例bean的创建、初始化等方法来创建一个单实例bean
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}
事务通知是什么???
打断点看一下其庐山真面目:
可以发现它的beanName为org.springframework.transaction.config.internalTransactionAdvisor,这是什么呢?其实它就是《
【spring事务源码学习】— spring事务核心组件创建过程》那篇文章里介绍到的通过@EnableTransactionManagement注解导入的ProxyTransactionManagementConfiguration类里BeanFactoryTransactionAttributeSourceAdvisor
组件 — 5.1.7中的代码 。
5.1.7 isEligibleBean — 判断当前事务通知是否合法
对5.1.6种的isEligibleBean源码再跟一下,可以跟到如下代码:
所在类InfrastructureAdvisorAutoProxyCreator
@Override
protected boolean isEligibleAdvisorBean(String beanName) {
return (this.beanFactory != null && this.beanFactory.containsBeanDefinition(beanName) &&
this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);
}
可以看到代码中的最后一个判断条件其实是在判断当前事务通知的Role信息是否为BeanDefinition.ROLE_INFRASTRUCTURE,回看一下《【spring事务源码学习】— spring事务核心组件创建过程》那篇文章对BeanFactoryTransactionAttributeSourceAdvisor
组件注入源码的介绍如下,可以看到正好符合。
//这里的name正是org.springframework.transaction.config.internalTransactionAdvisor
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
//这里的Role正是BeanDefinition.ROLE_INFRASTRUCTURE
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
5.2 小结
在创建第一个业务bean时,事务干了什么???
答案:
(1)找到事务通知的beanName并将其缓存起来
(2)创建事务通知并将其加入到IOC容器
其他非事务切面业务bean再创建的时候,直接拿着缓存里的事务通知beanName去IOC容器里拿事务通知。
又这么晚了,再多发点感慨吧。。。
今天跟一个同学通电话,他说打算去国外读博后了,我其实有些羡慕,说实话我现在有点不知道自己当时那么毅然决然的放弃读博是对是错,因为我现在确实还啥都不是!!!
身边莫名其妙有好几个同学又回去读博了,其实,有时候我也想过。。。。。。。
但是我想不清楚自己是真想搞学术,还是就是为了想有块更硬的敲门砖。。。。。
所以还是算了,我觉得我现在应该就在正确的路上,至少我觉得是,努力!!!加油!!!