ioc容器的实现方法有哪些_自己动手实现一个简单的 IOC容器

90ffe165b81e4542a2988fa85fad9072.png

控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,拉勾IT课小编分解

可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection(DI),通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的容器,将其所依赖的对象的引用传递给它,也可以说,依赖被注入到对象中,这个容器就是我们经常说到IOC容器。Sping及SpringBoot框架的核心就是提供了一个基于注解实现的IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

这篇文章我们自己动手实现一个基于注解的简单IOC容器,当然由于是个人实现不会真的完全按照SpringBoot框架的设计模式,也不会考虑过多的如循环依赖、线程安全等其他复杂问题, 整个实现原理很简单,扫描注解,通过反射创建出我们所需要的bean实例,再将这些bean放到集合中,对外通过IOC容器类提供一个getBean()方法,用来获取ean实例,废话不多说,下面开始具体设计与实现。

4aae1bb203e24940701d9790cf2d2e04.png

1、定义注解

@Retention(RetentionPolicy.RUNTIME)

public @interface SproutComponet {

String value() default "";

}

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SproutRoute {

RouteEnum value();

}

2、实现jar包扫描类

根据传入jar包,扫描与缓存jar包下所有指定注解的class<?>类对象

public class ClassScanner {

private static Set<Class<?>> classSet = null;

private static Map<String, Class<?>> componetMap = null;

/**

* 获取指定包名下所有class类

* @param packageName

* @return

* @throws Exception

*/

public static Set<Class<?>> getClasses(String packageName) throws Exception {

if (classSet == null){

classSet = ReflectUtils.getClasses(packageName);

}

return classSet;

}

/**

* 缓存所有指定注解的class<?>类对象

* @param packageName

* @return

* @throws Exception

*/

public static Map<String, Class<?>> getBean(String packageName) throws Exception {

if (componetMap == null) {

Set<Class<?>> clsList = getClasses(packageName);

if (clsList == null || clsList.isEmpty()) {

return componetMap;

}

componetMap = new HashMap<>(16);

for (Class<?> cls : clsList) {

Annotation annotation = cls.getAnnotation(SproutComponet.class);

if (annotation == null) {

continue;

}

SproutComponet sproutComponet = (SproutComponet) annotation;

componetMap.put(sproutComponet.value() == null ? cls.getName() : sproutComponet.value(), cls);

}

}

return componetMap;

}

}

基于ClassScanner,扫描并缓存加有注解的Method对象,为后面实现方法路由提供支持

public class RouterScanner {

private String rootPackageName;

private static Map<Object, Method> routes = null;

private List<Method> methods;

private volatile static RouterScanner routerScanner;

/**

* get single Instance

*

* @return

*/

public static RouterScanner getInstance() {

if (routerScanner == null) {

synchronized (RouterScanner.class) {

if (routerScanner == null) {

routerScanner = new RouterScanner();

}

}

}

return routerScanner;

}

private RouterScanner() {

}

public String getRootPackageName() {

return rootPackageName;

}

public void setRootPackageName(String rootPackageName) {

this.rootPackageName = rootPackageName;

}

/**

* 根据注解 指定方法 get route method

*

* @param queryStringDecoder

* @return

* @throws Exception

*/

public Method routeMethod(Object key) throws Exception {

if (routes == null) {

routes = new HashMap<>(16);

loadRouteMethods(getRootPackageName());

}

Method method = routes.get(key);

if (method == null) {

throw new Exception();

}

return method;

}

/**

* 加载指定包下Method对象

*

* @param packageName

* @throws Exception

*/

private void loadRouteMethods(String packageName) throws Exception {

Set<Class<?>> classSet = ClassScanner.getClasses(packageName);

for (Class<?> sproutClass : classSet) {

Method[] declaredMethods = sproutClass.getMethods();

for (Method method : declaredMethods) {

SproutRoute annotation = method.getAnnotation(SproutRoute.class);

if (annotation == null) {

continue;

}

routes.put(annotation.value(), method);

}

}

}

}

3、定义BeanFacotry对象工厂接口

接口必须具备三个基本方法:

init() 初始化注册Bean实例

getBean() 获取Bean实例

release() 卸载Bean实例

public interface ISproutBeanFactory {

/**

* Register into bean Factory

*

* @param object

*/

void init(Object object);

/**

* Get bean from bean Factory

*

* @param name

* @return

* @throws Exception

*/

Object getBean(String name) throws Exception;

/**

* release all beans

*/

void release();

}

4、实现BeanFacotry对象工厂接口

BeanFactory接口的具体实现,在BeanFacotry工厂中我们需要一个容器,即beans这个Map集合,在初始化时将所有的需要IOC容器管理的对象实例化并保存到 bean 容器中,当需要使用时只需要从容器中获取即可,

解决每次创建一个新的实例都需要反射调用 newInstance() 效率不高的问题。

