Implementing reverse control in Eclipse RCP (IoC)

Source: Internet
Author: User
Tags add define object connection pooling getmessage interface version access
Controlling the Eclipse Rich customer Platform (RCP) is a powerful software platform that allows developers to build common applications based on interconnection and collaboration between Plug-ins. RCP allows developers to focus on the development of application business code without taking the time to reinvent the wheel to write application management logic.

Reverse control (inversion, IoC) and Dependency injection (Dependency injection, DI) are two programming modes that can be used to reduce coupling between programs. They follow a simple principle: You don't create your objects; you describe how they should be created. Instead of instantiating the objects you need for your parts or directly locating the services you need for your parts, you describe which parts are needed and others (usually a container) are responsible for connecting them together. This is also considered the Hollywood rule: Don ' t call Us--we ' ll called you.

This article describes a simple way to use dependency injection in an Eclipse RCP application. To avoid polluting the infrastructure of the eclipse platform and transparently adding an IOC framework above RCP, we will combine runtime bytecode operations (using the ObjectWeb ASM Library), Java class loading agents (using java.lang.instrument packages) and Java annotation.

   What is the Eclipse Rich customer platform?

In a word, the rich client platform is a collection of class libraries and software frameworks, a run-time environment for building stand-alone and networking applications.

Although Eclipse is considered to be the framework for building the Integrated development environment (IDE), from 3.0, Eclipse's entire product has been refactored, split into various parts, and some parts can be used to build arbitrary applications. A subset of these forms a rich customer platform that contains the following elements: Basic run-time environment, user interface components (SWT and JFace), plug-ins, and OSGi layers. Figure 1 shows the main components of the Eclipse platform.


Figure 1. The main components of the Eclipse platform

The entire eclipse platform is based on plug-ins and extension points. One Plug-inis one of the smallest functional units that can be developed and released independently. It is usually packaged into a JarFile to extend the platform by adding features such as an editor, a toolbar button, or a compiler. The entire platform is a collection of plug-ins that are interconnected and communicated. One Extension Pointsis a mutually connected endpoint that other plug-ins can use to provide additional functionality (called in Eclipse Extended)。 Extension and extension point definitions in an XML configuration file, XML files are bundled with Plug-ins.

The plug-in pattern reinforces the concept of separation, with strong connections between plug-ins and communication required to set their dependencies through wiring. A typical example is derived from the list services needed to locate an application, such as database connection pooling, log processing, or user-saved preferences. Reverse control and dependency injection are a viable solution for eliminating this dependency.

   inversion control and dependency injection

Inversion control is a programming pattern that focuses on how services (or application parts) are defined and how they should locate other services they rely on. Typically, a container or positioning framework is used to obtain the definition and positioning of the separation, container or positioning framework responsible for:
    • Save a collection of available services
    • Provides a way to bind various parts together with the services they depend on
    • Provides a way for application code to request a configured object (for example, an object that all dependencies are satisfied), which ensures that all the related services that the object requires are available.
The existing framework actually uses the framework of the following three basic technologies to perform the binding between services and parts:
    • Type 1 (interface based): A service object needs to implement a specialized interface that provides an object from which to look for dependencies (other services). This pattern was used by early container Excalibur.
    • Type 2 (Setter based): Specifies a service for a service object through the JavaBean property (setter method). Hivemind and spring take this approach.
    • Type 3 (constructor based): Specifies a service for a service object through the parameters of the constructor. Picocontainer only use this method. Hivemind and spring also use this approach.
