SpringBoot2 + Netty build a simple version of the popular RPC communications framework

2019-07-19: complete basic RPC communication!

2019-07-22: optimize this framework to achieve a single long connection!

2019-07-24: continue to optimize this framework: 1, increase service delivery notes (with version number), then immediately save the Spring Framework provides an implementation class service at startup. 2, optimization NettyConfig (distinguish between consumers and providers configuration), as a project at the same time as service providers and service consumers, so adding two configured to distinguish between the provision of services or consumer services, but also because if all local start the two projects, the IP must be the same, so it is necessary to distinguish between service port and port consumption. Otherwise there will be the following incidents: first start the client, and then start the server, but they also rely on netty package, so also launched a netty client service, and only one will lead to the same port RPC communication client also launched its own channel Netty service. . .

2019-07-27: optimize this framework: increased registry, using a registry Zookeeper.

Next: I will Netty optimization aspects, such as increased heartbeat, a unified business process using a custom thread pool business, client or server abnormal disconnection handling, then it will optimize the structure and communication projects rpc return results, etc., Finally, you may consider increasing Redis as a registration center. And so do all of these, the entire project will be to re-write an article to introduce yourself whole idea, of course, if students need to be in the comments below, I can write articles in advance, and to complete the registration center before the code is described in detail, other then to add new features to achieve process! ~

2019-07-30: Completed all of the features. Put connection: full version of RPC communications framework

The following article was written 2019-07-19, so the code is not optimized, but the core code, still need to read it, you need to go to the bottom to see the complete code of the github address, we can pull under the label corresponding code, then there's trouble you - the test method is sayHello method HelloController of it, then you can also fiddle with some of his test ~

​​​​

Some time ago, I took two weeks to re-learn Netty, always look after a while because there is no read before, so this time was determined to read all, and then also thinking of doing some thinking questions, and simple console version of the IM system worked out. Although called IM systems, but it is very simple, ha ha, just log in, single chat, colonization, plus group, group retreat, group chat and other simple functions. We can look to my github: Netty-IM

After writing the IM system, I intend to write a web version, but taking into account its own front-end skills seem to have largely been degraded, but the time may not be so abundant, let the matter rest. Then one day, suddenly remembered RPC framework used before -> Dubbo, his communication is the use of the underlying Netty, then I think either their first try chanting put forward a simple version, because the most important thing is to learn practical skills too about it, or he did not seem to learn the same school. . .

Before starting hands that he repeated his thoughts, also made reference to two articles, we decided to do a simple version of the RPC framework, without the kind of registry. Then came my boy, we first look at the overall flow chart is Zeyang:

Then came the main event, the following will have to say about the process in more detail:

Briefly explain the project structure:

simple-rpc-client: Consumer Services

simple-rpc-server: service providers

simple-rpc-encapsulation: consumers and providers of public interface

simple-rpc-netty: something about Netty, comprising: a custom protocol, serialized, Packet communication entity, like various Handler.

Client:
        1, first two notes, a note is: identify those calls will be RPC interface communication, that @NettyRPC comment.
        Another comment was: to tell the program which packages in the class will use the RPC communication, as @ComponentScan same as that @EnableNettyRPC comment.

/**
 * @author Howinfun
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NettyRPC {
}
/**
 * @author Howinfun
 * @desc
 * @date 2019/7/15
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableNettyRPC {
    //扫描的包名,如果为空,则根据启动类所在的包名扫描
    String[] basePackages() default {};
}

        2, because we use @NettyRPC will be some interfaces, if the project is not implemented inside the class, it is the call fails, then we can achieve ImportBeanDefinitionRegistrar and custom FactoryBean and InvocationHandler, the use of dynamic proxy interface has to implement, and can be dynamically injection Bean. ImportBeanDefinitionRegistrar interface detail about, because it is a dynamic injection Bean, how inject rule is self-given, mainly by ClassPathScanningCandidateComponentProvider this class, which main function is to all classes under the scanning ClassPath, and to determine which classes can isCandidateComponent method as a candidate, of course, isCandidateComponent ways you can rewrite, then add your own rules, I have to be here and are independent of the interface, in order to become a candidate. ClassPathScanningCandidateComponentProvider can then add filters, I add here the main filter is a comment filter, as long as with @NettyRPC annotated, others are not.
    But note that it is important: Remember to use configuration class has @Configuration annotated @Import import class implements ImportBeanDefinitionRegistrar, otherwise realize the role of dynamic injection Bean, here we can start the Import class on the client side.

package com.hyf.rpc.netty.client.config;

import com.hyf.rpc.netty.anno.EnableNettyRPC;
import com.hyf.rpc.netty.anno.NettyRPC;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * 自定义注册带@NettyRPC注解的接口,利用动态代理使接口有实现
 * 然后在有@Configuration注解的配置类上使用@Import导入,不然不能注入这些实现@NettyRPC接口的BeanDefinition
 * @author Howinfun
 * @date 2019-07-18
 */
public class NettyRpcClientRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {

    private ClassLoader classLoader;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        ClassPathScanningCandidateComponentProvider scan = getScanner();

        //指定注解,类似于Feign注解,只扫描带@NettyRPC注解的接口
        scan.addIncludeFilter(new AnnotationTypeFilter(NettyRPC.class));

