SkillAgentSearch skills...

Whale

A JSR330 Based Java DI Framework

Install / Use

/learn @techarts0/Whale
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Whale: A JSR330 Based Java DI Framework

Generic badge Generic badge Generic badge Generic badge Generic badge Generic badge Generic badge Generic badge Generic badge

Please access DeepWiki to get more useful help.

1. Summary

Whale is a lightweight dependence Injection(DI) container that fully implements JSR330, and supports javax.inject and jakarta.inject API both. If you are a Java developer and familiar with spring framework or google guice, we highly recommend you giving whale a try.

2. Annotations

JSR330's appeal lies in its simplicity, consisting of just 4 annotations and one interface. To enhance flexibility, whale adds 2 annotations: Valued and Bind, as follows:

| # | Annotation | Usage | | ---- | ---------- | ------------------------------------------------------------ | | 1 | Inject | Indicates a field, constructor or method that will be injected with a managed object. | | 2 | Named | Gives the managed object a qualifier name, or tells the injector which will be injected in. | | 3 | Valued | Injects a value(primitive types like int, string, boolean) or a key from configuration file. (Non JSR330) | | 4 | Singleton | There is only one instance of the managed object in DI container. | | 5 | Bind | Relates an interface or abstraction to a specific implementation. (Non JSR330) | | 6 | Qualifier | Meta annotation. | | 7 | Provider | An interface, not an annotation, used for resolving circular dependencies or lazy loading. | | 8 | Ready | The method is an initializer of the object. It will be call ONCE after object creating.(Non JSR330) | | 9 | Advice | Interceptor. The class will be weaved some appects.(Non JSR330) | | 10 | Advise | Interceptor. The method of an interface will be enhanced.(Non JSR330) |

Whale supports three dependence types:

  • REF: A managed object in container. XML property is ref;
  • KEY: A configuration from properties file. XML property is key;
  • VAL: A value of primitive type(e. g. int, String, boolean, float), XML property is val;

3. Basic Usage

Whale offers four approaches for managing the dependencies between Java objects. To illustrate these methods, let's examine some sample test code. We'll assume the test code is located in the directory on the classpath: "/tmp/project/demo/bin".

  • The Class Person dependents on the class Mobile, a given number value and some configurations:
package whale.demo;

@Named
@Singleton
public class Person{
    @Inject 
    @Valued(val="3")
    private int id;
    
    @Inject
    @Valued(key="user.name") 
    private String name;
    
    private int age;
    
    @Inject
    private Mobile mobile;
    
    public Person(){}

    @Inject
    public void setAge(@Valued(key="user.age")int age){
        this.age = age;
    }
    
    //Getters and Setters
}
  • The class Mobile dependents on two keys from configuration and is injected via the constructor:
package whale.demo;

@Singleton
public class Mobile{
    private String areaCode;
    private String number;

    @Inject
    public Mobile(@Valued(key="mobile.area")String areaCode, @Valued(key="mobile.number")String number){
        this.areaCode = areaCode;
        this.number = number;
    }
    //Getters & Setters
}
  • To ensure successful testing, we need to prepare a configuration beforehand (/tmp/project/demo/config.properties):
user.age=18
user.name=John Denver
mobile.area=+86
mobile.number=13603166666
  • or some static test data:
public class TestData{
	public static final Map<String, String>
	CONFIGS = Map.of("user.age", "18",
					"user.name", "John Denver", 
             		 "mobile.area", "+86", 
             		 "mobile.number", "13603166666");
}

A. Scan classpath to resolve the dependencies:

The JUNIT test case as following:

public class WhaleTest{
    @Test
    public void testScanClasspath(){
        var context = Context.make(CONFIGS);
        
        //Or read configuration from a properties file
        //var context = Context.make("/tmp/project/demo/config.properties");
        
        var loader = context.getLoader();
        loader.scan("/tmp/project/demo/bin");
        
        //If you have more than one classpath:
        //loader.scan("Another classpath");
        //loader.scan("classpath-1", "class-path2");
        
        context.start();
        
        //The chain-stype calling is supported:
        //context.getLoader().scan("/tmp/project/demo/bin").start();
        
        var person = context.get(Person.class);
        var mobile = context.get(Mobile.class);
        TestCase.assertEquals("John Denver", person.getName());
        TestCase.assertEquals(18, person.getAge());
        TestCase.assertEquals("+86", mobile.getAreaCode());
        TestCase.assertEquals("13603166666", person.getMobile().getNumber());
    }
}

