ByxContainerAnnotation——基于注解的轻量级IOC容器

ByxContainerAnnotation是一个模仿Spring IOC容器基于注解的轻量级IOC容器,支持构造函数注入和字段注入,支持循环依赖处理和检测,具有高可扩展的插件系统。

项目地址:https://github.com/byx2000/byx-container-annotation

Maven引入

<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>

使用示例

通过一个简单的例子来快速了解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函数:

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);
}

执行main函数后,控制台输出结果:

hello!
hi

快速入门

如无特殊说明,以下示例中的类均定义在byx.test包下。

AnnotationContainerFactory

该类是ContainerFactory接口的实现类,ContainerFactory是容器工厂,用于从指定的地方创建IOC容器。

AnnotationContainerFactory通过包扫描的方式创建IOC容器,用法如下:

Container container = new AnnotationContainerFactory(/*包名或某个类的Class对象*/).create();

在构造AnnotationContainerFactory时,需要传入一个包名或者某个类的Class对象。调用create方法时,该包及其子包下所有标注了@Component的类都会被扫描,扫描完成后返回一个Container实例。

Container

该接口是IOC容器的根接口,可以用该接口接收ContainerFactorycreate方法的返回值,内含方法如下:

方法 描述
void registerObject(String id, ObjectFactory factory) 向IOC容器注册对象,如果id已存在,则抛出IdDuplicatedException
<T> T getObject(String id) 获取容器中指定id的对象,如果id不存在则抛出IdNotFoundException
<T> T getObject(Class<T> type) 获取容器中指定类型的对象,如果类型不存在则抛出TypeNotFoundException,如果存在多于一个指定类型的对象则抛出MultiTypeMatchException
Set<String> getObjectIds() 获取容器中所有对象的id集合

用法如下:

Container container = new AnnotationContainerFactory(...).create();

// 获取容器中类型为A的对象
A a = container.getObject(A.class);

// 获取容器中id为msg的对象
String msg = container.getObject("msg");

@Component注解

@Component注解可以加在类上,用于向IOC容器注册对象。在包扫描的过程中,只有标注了@Component注解的类才会被扫描。

例子:

@Component
public class A {}

public class B {}


// 可以获取到A对象
A a = container.getObject(A.class);

// 这条语句执行会抛出TypeNotFoundException
// 因为class B没有标注@Component注解,所以没有被注册到IOC容器中
B b = container.getObject(B.class);

@Component注解还可以加在方法上,用于向IOC容器注册一个实例方法创建的对象,注册的id为方法名。

例子:

@Component
public class A {
    // 注册了一个id为msg的String
    @Component
    public String msg() {
        return "hello";
    }
}

// msg的值为hello
String msg = container.getObject("msg");

注意,如果某个方法被标注了@Component,则该方法所属的类也必须标注@Component,否则该方法不会被包扫描器扫描。

@Id注解

@Id注解可以加在类上,与@Component配合使用,用于指定注册对象时所用的id。

例子:

@Component @Id("a")
public class A {}

// 使用id获取A对象
A a = container.getObject("a");

注意,如果类上没有标注@Id,则该类注册时的id为该类的全限定类名。

@Id注解也可以加在方法上,用于指定实例方法创建的对象的id。

例子:

@Component
public class A {
    // 注册了一个id为msg的String
    @Component @Id("msg")
    public String f() {
        return "hello";
    }
}

// hello
String msg = container.getObject("msg");

@Id注解还可以加在方法参数和字段上,请看构造函数注入方法参数注入@Autowire自动装配

构造函数注入

如果某类只有一个构造函数(无参或有参),则IOC容器在实例化该类的时候会调用该构造函数,并自动从容器中注入构造函数的参数。

例子:

@Component
public class A {
    private final B b;

    // 通过构造函数注入字段b
    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {}

// a被正确地构造,其字段b也被正确地注入
A a = container.getObject(A.class);

在构造函数的参数上可以使用@Id注解来指定注入的对象id。如果没有标注@Id注解,则默认是按照类型注入。

例子:

@Component
public class A {
    private final B b;

    // 通过构造函数注入id为b1的对象
    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 {}

// 此时a中的b注入的是B1的实例
A a = container.getObject(A.class);

对于有多个构造函数的类,需要使用@Autowire注解标记实例化所用的构造函数。

例子:

@Component
public class A {
    private Integer i;
    private String s;

    public A(Integer i) {
        this.i = i;
    }

    // 使用这个构造函数来创建A对象
    @Autowire
    public A(String s) {
        this.s = s;
    }
}

@Component
public class B {
    @Component
    public Integer f() {
        return 123;
    }