public class SproutBeanFactory implements ISproutBeanFactory {

/**

* 对象map

*/

private static Map<Object, Object> beans = new HashMap<>(8);

/**

* 对象map

*/

private static List<Method> methods = new ArrayList<>(2);

@Override

public void init(Object object) {

beans.put(object.getClass().getName(), object);

}

@Override

public Object getBean(String name) {

return beans.get(name);

}

public List<Method> getMethods() {

return methods;

}

@Override

public void release() {

beans = null;

}

}

5、实现bean容器类

IOC容器的入口及顶层实现类,声明bena工厂实例,扫描指定jar包,基于注解获取 Class<?>集合,实例化后注入BeanFacotry对象工厂

public class SproutApplicationContext {

private SproutApplicationContext() {

}

private static volatile SproutApplicationContext sproutApplicationContext;

private static ISproutBeanFactory sproutBeanFactory;

public static SproutApplicationContext getInstance() {

if (sproutApplicationContext == null) {

synchronized (SproutApplicationContext.class) {

if (sproutApplicationContext == null) {

sproutApplicationContext = new SproutApplicationContext();

}

}

}

return sproutApplicationContext;

}

/**

* 声明bena工厂实例,扫描指定jar包,加载指定jar包下的实例

*

* @param packageName

* @throws Exception

*/

public void init(String packageName) throws Exception {

//获取到指定注解类的Map

Map<String, Class<?>> sproutBeanMap = ClassScanner.getBean(packageName);

sproutBeanFactory = new SproutBeanFactory();

//注入实例工厂

for (Map.Entry<String, Class<?>> classEntry : sproutBeanMap.entrySet()) {

Object instance = classEntry.getValue().newInstance();

sproutBeanFactory.init(instance);

}

}

/**

* 根据名称获取获取对应实例

*

* @param name

* @return

* @throws Exception

*/

public Object getBean(String name) throws Exception {

return sproutBeanFactory.getBean(name);

}

/**

* release all beans

*/

public void releaseBean() {

sproutBeanFactory.release();

}

}

6、实现方法路由

提供方法,接受传入的注解,通过RouterScanner与SproutApplicationContext 获取对应Method对象与Bean实例,调用具体方法,从而实现方法路由功能。

public class RouteMethod {

private volatile static RouteMethod routeMethod;

private final SproutApplicationContext applicationContext = SproutApplicationContext.getInstance();

public static RouteMethod getInstance() {

if (routeMethod == null) {

synchronized (RouteMethod.class) {

if (routeMethod == null) {

routeMethod = new RouteMethod();

}

}

}

return routeMethod;

}

/**

* 调用方法

* @param method

* @param annotation

* @param args

* @throws Exception

*/

public void invoke(Method method, Object[] args) throws Exception {

if (method == null) {

return;

}

Object bean = applicationContext.getBean(method.getDeclaringClass().getName());

if (args == null) {

method.invoke(bean);

} else {

method.invoke(bean, args);

}

}

/**

* 根据注解调用方法

* @param method

* @param annotation

* @param args

* @throws Exception

*/

public void invoke(RouteEnum routeEnum, Object[] args) throws Exception {

Method method = RouterScanner.getInstance().routeMethod(routeEnum);

if (method == null) {

return;

}

Object bean = applicationContext.getBean(method.getDeclaringClass().getName());

if (args == null) {

method.invoke(bean);

} else {

method.invoke(bean, args);

}

}

}

7、具体使用

到这里IOC容器的主要接口与实现类都以基本实现,我们看下具体的使用

首先初始化IOC容器,这里根据main方法扫描应用程序所在包下的所有类,把有注解的bean实例注入实例容器

public void start() {

try {

resolveMainClass();

if(mainClass!=null) {

SproutApplicationContext.getInstance().init(mainClass.getPackage().getName());

}

}catch (Exception e) {

// TODO: handle exception

}

}

/**

* 查询main方法的class类

*

*/

private Class<?> resolveMainClass() {

try {

if(!StringUtils.isEmpty(config().getRootPackageName())) {

mainClass = Class.forName(config().getRootPackageName());

}else {

StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();

for (StackTraceElement stackTraceElement : stackTrace) {

if ("main".equals(stackTraceElement.getMethodName())) {

mainClass = Class.forName(stackTraceElement.getClassName());

break;

}

}

}

} catch (Exception ex) {

// ignore this ex

}

return mainClass;

}

获取bead实例,并调用方法

/**

* 根据注解调用方法

* @param method

* @param annotation

* @param args

* @throws Exception

*/

public void invoke(RouteEnum routeEnum, Object[] args) throws Exception {

Method method = RouterScanner.getInstance().routeMethod(routeEnum);//基于IOC实现的方法路由

if (method == null) {

return;

}

Object bean = applicationContext.getBean(method.getDeclaringClass().getName()); // 通过Bean容器直接获取实例

if (args == null) {

method.invoke(bean);

} else {

method.invoke(bean, args);

}

}

847fbc2de977a77060365c14406b4484.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值