B. Register managed objects manually:

    @Test
    public void testRegisterManually(){
        var context = Context.make(CONFIGS);
        var binder = context.getBinder();
        binder.register(Person.class);
        binder.register(Mobile.class);
        context.start();
        
        //Chain-style calling:
        //binder.register(Person.class, Mobile.class).start();
        
        var person = context.get(Person.class);
        var mobile = context.get(Mobile.class);
        TestCase.assertEquals(18, person.getAge());
        TestCase.assertEquals("John Denver", person.getName());
        TestCase.assertEquals("+86", mobile.getAreaCode());
        TestCase.assertEquals("13603166666", person.getMobile().getNumber());
    }

C. Load classes and dependencies from a given JAR file:

We assume to packed these 2 classes into a JAR file "/tmp/project/demo/lib/demo.jar"

    @Test
    public void testLoadFromJAR(){
    	var context = Context.make(CONFIGS);
    	var loader = context.getLoader();
    	loader.load("/tmp/project/demo/lib/demo.jar");
    	context.start();
       
   	var person = context.get(Person.class);
    	var mobile = context.get(Mobile.class);
    	TestCase.assertEquals(18, person.getAge());
     	TestCase.assertEquals("John Denver", person.getName());
    	TestCase.assertEquals("+86", mobile.getAreaCode());
    	TestCase.assertEquals("13603166666", person.getMobile().getNumber());
    }

D. Parse the XML Definition (beans.xml)

If you are a Spring Framework developer, you will be very familiar with XML configuration. Whale also supports you defining the manged objects in the XML file located in "/tmp/project/demo/beans.xml":

<beans>
	<bean id="person" singleton="true" type="whale.demo.Person">
    	<props>
			<prop name="id" val="45" />
			<prop name="name" key="user.name" />
    		<prop name="mobile" ref="mobile" />
    	</props>
        <methods>
        	<method name="setAge">
            	<arg key="user.age" type="int" />
            </method>
        </methods>
    </bean>
    <bean id="mobile" singleton="true" type="whale.demo.Mobile">
        <args>
	    	<arg key="mobile.area" type="String" />
	    	<arg key="mobile.number" type="String" />
	    </args>
	</bean>
</beans> 

Please note that XML definition just supports field injection(using the props tag), constructor injection(using the args tag) and method injection(using the methods tag). For the constructor and method injection, you must explicitily declare the parameter types. Othewise, whale may not be able to correctly identify overloaded methods in certain situations. For example:

//In constructor:
public Student(String studentNumber);
public Student(int age);
//How do we correctly explain the value "21" from configuration? 

//In general methods:
public void setScore(int age);
public void setScore(float age);
//We can convert the value "85" to 85(int) or 85.0(float). Which method will be invoked? 

More advanced features are forbidden because it makes the XML schema very ugly.

    @Test
    public void testParseXMLDefinition(){
    	var context = Context.make(CONFIGS);
     	var loader = context.getLoader();
      	loader.parse("/tmp/project/demo/beans.xml");
      	context.start();
       	
       	//Chain-stype calling
       	//context.createFactory().parse("/tmp/project/demo/beans.xml").start();
       	
       	var person = context.get(Person.class);
        var mobile = context.get(Mobile.class);
        
        TestCase.assertEquals(18, person.getAge());
        TestCase.assertEquals("John Denver", person.getName());
        TestCase.assertEquals("+86", mobile.getAreaCode());
        TestCase.assertEquals("13603166666", person.getMobile().getNumber());
    }

You can actually pass multiple XML definitions to the method parse. For example:

    loader.parse("/tmp/project/demo/beans-1.xml", "/tmp/proj

Related Skills

View on GitHub
GitHub Stars5
CategoryDevelopment
Updated1mo ago
Forks0

Languages

Java

Security Score

90/100

Audited on Feb 10, 2026

No findings