preface

Two days ago I wrote an article “Make a Wheel” — CICada (Lightweight WEB Framework) to introduce cicADA and received a lot of feedback and good suggestions.

At the same time in GitHub also harvested 80 small ♥ (absolutely not brush..)

There are also friends who hope to give a source introduction, this article on the current V1.0.1 version to analyze together.

Yes, a bug has been fixed just released. If you want to try it out, please upgrade to 1.0.1.

Technology selection

There is usually a process of technology selection before building a new gadget, but that was surprisingly easy with CICada.

Because my requirement is to provide a high-performance HTTP service, there are not many options in the open source community.

Plus I’ve been working on Netty development lately, so it was a natural choice.

At the same time Netty has its own HTTP protocol codec, can be very simple and fast development of an HTTP server. I just need to focus on parameter processing, routing and other business processing.

At the same time, Netty is based on NIO implementation, performance is also guaranteed. For more information about Netty, see here.

Let’s focus on each of these processes.

Routing rules

The core, of course, is the HTTP handle, which corresponds to the HttpHandle class.

Looking at the source code, it’s easy to see the steps, and the comments are obvious.

Only the key features are analyzed here.

So let’s think about the demand.

First, as an HTTP framework, it is natural to give users a place to implement business code; Just like the controller we write now when we use SpringMVC.

Three options were considered:

  • I define annotations like SpringMVC, and as long as I declare the corresponding annotation, I consider it a business class.
  • Those of you who have used Struts2 will remember that the business class Action is configured into an XML. Configure the service processing class corresponding to the interface.
  • Same idea, just replace the XML file withpropertiesConfiguration file, in which to write the corresponding relationship in JSON format.

It’s time to analyze the pros and cons of each solution.

Schemes 2 and 3 are really XML versus JSON; XML makes the maintainer feel structured and easy to maintain and add.

JSON is less convenient to work with, and certainly not to the advantage in scenarios like this where it is not used for transport.

Finally, given that popular SpringBoot is going XML free, it would be hard to create something that relies on XML.

So you use annotations like SpringMVC.

With annotations, how does the framework know when a user accesses an interface that corresponds to a business class?

So the natural first step is to scan all the annotated classes and put them in a local cache.

In this way, route location is convenient.

Routing strategy

The core source is in the routeAction method.

First the annotation using @cicadaAction is scanned globally, and then the corresponding business class is found based on the requested address.

Global scan code:

The first step is to get all the custom classes in the project, and then determine if the @cicadaAction annotation is added.

The target class caches it in a local Map so that it can be retrieved from the cache the next time it is accessed without scanning (reflection is expensive).

After executing the routeAction, you get the real business class type.

Class<? > actionClazz = routeAction(queryStringDecoder, appConfig);

By reference

Once you get the class type of the business class, you’re halfway there. You just need to reflect the object that generated it and execute the method.

Another question that comes up before executing the method is, how do I pass parameters?

Considering the flexibility, I used the simplest Map method.

Therefore, a generic Param interface is defined and inherits the Map interface.

public interface Param extends Map<String.Object> {

    /**
     * get String
     * @param param
     * @return* /
    String getString(String param);

    /**
     * get Integer
     * @param param
     * @return* /
    Integer getInteger(String param);

    /**
     * get Long
     * @param param
     * @return* /
    Long getLong(String param);

    /**
     * get Double
     * @param param
     * @return* /
    Double getDouble(String param);

    /**
     * get Float
     * @param param
     * @return* /
    Float getFloat(String param);

    /**
     * get Boolean
     * @param param
     * @return* /
    Boolean getBoolean(String param) ;
}
Copy the code

Several basic types of acquisition methods are encapsulated.

Also, in the buildParamMap() method, encapsulate the parameters from the interface into this Map.

Param paramMap = buildParamMap(queryStringDecoder);
Copy the code

Business execution

Finally, you just need to execute the business; Since we got the class type of the business class above, this is called by reflection.

It also defines a generic interface WorkAction that a business class needs to implement. If you want to implement a specific business, you just need to implement it.

The method parameters are, of course, the parameter interface Param.

Since all business classes implement WorkAction, they can be defined as WorkAction objects when reflected.

WorkAction action = (WorkAction) actionClazz.newInstance();
WorkRes execute = action.execute(paramMap);
Copy the code

Finally, pass in the constructed parameter map.

Return the response

If you have a request, you must have a response. If you look at the WorkAction interface, you can see that a WorkRes response class is defined.

All response data needs to be encapsulated in this object.

There’s nothing to talk about. It’s just basic data.

Finally, encode the response data as JSON output in the responseMsg() method.

Interceptor design

Interceptors are also a basic feature of the framework and are very useful.

Cicada is implemented by simply calling one method before the WorkAction interface executes the business logic and another method after execution.

In the same vein, you need to define an interface CicadaInterceptor, which has two methods.

Look at the name of the method can also see the specific role.

Perform specific calls in both methods at the same time.

The focus here is on the interceptorBefore method.

A cache is added to minimize reflection.

The adapter

Such an interceptor interface is sufficient, but not all businesses need to implement two interfaces.

Therefore also provides an adapter AbstractCicadaInterceptorAdapter.

It implements the CicadaInterceptor interface as an abstract class so that subsequent intercepting services can inherit the interface’s optional implementation methods.

Something like this:

conclusion

V1.0.1 version of CICADA is introduced, the principle and source code are relatively simple.

A lot of use of reflection and some design patterns, polymorphic applications, this less experienced friends can refer to.

At the same time, there are many shortcomings; For example, a more elegant way of passing parameters will be considered in the future, the interceptor is currently dead, and dynamic proxy will be used to implement custom interception.

In fact, CICada only used two days over the weekend to do it, so there must be bugs. You are also welcome to make an issue on GitHub.

Finally, paste the project address:

Github.com/TogetherOS/…

Your likes and retweets are the biggest support.