We will use a variant of the second way to provide services by tagging (the source code for the following sample program can be obtained in the Resources section). Declaring a dependency can be expressed as:
@Injected public void Aservicingmethod (Service s1, anotherservice S2) {   //save S1 and S2 to class variables that can be used when required}
The reverse control container looks for the injected annotation and invokes the method using the requested parameter. We want to introduce the IOC to the Eclipse platform, and the Services and service objects will be packaged into the Eclipse plug-in. The plug-in defines an extension point (named Com.onjava.servicelocator.servicefactory), which provides a service factory to the program. When a serviced object needs to be configured, the plug-in requests a service instance from a factory. The Servicelocator class will do all the work, and the following code describes the class (we omitted the part of the analysis extension point because it is more intuitive):
    /** * Injects the requested dependencies into the parameter object. IT Scans * The serviceable object looking for methods tagged with the * {@link injected} annotation. Parameter types are extracted from the * matching method. An instance of each type is created from the registered * factories (@link iservicefactory}). When instances to all the * parameter types have been created the "is" invoked and the next one * is Exami      Ned. * * @param serviceable * The object to be serviced * @throws serviceexception * * publi         c static void service (Object serviceable) throws Serviceexception {Servicelocator sl = getinstance ();  if (sl.isalreadyserviced (serviceable)) {//Prevent multiple initializations due to//constructor Hierarchies System.out.println ("Object" + serviceable + "has already been configured")             ;        Return } System.out.println ("Configuring" + serviceable); Parse the class for the "Requested services for" (Method M:serviceable.getclass (). GetMethods ()) {b             Oolean skip = false;             Injected ann = M.getannotation (injected.class);                 if (Ann!= null) {object[] services = new Object[m.getparametertypes (). length];                 int i = 0;                             For (class <?> class:m.getparametertypes ()) {Iservicefactory factory = Sl.getfactory (class, Ann                     . Optional ());                         if (factory = = null) {skip = true;                     Break                     Object service = Factory.getserviceinstance (); Sanity check:verify that the returned/service ' s class is the expected one/ /From the method assert (Service.getclass (). Equals (clAss) | |                     Class. IsAssignableFrom (Service.getclass ());                 services[i++] = Service;                 try {if (!skip) M.invoke (serviceable, services); catch (Illegalaccessexception iae) {if (!ann.optional ()) throw new S                                         Erviceexception ("Unable to initialize services on"                 + serviceable + ":" + iae.getmessage (), IAE); catch (InvocationTargetException ite) {if (!ann.optional ()) throw new Servi  Ceexception ("Unable to initialize services on" +                 Serviceable + ":" + ite.getmessage (), ITE);     }} sl.setasserviced (serviceable); }


Because the services returned by the service factory may also be serviced objects, this policy allows the hierarchy of services to be defined (however, circular dependencies are not currently supported).

  ASM and Java.lang.instrument Agents

The various injection strategies described in the preceding section typically rely on the container to provide an entry point where the application uses an entry point to request a properly configured object. However, we hope to adopt a transparent approach when developing the IOC plug-in for two reasons:

    • RCP uses complex classloader and instantiation policies (think Createexecutableextension ()) to maintain plug-in isolation and enforce visibility restrictions. We do not want to modify or replace these policies and introduce our container based instantiation rules.
    • Explicitly referencing such an entry point (the service () method defined in the service locator plug-in) will force the application to adopt an explicit pattern and logic to get the initialized part. This means that the application code appears in the library lock-in. We want to define a plug-in that can collaborate, but it doesn't need to be shown to refer to its base code.

For these reasons, I will introduce the Java Transformation Agent, which is defined in the Java.lang.instrument package, J2SE 5.0 and later. A conversion agent is an object that implements the Java.lang.instrument.ClassFileTransformer interface, which defines only one transform () method. When a transformation instance registers with the JVM, it is invoked whenever the JVM creates an object of a class. This converter can access the byte code of the class and can modify the representation of the class before it is loaded by the JVM.

You can use the JVM command-line arguments to register the conversion agent in the form-javaagent:jarpath[=options], where jarpath is the path to the jar file that contains the code class, and the options is the proxy's parameter string. The proxy jar file specifies the actual proxy class using a special manifest property, which must define a public static void Premain (String options, Instrumentation Inst) method. The agent's Premain () method is invoked before the application's main () executes, and a translator can be registered through an incoming Java.lang.instrument.Instrumentation object instance.

In our example, we define an agent to perform bytecode operations and transparently add calls to the IOC container (Service Locator plug-in). The agent identifies the object that can be serviced based on whether the serviceable annotation appears. It then modifies all constructors and adds callbacks to the IOC container, so that objects can be configured and initialized when instantiated.

Suppose we have an object that relies on external services (injected annotations):

@Serviceable public class Serviceableobject {public   serviceableobject () {System.out.println ()}     ("Initializing ...");   }   @Injected public void Aservicingmethod (Service s1, anotherservice S2) {     //...} omissis ...   }}

When the agent modifies it, its bytecode is the same as the following class is normally compiled:

@Serviceable public class Serviceableobject {public   serviceableobject () {     servicelocator.service (this);     System.out.println ("Initializing ...");   }   @Injected public void Aservicingmethod (Service s1, anotherservice S2) {     //...} omissis ...   }}

In this way, we are able to properly configure the serviced objects and do not require developers to hard-code the dependent containers. Developers only need to mark the serviceable with an annotation. The code for the agent is as follows:

 public class Ioctransformer implements Classfiletransformer {public byte[] transform (ClassLoader loader, String clas Sname, Class <?> classbeingredefined, Protectiondomain protectiondomain, byte[] classfilebuffer) th         Rows illegalclassformatexception {System.out.println ("Loading" + className);         Classreader CReader = new Classreader (classfilebuffer);         Parse the class file Constructorvisitor CV = new Constructorvisitor ();         Classannotationvisitor cav = new Classannotationvisitor (CV);         Creader.accept (Cav, True);             if (Cv.getconstructors (). Size () > 0) {System.out.println ("Enhancing" + ClassName);             Generate the Enhanced-constructor class Classwriter CW = new Classwriter (false);             Classconstructorwriter writer = new Classconstructorwriter (cv. GetConstructors (), CW);             Creader.accept (writer, false); return Cw.tobytearray ();         else return null; public static void Premain (String Agentargs, Instrumentation inst) {Inst.addtransformer (New Ioctransformer (     )); } }

Constructorvisitor, Classannotationvisitor, Classwriter, and Classconstructorwriter use the ObjectWeb ASM Library to perform bytecode operations. The

ASM uses the visitor pattern to process class data (including instruction sequences) in an event flow manner. When decoding an existing class, ASM generates an event stream for us, invoking our methods to handle these events. When a new class is generated, the procedure is reversed: we generate an event stream, which the ASM library converts into a class. Note that the method described here does not depend on a particular bytecode library (we are using ASM here), and other solutions, such as Bcel or javassist, work.

We no longer delve into the internal structure of ASM. It is sufficient to know that the Constructorvisitor and Classannotationvisitor objects are used to find the constructors that are marked as serviceable classes and to collect them. Their source code is as follows:

         public class Classannotationvisitor extends Classadapter {Private Boolean matches = false;     Public Classannotationvisitor (Classvisitor CV) {super (CV); @Override public annotationvisitor visitannotation (String desc, Boolean visible) {if (Visible &&amp ; Desc.equals ("lcom/onjava/servicelocator/annot/serviceable;")         {matches = true;     return Super.visitannotation (desc, visible); @Override public methodvisitor visitmethod (int access, string name, String desc, string signature, S Tring[] Exceptions {if (matches) return Super.visitmethod (access, name, desc, signature, exceptions)         ;         else {return null;     }} public class Constructorvisitor extends Emptyvisitor {private Set <Method> constructors;     Public Constructorvisitor () {constructors = new hashset <Method> (); Public Set <Method> GetConstructors () {RETurn constructors; @Override public methodvisitor visitmethod (int access, string name, String desc, string signature, S         Tring[] Exceptions {Type t = type.getreturntype (DESC); if (Name.indexof ("<init>")!=-1 && t.equals (type.void_type)) {Constructors.add (new method (name, desc         ));     Return Super.visitmethod (access, name, desc, signature, exceptions); } }

A Classconstructorwriter instance modifies each of the collected constructors and injects a call to the service locator plug-in:

Com.onjava.servicelocator.ServiceLocator.service (this);

ASM requires the following instructions to complete the work:

MV is-a ASM method visitor,//A class which allows method manipulation mv.visitvarinsn (aload, 0); MV.VISITMETHODINSN (     invokestatic,      "Com/onjava/servicelocator/servicelocator",      "service", "      ( Ljava/lang/object) V ");

The first instruction loads the This object reference onto the stack, and the second instruction uses it. Its two instructions call the Servicelocator static method.

  Eclipse rcp Application sample

Now we have all the elements that build the application. Our example can be used to show the famous aphorism that the user is interested in. It consists of four plug-ins:

    • Service Locator Plugin, providing IOC framework
    • Fortuneservice Plug-ins, providing service management fortune cookies
    • Fortuneinterface plug-in, public interface required to publish access services
    • Fortuneclient Plug-ins, which provide eclipse applications to display famous sayings in Eclipse view.

The IOC design is used to separate the service from the customer; The service instance can be modified and has no impact on the customer. Figure 2 shows the dependencies between Plug-ins.


Figure 2. Dependencies between Plug-ins: Servicelocator and interface definitions separate the service from the customer.

As mentioned earlier, service locator binds customers and services together. Fortuneinterface only defines a common interface Ifortunecookie that customers can use to access cookie messages:

Public interface Ifortunecookie {public         String getMessage ();}

Fortuneservice provides a simple service factory for creating Ifortunecookie implementations:

public class Fortuneservicefactory implements Iservicefactory {public     Object getserviceinstance () throws serviceexception {return         new Fortunecookieimpl ();     }     ... omissis ...}

The factory registers to the service Locator plug-in extension point, in the Plugin.xml file:

   
   
    
     
     
   
  

The Resourceclass property defines a class of services provided by the factory. In the Fortuneclient plug-in, the Eclipse view uses this service:

@Serviceable public class View extends Viewpart {public     static final String ID = "Fortuneclient.view";     Private Ifortunecookie cookies;     @Injected (optional = false) public     void setdate (Ifortunecookie cookie) {         This.cookie = cookie;     }     public void Createpartcontrol (composite parent) {         label L = new label (parent, SWT. WRAP);         L.settext ("Your fortune Cookie is:\n" + cookie.getmessage ());     }     public void SetFocus () {     }}

Note that there are serviceable and injected annotations that define the dependent external services and do not reference any service code. The end result is that Createpartcontrol () is free to use the cookie object to ensure that it is properly initialized. The sample program is shown in Figure 3


Figure 3. Sample Programs

  Conclusion

This article I discussed how to combine a powerful programming pattern-it simplifies the processing of code dependencies (inversion control) with Java client programs (Eclipse RCP). Even if I did not deal with more details of the impact of this issue, I have demonstrated how a simple application's services and customers are decoupled. I also described how the Eclipse plug-in technology is a separation of concerns when developing customers and services. However, there are a number of interesting factors that still need to be explored, such as the cleanup strategy when the service is no longer needed, or the unit test of the client plug-in using the mock-up service, which I will leave to the reader to think about.



Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.