Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

事件通知的几个问题 #8098

Closed
zrlw opened this issue Jun 18, 2021 · 7 comments
Closed

事件通知的几个问题 #8098

zrlw opened this issue Jun 18, 2021 · 7 comments

Comments

@zrlw
Copy link
Contributor

@zrlw zrlw commented Jun 18, 2021

Environment

  • Dubbo version: 2.7.5
  • Operating System version: centos7
  • Java version: 1.8

Steps to reproduce this issue

xml配置dubbo:reference方式,dubbo:method配置的onreturn,onthrow方法为spring bean(以下称A)的方法,这些方法上还有诸如@Transactional之类的Spring AOP注解。
问题1:
A里面@Autowired注解的成员均注入失败,使用时均为null对象。
我们采取的补救方法是去掉@Autowired注解,将A增加ApplicationListener<ContextRefreshedEvent>接口实现,在onApplicationEvent方法里重新从ApplicationContext里取得相应的bean赋给本该由@Autowired注入的成员。

问题2:
onreturn,onthrow方法上的Spring AOP注解均失效。
我们采取的补救方法是将onreturn,onthrow方法拆分,相关业务处理代码以及Spring AOP注解移到另外一个bean,A里面的onreturn,onthrow方法只是调用另外那个bean带注解的方法。

问题3:
onThrow方法捕获不了dubbo客户端抛出的RpcException。
重现方法:删除dubbo服务接口入参class上的序列化接口定义,在事件通知方式下客户端的onThrow方法未捕获到任何异常,在@Reference注解方式下调用会抛出dubbo入参未实现java.io.Serializable接口的RPC异常。

问题4:
@Reference注解配置oninvoke,onreturn,onthrow无效,我们看到 #6833 已经描述过这个问题,5月份已修复,我们看到最新发布的3.0代码里已包含了修复,但是修复的方法上还有@Deprecated注解,是否还有进一步修订计划?

@AlbumenJ
Copy link
Member

@AlbumenJ AlbumenJ commented Jun 21, 2021

@kylixs PTAL

@kylixs
Copy link
Contributor

@kylixs kylixs commented Jun 21, 2021

问题4:
@reference注解配置oninvoke,onreturn,onthrow无效,我们看到 #6833 已经描述过这个问题,5月份已修复,我们看到最新发布的3.0代码里已包含了修复,但是修复的方法上还有@deprecated注解,是否还有进一步修订计划?

3.0 分支重构了ReferenceBean的逻辑,统一了xml及Annotation的初始化过程,方法回调配置解析改为: org.apache.dubbo.config.spring.reference.ReferenceCreator#createMethodConfig。 相关pr: #8109

@kylixs
Copy link
Contributor

@kylixs kylixs commented Jun 22, 2021

xml配置dubbo:reference方式,dubbo:method配置的onreturn,onthrow方法为spring bean(以下称A)的方法,这些方法上还有诸如@transactional之类的Spring AOP注解。
问题1:
A里面@Autowired注解的成员均注入失败,使用时均为null对象。

可以提供一个具体的测试用例吗?

@zrlw
Copy link
Contributor Author

@zrlw zrlw commented Jun 22, 2021

  1. 接口工程notifyApi.jar
    dubbo服务接口UserNotifyService:
package com.test.notifyApi;
public interface UserNotifyService {
    String getName(NotifyDto dto);
}

接口方法入参NotifyDto:

package com.test.notifyApi;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import lombok.Data;
@Data
public class NotifyDto implements Serializable {
    private static final long serialVersionUID = 1L;
    private String id;
    @NotNull
    private String sex;
}
  1. consumer工程notifyConsumer.jar
    配置类DubboReferenceConfig:
package com.test.notifyConsumer.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource({"classpath:notify.xml"})
public class DubboReferenceConfig {
}

定义Dubbo Reference bean的notify.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans        
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        
    http://dubbo.apache.org/schema/dubbo        
    http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    
    <dubbo:reference id="userNotifyService"
        interface="com.test.notifyApi.UserNotifyService"
        check="false" timeout="3000">
        <dubbo:method name="getName" validation="true"
            retries="0" async="true"
            oninvoke="consumerNotifyServiceImpl.onInvoke"
            onreturn="consumerNotifyServiceImpl.onReturn"
            onthrow="consumerNotifyServiceImpl.onThrow" />
    </dubbo:reference>		

</beans>

事件通知接口ConsumerNotifyService:

package com.test.notifyConsumer.service;
import com.test.notifyApi.NotifyDto;
public interface ConsumerNotifyService {
    void onInvoke(NotifyDto dto);
    void onReturn(String name, NotifyDto dto);    
    void onThrow(Throwable e, NotifyDto dto);
    
    void getName(NotifyDto dto);
}

事件通知接口实现类ConsumerNotifyServiceImpl :