        Set<BeanDefinition> candidateComponents = new HashSet<>();
        for (String basePackage : getBasePackages(importingClassMetadata)) {
            candidateComponents.addAll(scan.findCandidateComponents(basePackage));
        }
        candidateComponents.stream().forEach(beanDefinition -> {
            if (!registry.containsBeanDefinition(beanDefinition.getBeanClassName())) {
                if (beanDefinition instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
                    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(NettyRPC.class.getCanonicalName());

                    this.registerNettyRpcClient(registry, annotationMetadata,attributes);
                }
            }
        });
    }

    private void registerNettyRpcClient(BeanDefinitionRegistry registry,
                                        AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        // 指定工厂,使用@NettyRPC注解的接口,当代码中注入时,是从指定工厂获取,而这里的工厂返回的是代理
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(NettyClientFactoryBean.class);
        // @Autowrie:根据类型注入
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        // 注定type属性
        definition.addPropertyValue("type", className);
        String name = attributes.get("name") == null ? "" :(String)(attributes.get("name"));
        // 别名
        String alias = name + "NettyRpcClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setPrimary(true);
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        // 注册BeanDefinition
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }



    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                // 判断候选人的条件:必须是独立的,然后是接口
                if (beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().isInterface()){
                    return true;
                }
                return false;
            }
        };
    }

    /**
     * 获取指定扫描@NettyRPC注解的包路径
     * @param importingClassMetadata
     * @return
     */
    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableNettyRPC.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        // 如果指定的包路径为空,则获取启动类当前路径
        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }else{
            for (String pkg : (String[]) attributes.get("basePackages")) {
                if (StringUtils.hasText(pkg)) {
                    basePackages.add(pkg);
                }
            }
        }
        return basePackages;
    }
}
package com.hyf.rpc.netty.client.config;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

import java.lang.reflect.Proxy;

/**
 * @author Howinfun
 * @desc
 * @date 2019/7/15
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Component
public class NettyClientFactoryBean implements FactoryBean<Object> {

    private Class<?> type;

    @Override
    public Object getObject() throws Exception {
        // 这里的interfaces注意是就是type,因为我们现在是给接口做代理,千万别写type.getInterfaces(),不然启动会报错
        return Proxy.newProxyInstance(type.getClassLoader(),new Class[]{type},new NettyRPCInvocationHandler(this.type));
    }

    @Override
    public Class<?> getObjectType() {
        return this.type;
    }
}

        3, invoke the method of dynamic proxies inside, we will start a client Netty's, to bring information interface call, then wait Netty server returns the results to the results back to the front.

package com.hyf.rpc.netty.client.config;

import com.hyf.rpc.netty.client.NettyClient;
import com.hyf.rpc.netty.packet.RPCRequestPacket;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author Howinfun
 * @desc
 * @date 2019/7/15
 */
@NoArgsConstructor
@Component
public class NettyRPCInvocationHandler implements InvocationHandler {

    private Class<?> type;

    public NettyRPCInvocationHandler(Class<?> type){
        this.type = type;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RPCRequestPacket requestPacket = new RPCRequestPacket();
        requestPacket.setClazz(type);
        requestPacket.setMethodName(method.getName());
        requestPacket.setParamTypes(method.getParameterTypes());
        requestPacket.setParams(args);
        Object result = NettyClient.callRPC(requestPacket);
        return result;
    }
}

    There is a pit: When the client receives the server returns the result, remember to close the channel [. Ctx.channel () close ()], because the RPC call in the client is waiting for the synchronization Channel off, or can not respond to front end.

Server: Process server slightly be much simpler
        1, starting Netty service end, and the receiving client link request, parses the request
        2, then according to the call interface information, using reflected acquired implementation class and a corresponding method, and finally call the method to give As a result, you can then package at the results corresponding to the client.

package com.hyf.rpc.netty.server.handler;

import com.hyf.rpc.netty.packet.RPCRequestPacket;
import com.hyf.rpc.netty.packet.RPCResponsePacket;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.reflections.Reflections;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * @author Howinfun
 * @desc
 * @date 2019/7/16
 */
@ChannelHandler.Sharable
public class RPCRequestPacketHandler extends SimpleChannelInboundHandler<RPCRequestPacket> {

    public static final RPCRequestPacketHandler INSTANCE = new RPCRequestPacketHandler();
    private RPCRequestPacketHandler(){}

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RPCRequestPacket msg) throws Exception {
        RPCResponsePacket responsePacket = new RPCResponsePacket();
        // 获取rpc调用信息,利用反射执行方法,返回结果
        Class clazz = msg.getClazz();
        String methodName = msg.getMethodName();
        Object[] params = msg.getParams();
        Class[] paramTypes = msg.getParamTypes();
        // 扫面路径下所有元数据
        Reflections reflections = new Reflections("com.hyf.rpc.serviceImpl");
        Set<Class> subTypes = reflections.getSubTypesOf(clazz);
        if (subTypes.isEmpty()){
            responsePacket.setSuccess(false);
            responsePacket.setMsg("没有实现类");
        }else if (subTypes.size() > 1){
            responsePacket.setSuccess(false);
            responsePacket.setMsg("多个实现类,无法判断执行哪一个");
        }else{
            Class subClass = subTypes.toArray(new Class[1])[0];
            Method method = subClass.getMethod(methodName,paramTypes);
            Object result = method.invoke(subClass.newInstance(),params);
            responsePacket.setSuccess(true);
            responsePacket.setResult(result);
        }
        ctx.channel().writeAndFlush(responsePacket);
    }
}

        3, reflected here I recommend a good use of the framework -> Reflections. I briefly explain what the use of API, first and foremost, based on the scan path reflection metadata
    and then get all of its implementation class based on the interface, and then you can get information reflecting the implementation class, a method for obtaining the results.

If the students of this relatively simple code also mildly interested, can go to the cloud and see my code:Netty-RPC

Guess you like

Origin www.cnblogs.com/Howinfun/p/11612494.html