ByxContainerAnnotation is a lightweight IOC container based on annotations that mimics Spring IOC. It supports constructor injection and field injection, circular dependency processing and detection, and has a highly extensible plug-in system.
Project address: https://github.com/byx2000/byx-container-annotation
Maven introduction
<repositories> <repository> <id>byx-maven-repo</id> <name>byx-maven-repo</name> <url>https://gitee.com/byx2000/maven-repo/raw/master/</url> </repository> </repositories> <dependencies> <dependency> <groupId>byx.ioc</groupId> <artifactId>byx-container-annotation</artifactId> <version>1.0.0</version> </dependency> </dependencies>
Use example
Use a simple example to quickly understand the use of ByxContainerAnnotation.
A.java:
package byx.test; import byx.ioc.annotation.Autowired; import byx.ioc.annotation.Autowired; import byx.ioc.annotation.Component; @Component public class A { @Autowired private B b; public void f() { b.f(); } }
B.java:
package byx.test; import byx.ioc.annotation.Component; @Component public class B { public void f() { System.out.println("hello!"); } @Component public String info() { return "hi"; } }
main function:
public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); A a = container.getObject(A.class); a.f(); String info = container.getObject("info"); System.out.println(info); }
After executing the main function, the console outputs the following results:
hello! hi
quick get start
Unless otherwise specified, the classes in the following examples are defined in byx Test package.
AnnotationContainerFactory
This class is the implementation class of ContainerFactory interface. ContainerFactory is a container factory, which is used to create IOC containers from specified places.
AnnotationContainerFactory creates IOC containers through package scanning. The usage is as follows:
Container container = new AnnotationContainerFactory(/*Package name or Class object of a Class*/).create();
When constructing AnnotationContainerFactory, you need to pass in a package name or a Class object of a Class. When the create method is called, all classes marked with @ Component under the package and its sub packages will be scanned, and a Container instance will be returned after the scanning is completed.
Container
This interface is the root interface of IOC container. You can use this interface to receive the return value of the create method of ContainerFactory. The included methods are as follows:
method | describe |
---|---|
void registerObject(String id, ObjectFactory factory) | Register the object with IOC container. If the id already exists, IdDuplicatedException will be thrown |
<T> T getObject(String id) | Get the object with the specified id in the container. If the id does not exist, throw IdNotFoundException |
<T> T getObject(Class<T> type) | Gets the object of the specified type in the container. If the type does not exist, a TypeNotFoundException will be thrown. If there are more than one object of the specified type, a MultiTypeMatchException will be thrown |
Set<String> getObjectIds() | Gets the id collection of all objects in the container |
The usage is as follows:
Container container = new AnnotationContainerFactory(...).create(); // Gets the object of type A in the container A a = container.getObject(A.class); // Get the object with id msg in the container String msg = container.getObject("msg");
@Component annotation
@Component annotations can be added to classes to register objects with IOC containers. During package scanning, only classes marked with @ Component annotation will be scanned.
example:
@Component public class A {} public class B {} // You can get the A object A a = container.getObject(A.class); // TypeNotFoundException will be thrown when this statement is executed // Because class B is not annotated with @ Component annotation, it is not registered in the IOC container B b = container.getObject(B.class);
@The Component annotation can also be added to the method to register an object created by an instance method with the IOC container. The registered id is the method name.
example:
@Component public class A { // A String with id msg is registered @Component public String msg() { return "hello"; } } // The value of msg is hello String msg = container.getObject("msg");
Note that if a method is marked with @ Component, the class to which the method belongs must also be marked with @ Component, otherwise the method will not be scanned by the package scanner.
@Id annotation
@The id annotation can be added to the class and used with @ Component to specify the id used when registering the object.
example:
@Component @Id("a") public class A {} // Get A object with id A a = container.getObject("a");
Note that if the class is not marked with @ id, the id when the class is registered is the fully qualified class name of the class.
@The id annotation can also be added to the method to specify the id of the object created by the instance method.
example:
@Component public class A { // A String with id msg is registered @Component @Id("msg") public String f() { return "hello"; } } // hello String msg = container.getObject("msg");
@Id annotation can also be added to method parameters and fields. See Constructor Inject ,Method parameter injection and @Autowire auto assembly.
Constructor Inject
If a class has only one constructor (no or with parameters), the IOC container will call the constructor when instantiating the class and automatically inject the parameters of the constructor from the container.
example:
@Component public class A { private final B b; // Inject field b through constructor public A(B b) { this.b = b; } } @Component public class B {} // a is correctly constructed and its field b is correctly injected A a = container.getObject(A.class);
The @ id annotation can be used on the parameters of the constructor to specify the injected object id. If there is no @ id annotation, it is injected by type by default.
example:
@Component public class A { private final B b; // Inject the object with id b1 through the constructor public A(@Id("b1") B b) { this.b = b; } } public class B {} @Component @Id("b1") public class B1 extends B {} @Component @Id("b2") public class B2 extends B {} // At this time, b in a injects an instance of B1 A a = container.getObject(A.class);
For classes with multiple constructors, you need to use the @ Autowire annotation to mark the constructor used for instantiation.
example:
@Component public class A { private Integer i; private String s; public A(Integer i) { this.i = i; } // Use this constructor to create the A object @Autowire public A(String s) { this.s = s; } } @Component public class B { @Component public Integer f() { return 123; } @Component public String g() { return "hello"; } } // A instantiated using a constructor with a String parameter A a = container.getObject(A.class);
Note that it is not allowed to annotate @ Autowire annotation on multiple constructors at the same time.
@Autowired auto assembly
@The annotation field of the object is directly injected on the annotation field of the object.
example:
@Component public class A { @Autowired private B b; } @Component public class B {} // Field b in a was successfully injected A a = container.getObject(A.class);
By default, @ Autowired is injected by type@ Autowired can also be used together with @ id to realize injection according to id.
example:
@Component public class A { // Inject object with id b1 @Autowired @Id("b1") private B b; } public class B {} @Component @Id("b1") public class B1 extends B {} @Component @Id("b2") public class B2 extends B {} // Field b in a injects the object of B1 A a = container.getObject(A.class);
@Autowired can also be marked on the constructor. See Constructor Inject .
Method parameter injection
If the instance method marked with @ Component has a parameter list, these parameters will also be automatically injected from the container, and the injection rules are the same as those of the constructor.
example:
@Component public class A { // All parameters of the method are obtained from the container @Component public String s(@Id("s1") String s1, @Id("s2") String s2) { return s1 + " " + s2; } } @Component public class B { @Component public String s1() { return "hello"; } @Component public String s2() { return "hi"; } } // The value of s is: hello hi String s = container.getObject("s");
@Init annotation
@Init annotation is used to specify the initialization method of the object, which is created after the object attribute is filled and before the proxy object is created.
example:
@Component public class A { public A() { System.out.println("constructor"); State.state += "c"; } @Autowired public void set1(String s) { System.out.println("setter 1"); State.state += "s"; } @Init public void init() { System.out.println("init"); State.state += "i"; } @Autowired public void set2(Integer i) { System.out.println("setter 2"); State.state += "s"; } } // Get a object A a = container.getObject(A.class);
The output is as follows:
constructor setter 1 setter 2 init
@Value annotation
@The Value annotation is used to register constant values into the container. This annotation is marked on a class marked by @ Component and can be marked repeatedly.
@Component // Register an object of type String with id strVal and value hello @Value(id = "strVal", value = "hello") // Register an object of type int with id intVal and value 123 @Value(type = int.class, id = "intVal", value = "123") // Register an object of type String whose id and value are hi @Value(value = "hi") // Register an object of type double with id and value of 6.28 @Value(type = double.class, value = "6.28") public class A { }
Users can register custom types by implementing a ValueConverter:
public class User { private final Integer id; private final String username; private final String password; public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } // Omit getter s and setter s } @Component // Note that the converter needs to be registered in the container public class UserConverter implements ValueConverter { @Override public Class<?> getType() { return User.class; } @Override public Object convert(String s) { // Convert string to User object s = s.substring(5, s.length() - 1); System.out.println(s); String[] ps = s.split(","); System.out.println(Arrays.toString(ps)); return new User(Integer.valueOf(ps[0]), ps[1].substring(1, ps[1].length() - 1), ps[2].substring(1, ps[2].length() - 1)); } } // Register a User object @Value(id = "user", type = User.class, value = "User(1001,'byx','123')") public class A { }
Cyclic dependence
ByxContainerAnnotation supports the processing and detection of various cyclic dependencies. The following are some examples.
Circular dependency of an object:
@Component public class A { @Autowired private A a; } public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); // a was successfully created and initialized A a = container.getObject(A.class); }
Circular dependency of two objects:
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; } public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); // Both a and b were successfully created and initialized A a = container.getObject(A.class); B b = container.getObject(B.class); }
Circular dependency of mixed constructor injection and field injection:
@Component public class A { private final B b; public A(B b) { this.b = b; } } @Component public class B { @Autowired private A a; } public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); // Both a and b were successfully created and initialized A a = container.getObject(A.class); B b = container.getObject(B.class); }
Circular dependency of three objects:
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private C c; } @Component public class C { @Autowired private A a; } public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); // a. Both b and c are successfully created and initialized A a = container.getObject(A.class); B b = container.getObject(B.class); C c = container.getObject(C.class); }
Unresolved circular dependencies:
@Component public class A { private final B b; public A(B b) { this.b = b; } } @Component public class B { private final A a; public B(A a) { this.a = a; } } public static void main(String[] args) { Container container = new AnnotationContainerFactory("byx.test").create(); // Throw CircularDependencyException A a = container.getObject(A.class); B b = container.getObject(B.class); }
extend
ByxContainer provides a flexible plug-in system. You can extend the functions of ByxContainer by introducing some dependencies named byx container extension - *. Of course, you can also write your own extensions.
Existing extensions
extend | explain |
---|---|
byx-container-extension-aop | Provide aspect oriented programming (AOP) support, including pre enhancement (@ Before), post enhancement (@ After), surround enhancement (@ Around) and exception enhancement (@ AfterThrowing) |
byx-container-extension-transaction | Provides support for declarative transactions, including support for JdbcUtils and @ Transactional annotations |
Write your own extensions
AnnotationContainerFactory provides two extension points:
-
ContainerCallback interface
The interface is defined as follows:
public interface ContainerCallback { void afterContainerInit(Container container); default int getOrder() { return 1; } }
ContainerCallback is similar to Spring's beanfactorypost processor. The afterContainerInit method will call back after the package scanning. Users can dynamically register additional components in the container by creating the implementation class of the interface.
When there are multiple containercallbacks, the order of their calls depends on the order value returned by getOrder. Those with small numbers are executed first.
-
ObjectCallback interface
The interface is defined as follows:
public interface ObjectCallback{ default void afterObjectInit(ObjectCallbackContext ctx) { } default Object afterObjectWrap(ObjectCallbackContext ctx) { return ctx.getObject(); } default int getOrder() { return 1; } }
ObjectCallback is similar to Spring's BeanPostProcessor. afterObjectInit method will call back after object initialization (i.e. after attribute filling), and afterObjectWrap method will call back after proxy object creation.
When there are multiple objectcallbacks, the order of their calls depends on the order value returned by getOrder. Those with small numbers are executed first.
To write a ByxContainer extension:
-
Define one or more implementation classes of ContainerCallback and ObjectCallback. These implementation classes need to have an accessible default constructor
-
In the resources directory, create a file named byx container extension Properties, which declares the components to be exported, and the key values are as follows:
Key value meaning containerCallback Fully qualified class names of all ContainerCallback, separated by objectCallback Fully qualified class names of all ObjectCallback, separated by -
Package the project into Jar package or Maven dependency and introduce it into the main project (i.e. the project with byx container annotation) to enable custom callback components