    @Component
    public String g() {
        return "hello";
    }
}

// 使用带String参数的构造函数实例化的a
A a = container.getObject(A.class);

注意,不允许同时在多个构造函数上标注@Autowire注解。

@Autowired自动装配

@Autowired注解标注在对象中的字段上,用于直接注入对象的字段。

例子:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {}

// a中的字段b被成功注入
A a = container.getObject(A.class);

默认情况下,@Autowired按照类型注入。@Autowired也可以配合@Id一起使用,实现按照id注入。

例子:

@Component
public class A {
    // 注入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 {}

// a中的字段b注入的是B1的对象
A a = container.getObject(A.class);

@Autowired还可标注在构造函数上,请看构造函数注入

方法参数注入

如果标注了@Component的实例方法带有参数列表,则这些参数也会从容器自动注入,注入规则与构造函数的参数注入相同。

例子:

@Component
public class A {
    // 该方法的所有参数都从容器中获得
    @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";
    }
}

// s的值为:hello hi
String s = container.getObject("s");

@Init注解

@Init注解用于指定对象的初始化方法,该方法在对象属性填充后、创建代理对象前创建。

例子:

@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";
    }
}

// 获取a对象
A a = container.getObject(A.class);

输出如下:

constructor
setter 1
setter 2
init

@Value注解

@Value注解用于向容器中注册常量值。该注解标注在某个被@Component标注的类上,可重复标注。

@Component
// 注册一个id为strVal、值为hello的String类型的对象
@Value(id = "strVal", value = "hello")
// 注册一个id为intVal、值为123的int类型的对象
@Value(type = int.class, id = "intVal", value = "123")
// 注册一个id和值都为hi的String类型的对象
@Value(value = "hi")
// 注册一个id和值都为6.28的double类型的对象
@Value(type = double.class, value = "6.28")
public class 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;
    }

    // 省略getter和setter ...
}

@Component // 注意,该转换器要在容器中注册
public class UserConverter implements ValueConverter {
    @Override
    public Class<?> getType() {
        return User.class;
    }

    @Override
    public Object convert(String s) {
        // 将字符串转换为User对象
        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));
    }
}

// 注册一个User对象
@Value(id = "user", type = User.class, value = "User(1001,'byx','123')")
public class A {
}

循环依赖

ByxContainerAnnotation支持各种循环依赖的处理和检测,以下是一些例子。

一个对象的循环依赖:

@Component
public class A {
    @Autowired
    private A a;
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // a被成功创建并初始化
    A a = container.getObject(A.class);
}

两个对象的循环依赖:

@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();

    // a和b都被成功创建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

构造函数注入与字段注入混合的循环依赖:

@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();

    // a和b都被成功创建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

三个对象的循环依赖:

@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、b、c都被成功创建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
    C c = container.getObject(C.class);
}

无法解决的循环依赖:

@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();

    // 抛出CircularDependencyException异常
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

扩展

ByxContainer提供了一个灵活的插件系统,你可以通过引入一些名称为byx-container-extension-*的依赖来扩展ByxContainer的功能。当然,你也可以编写自己的扩展。

当前已有的扩展

扩展 说明
byx-container-extension-aop 提供面向切面编程(AOP)的支持,包括前置增强(@Before)、后置增强(@After)、环绕增强(@Around)、异常增强(@AfterThrowing)四种增强类型
byx-container-extension-transaction 提供声明式事务的支持,包括对JdbcUtils@Transactional注解的支持

自己编写扩展

AnnotationContainerFactory对外提供两个扩展点:

  • ContainerCallback接口

    该接口定义如下:

    public interface ContainerCallback {
        void afterContainerInit(Container container);
    
        default int getOrder() {
            return 1;
        }
    }
    

    ContainerCallback类似于Spring的BeanFactoryPostProcessorafterContainerInit方法会在包扫描结束后回调,用户可通过创建该接口的实现类来动态地向容器中注册额外的组件。

    当存在多个ContainerCallback时,它们调用的先后顺序取决于getOrder返回的顺序值,数字小的先执行。

  • ObjectCallback接口

    该接口定义如下:

    public interface ObjectCallback{
        default void afterObjectInit(ObjectCallbackContext ctx) {
    
        }
    
        default Object afterObjectWrap(ObjectCallbackContext ctx) {
            return ctx.getObject();
        }
    
        default int getOrder() {
            return 1;
        }
    }
    

    ObjectCallback类似于Spring的BeanPostProcessorafterObjectInit方法会在对象初始化后(即属性填充后)回调,afterObjectWrap方法会在代理对象创建后回调。

    当存在多个ObjectCallback时,它们调用的先后顺序取决于getOrder返回的顺序值,数字小的先执行。

编写ByxContainer扩展的步骤:

  1. 定义一个或多个ContainerCallbackObjectCallback的实现类,这些实现类需要有可访问的默认构造函数

  2. resources目录下创建一个名为byx-container-extension.properties的文件,该文件声明了需要导出的组件,包含的键值如下:

    键值 含义
    containerCallback 所有ContainerCallback的全限定类名,用,分隔
    objectCallback 所有ObjectCallback的全限定类名,用,分隔
  3. 将该项目打包成Jar包或Maven依赖,在主项目(即引入了byx-container-annotation的项目)中引入,即可启用自定义的回调组件

原文地址:https://www.cnblogs.com/baiyuxuan/p/14764404.html