For a while, I'll write a series of articles on how to implement an RPC framework (I've implemented an example framework with code on my github). This is the second article in the series, which focuses on using spring as well as Java dynamic proxies to simplify the code that invokes other services.
In the first article in this series, we talked about the first point that the RPC framework needs to focus on simplifying client code by creating proxies. If you do not use a proxy.
What happens to our code if we don't use an agent to help us worry about service addressing, network communication problems.
Each time we invoke a remote service, it is certainly unacceptable to repeat the complex logic in the business code. Target Code
And our goal is to write simple code, like this:
This interface should be individually typed into a jar package, and is dependent on the server and client
@RPCService (helloservice.class) public
interface HelloService {
string Hello (string name);
@Component
@Slf4j Public
class Anotherservice {
@Autowired
helloservice helloservice;
public void Callhelloservice () {
//is as comfortable as invoking a local method.
Log.info ("Result of Callhelloservice: {}", Helloservice.hello ("World"));
}
@EnableRPCClients (basepackages = {"Pw.hshen.hrpc"}) public
class Helloclient {public
static void main ( String[] args) throws Exception {
ApplicationContext context = new Classpathxmlapplicationcontext ("Spring.xml"); C18/>anotherservice Anotherservice = Context.getbean (anotherservice.class);
Anotherservice.callhelloservice ();
}
Anotherservice in code can simply call the remote HelloService method, just as simple as invoking a local service. In this code, HelloService can be viewed as a server, while Anotherservice is its caller, which can be treated as a client. Realization of ideas 1. Get the interface to be created agent
First, we need to know which interfaces to create proxies for.
We need to create an annotation for this particular interface, i.e. Rpcservice. Then we can get it by scanning all the interface that contain this annotation underneath a package.
So, how do you know which package to scan? The method is to obtain the basepackages value of the enablerpcclients annotation of the mainclass. 2. Create dynamic proxies for these interfaces
We can do this with the JDK's dynamic proxies:
Interface is the interface that needs to be created
proxy.newproxyinstance (
interface.getclassloader (),
new class<?>[]{ interface},
new Invocationhandler () {
@Override public
object Invoke (Object Proxy, Method method, object[] args) throws Throwable {
//Todo:do RPC action here and return the result
}
3. Register the created proxy object in the Bean container
You can refer to this article for information on how to dynamically register a custom bean in a spring container.
In my frame, I chose to use the hook provided by the beandefinitionregistrypostprocessor.
Once injected into the bean container, we can happily use annotations such as autowired in the code to get the agent created. Annotations required for complete code definition
/**
* @author hongbin
* Created on 22/10/2017
/
@Target (elementtype.type)
@Retention ( retentionpolicy.runtime) Public
@interface enablerpcclients {
string[] basepackages () default {};
}
/**
* @author hongbin
* Created on 21/10/2017
/
@Target (elementtype.type)
@Retention ( Retentionpolicy.runtime)
@Component
@Inherited public
@interface Rpcservice {
class<?> Value ();
}
Using spring's hook mechanism, register our own proxy bean into the container:
/** * Register proxy bean for required client in bean container. * 1. Get interfaces with annotation Rpcservice * 2. Create proxy bean for the interfaces and register them * * @author Hongbin * Created on 21/10/2017/@Slf4j @Required Argsconstructor public class Serviceproxyprovider extends Propertysourcesplaceholderconfigurer implements
beandefinitionregistrypostprocessor {@NonNull private servicediscovery servicediscovery;
@Override public void Postprocessbeandefinitionregistry (Beandefinitionregistry registry) throws Beansexception {
Log.info ("register Beans");
Classpathscanningcandidatecomponentprovider scanner = Getscanner ();
Scanner.addincludefilter (New Annotationtypefilter (Rpcservice.class));
For (String basepackage:getbasepackages ()) {set<beandefinition> candidatecomponents = scanner
. findcandidatecomponents (Basepackage); for (Beandefinition Candidatecomponent:candidatecomponents) {if (candidatecomponent instanceof annotatedbeandefinition) {Annotate
Dbeandefinition beandefinition = (annotatedbeandefinition) candidatecomponent;
Annotationmetadata annotationmetadata = Beandefinition.getmetadata ();
Beandefinitionholder holder = createbeandefinition (annotationmetadata);
Beandefinitionreaderutils.registerbeandefinition (holder, registry); '}} ' private Classpathscanningcandidatecomponentprovider Getscanner () {return new Classpathscanningcandidatecomponentprovider (false) {@Override protected Boolean Iscandidatecomp
Onent (annotatedbeandefinition beandefinition) {if (Beandefinition.getmetadata (). Isindependent ()) { if (Beandefinition.getmetadata (). Isinterface () && Beandefinition.getmet Adata (). GetinterfacenameS (). length = = 1 && Annotation.class.getName (). Equals (Beandefinition.getmetadata (). Geti Nterfacenames () [0])) {try {class<?> target = Class.forName (be
Andefinition.getmetadata (). GetClassName ());
return!target.isannotation ();
' Catch (Exception ex) {Log.error ("Could not load target class: {}, {}",
Beandefinition.getmetadata (). GetClassName (), ex);
} return true;
return false;
}
}; Private Beandefinitionholder createbeandefinition (Annotationmetadata annotationmetadata) {String className
= Annotationmetadata.getclassname ();
Log.info ("Creating bean Definition for class: {}", className); Beandefinitionbuilder definition = beandefInitionbuilder.genericbeandefinition (Proxyfactorybean.class);
String beanname = stringutils.uncapitalize (classname.substring (Classname.lastindexof ('. ') + 1));
Definition.addpropertyvalue ("type", className);
Definition.addpropertyvalue ("Servicediscovery", servicediscovery);
return new Beandefinitionholder (Definition.getbeandefinition (), beanname); Private Set<string> Getbasepackages () {string[] basepackages = Getmainclass (). Getannotation (EnableRP
Cclients.class). Basepackages ();
Set set = new Hashset<> ();
Collections.addall (set, basepackages);
return set; Private Class<?> Getmainclass () {for final map.entry<string, String> entry:System.getenv (). EntrySet ()) {if (Entry.getkey (). StartsWith ("Java_main_class")) {String MainClass = Entry.get
Value ();
Log.debug ("Main class: {}", MainClass);
try { Return Class.forName (MainClass);
catch (ClassNotFoundException e) {throw new IllegalStateException ("cannot determine main class.");
}} throw new IllegalStateException ("cannot determine main class."); @Override public void Postprocessbeanfactory (Configurablelistablebeanfactory beanfactory) throws Beansexception
{
}
}
The corresponding proxybeanfactory:
/**
* Factorybean for service proxy
* *
@author hongbin
* Created on 24/10/2017
/
@Slf4j
@ Data public
class Proxyfactorybean implements factorybean<object> {
private class<?> type;
Private Servicediscovery servicediscovery;
@SuppressWarnings ("unchecked")
@Override public
Object GetObject () throws Exception {
return Proxy.newproxyinstance (Type.getclassloader (), New Class<?>[]{type}, this::d oinvoke);
@Override public
class<?> Getobjecttype () {return
this.type;
}
@Override Public
Boolean Issingleton () {
true;
}
Private Object Doinvoke (object proxy, Method method, object[] args) throws Throwable {
//TODO: This handles the logic of service discovery, load balancing, network communication, etc.
}
}
In this way, we have implemented the client start sweep package, the process of creating agents, the next thing to do is to fill the logic of the agent. Complete code please see my github.