package com.test.notifyConsumer.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import com.test.notifyApi.NotifyDto;
import com.test.notifyApi.UserNotifyService;
import com.test.notifyConsumer.aspect.MethodAnnotation;
import com.test.notifyConsumer.service.ConsumerNotifyService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ConsumerNotifyServiceImpl implements ConsumerNotifyService {

    @Autowired
    UserNotifyService userNotifyService;
    
    @Autowired
    NotifyWork work;
    
    @MethodAnnotation("invoke-annotation")
    public void onInvoke(NotifyDto dto) {
        log.info("invoke for {}", dto);
        work.workInvoke(dto);
    }

    @MethodAnnotation("return-annotation")
    public void onReturn(String name, NotifyDto dto) {
        log.info("return {} for {}", name, dto);
        work.workReturn(name, dto);
    }

    @MethodAnnotation("throw-annotation")
    public void onThrow(Throwable e, NotifyDto dto) {
        log.info("throw exception {} for {}", e, dto);
        work.workThrow(e, dto);
    }

    public void getName(NotifyDto dto) {
        userNotifyService.getName(dto);
    }
}

注解MethodAnnotation:

package com.test.notifyConsumer.aspect;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MethodAnnotation {
    String value() default "";
}

切面MethodAspect :

package com.test.notifyConsumer.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@Aspect
public class MethodAspect {
    @Around("@annotation(annotation)")
    public Object aroundMethod(ProceedingJoinPoint pjp, MethodAnnotation annotation) throws Throwable {
        log.info("aspect {} for {}", annotation.value(), pjp.getSignature().getName());
        return pjp.proceed();
    }
}

测试类:

package com.test.notifyConsumer;
import javax.validation.ValidationException;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.test.notifyApi.NotifyDto;
import com.test.notifyConsumer.service.ConsumerNotifyService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class AppTest
{
    
    @Autowired
    ConsumerNotifyService consumerNotifyService;
    
    @Test
    public void test1() {
        NotifyDto dto = new NotifyDto();
        dto.setId("12345");
        dto.setSex("M");
        consumerNotifyService.getName(dto);        
    }
    
    // 2.7.5: ConstraintViolationException, 2.7.12: ValidationException(前者的超类)
    @Test(expected = ValidationException.class)
    public void test2() {
        NotifyDto dto = new NotifyDto();
        dto.setId("67890");
        consumerNotifyService.getName(dto);        
    }

    @Test
    public void test3() {
        NotifyDto dto = new NotifyDto();
        dto.setSex("W");
        consumerNotifyService.getName(dto);        
    }
    
    @AfterClass
    public static void waitForNotify() throws InterruptedException {
        log.info("WAIT 1s");        
        Thread.sleep(1000);
    }
}
  1. provider工程
    服务UserNotifyServiceImpl:
package com.test.notifyService.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.RpcException;
import com.test.notifyApi.NotifyDto;
import com.test.notifyApi.UserNotifyService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class UserNotifyServiceImpl implements UserNotifyService {
    @Override
    public String getName(NotifyDto dto) {
        log.info("getname param={}", dto);
        if (StringUtils.isEmpty(dto.getId())) {
            throw new RpcException("null id");
        }
        return "name-" + dto.getId() + "(" + dto.getSex() + ")";
    }
}

kylixs added a commit to kylixs/dubbo that referenced this issue Jun 22, 2021
@zrlw
Copy link
Contributor Author

@zrlw zrlw commented Jun 23, 2021

问题1、2、3在不同dubbo版本下的测试:

  1. dubbo2.7.5版本
    无论是在启动类加@EnableDubbo注解,还是在application.yml配置dubbo.scan.basePackages都存在问题1、2、3;两种方式启动时间都是12秒左右。

  2. dubbo2.7.12版本
    启动类上加@EnableDubbo注解的方式,运行正常,问题1、2、3都没有了;只是改成这种方式之后启动测试程序耗时从18秒变成了80秒,也是醉了。
    只在application.yml配置dubbo.scan.basePackages的方式还有问题1、2,而问题3没有了,启动耗时不到18秒。

补充:

启动慢的原因找到了,有1台nacos注册中心down了,up之后重新测试,两个dubbo版本每种方式各测试10次左右,情况如下。
dubbo2.7.5版本:两种方式下启动耗时均在5~7秒之间;
dubbo2.7.12版本:@EnableDubbo方式下启动耗时在9~12秒之间;application.yml配置dubbo.scan.basePackages方式下启动耗时在6~10秒之间。

@CrazyHZM
Copy link
Contributor

@CrazyHZM CrazyHZM commented Nov 16, 2021

@zrlw
从评论看,这个问题应该已经解决了吧

@zrlw
Copy link
Contributor Author

@zrlw zrlw commented Nov 16, 2021

可以关了,目前master分支还只支持一个onXXX回调,多个回调还要等多实例实现。

@zrlw zrlw closed this Nov 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
@kylixs @AlbumenJ @CrazyHZM @zrlw and others