![90ffe165b81e4542a2988fa85fad9072.png](https://i-blog.csdnimg.cn/blog_migrate/1458e4f487ade9f39042395997b8246e.jpeg)
控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,拉勾IT课小编分解
可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection(DI),通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的容器,将其所依赖的对象的引用传递给它,也可以说,依赖被注入到对象中,这个容器就是我们经常说到IOC容器。Sping及SpringBoot框架的核心就是提供了一个基于注解实现的IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
这篇文章我们自己动手实现一个基于注解的简单IOC容器,当然由于是个人实现不会真的完全按照SpringBoot框架的设计模式,也不会考虑过多的如循环依赖、线程安全等其他复杂问题, 整个实现原理很简单,扫描注解,通过反射创建出我们所需要的bean实例,再将这些bean放到集合中,对外通过IOC容器类提供一个getBean()方法,用来获取ean实例,废话不多说,下面开始具体设计与实现。
![4aae1bb203e24940701d9790cf2d2e04.png](https://i-blog.csdnimg.cn/blog_migrate/f268c402ffb34ca66000e7b11d86142a.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](https://i-blog.csdnimg.cn/blog_migrate/d6a30420cf4554f24b0999260842eaa9.png)