实现Spring IOC

tech2023-01-05  104

什么是IOC?

springIOC的意思是控制反转,传统的对象是我们自己去创建和管理,现在是交给spring去处理,由它来负责控制对象的生命周期和对象间的关系。

实现

在使用spring注入bean的时候,我们有两种方式,一种是xml,一种是注解,现在我们通过实现注解的方式来实现简单的ioc,这里我们需要实现自定义注解,关于自定义注解可以参考另一篇文章,首先展示目录结构图,看实现了哪些注解。 @Component,通过value设置bean的id。

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String value() default ""; }

@Controller,@Service类似

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { String value() default ""; }

@Scope,设置bean的作用域,singleton,prototype,request,session,global session,这里主要实现前面两种。

@Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Scope { String value() default ""; }

@Value,为属性注入具体的值。

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Value { //定义value属性 String value(); }

接下来就是核心类。首先定义两个map,一个map存放bean,一个map存放单例对象,第一个map里面存放类定义对象是为了方便在getBean的时候实现prototype,注意,这里为了保证线程安全使用了ConcurrentHashMap。

//定义两个map容器存储对象 //存储类定义对象 private Map<String,Class<?>> beanDefinationFactory = new ConcurrentHashMap<>(); //存储单例对象 private Map<String,Object> singletonBeanFactory = new ConcurrentHashMap<>();

定义构造方法,在初始化的时候就根据传入的路径扫描包。

//定义有参构造 传入要扫描的包路径 public AnnotationConfigApplicationContext(String packageName) { //扫描指定的包 scanPKG(packageName); }

实现扫描包的方法,将所有类上有@Controller、@Component、@Service的注解的类将key和类定义对象存入map。

/** * 扫描指定包 * 对于类上有注解的类反射创建类定义文件并加入容器中 * @param packageName */ private void scanPKG(final String packageName) { //用final 修饰 防止传入参数被修改调用 //首先将传入的包路径转换为目录结构 将.替换成/ String pkgDir = packageName.replaceAll("\\.","/"); //获取目录结构在类路径中的位置 URL封装了具体资源的路径 String url = getClass().getClassLoader().getResource(pkgDir).getPath(); //防止路径名有空格等出错 try { url = URLDecoder.decode(url, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //基于路径创建一个文件对象 File file = new File(url); //获取此路径下所有以.class结尾的文件 //listFiles返回某个目录下所有的文件和目录的绝对路径 list返回某个目录下所有文件和目录的文件名 返回String数组 File[] files = file.listFiles(new FileFilter() { //文件过滤 也可以实现FileNameFilter接口 @Override public boolean accept(File file) { //获取文件名 String fileNmae = file.getName(); //判断是否为目录,如果是,进一步扫描目录下的文件 if(file.isDirectory()){ scanPKG(packageName + "." + fileNmae); }else{ //判断文件的后缀是否为class if (fileNmae.endsWith(".class")){ return true; } } return false; } }); //遍历所有符合标准的file文件 for (File f:files) { String fileName = f.getName(); //获取文件名.class之前的类容 fileName = fileName.substring(0,fileName.lastIndexOf(".")); //获取类的全路径 String pkgCls = packageName + "." + fileName; try { //通过反射创建对象 Class<?> clazz = Class.forName(pkgCls); //判断是否需要注入bean的注解 if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Service.class)){ String key = getKey(clazz); if (key == null){ //将名字首字母小写 作为map中的key key = String.valueOf(fileName.charAt(0)).toLowerCase() + fileName.substring(1); } beanDefinationFactory.put(key, clazz); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }

实现getKey方法,判断注解的value值是否为默认值,如果不是,则返回value值,如果是默认值则用类名首字母小写作为key。

private String getKey(Class<?> clazz) { String key = null; //判断这个类是否有Component的注解 if (clazz.isAnnotationPresent(Component.class)){ //如果有这个注解 就获取value值,如果有自定义的value值,就将这个值设置为key,否则用默认的key if (!"".equals(clazz.getDeclaredAnnotation(Component.class).value())){ key = clazz.getDeclaredAnnotation(Component.class).value(); } } if (clazz.isAnnotationPresent(Controller.class)){ //如果有这个注解 就获取value值,如果有自定义的value值,就将这个值设置为key,否则用默认的key if (!"".equals(clazz.getDeclaredAnnotation(Controller.class).value())){ key = clazz.getDeclaredAnnotation(Controller.class).value(); } } if (clazz.isAnnotationPresent(Service.class)){ //如果有这个注解 就获取value值,如果有自定义的value值,就将这个值设置为key,否则用默认的key if (!"".equals(clazz.getDeclaredAnnotation(Service.class).value())){ key = clazz.getDeclaredAnnotation(Service.class).value(); } } return key; }

应该实现了的bean都已经存入map中了,接下来就是getBean,通过beanId,获取具体的对象。

/** * 根据传入的beanId获取容器中的对象 * @param beanId * @return */ public Object getBean(String beanId){ //根据传入的beanId获取map中的对象 Class<?> clazz = beanDefinationFactory.get(beanId); if (clazz == null){ throw new NoSuchBeanDefinitionException(beanId, "No matching bean found for bean name '" + beanId + "'! (Note: Qualifier matching not supported because given BeanFactory does not implement ConfigurableListableBeanFactory.)"); } String scope = null; //判断类上面是否有@Scope这个注解 如果有的话,获取里面的值 if (clazz.isAnnotationPresent(Scope.class)){ scope = clazz.getDeclaredAnnotation(Scope.class).value(); } if ("".equals(scope)){ //如果scope为空 没有设置,默认设置为单例模式 有五种 singleton prototype request session global session scope = "singleton"; } //根据获取的值判断bean的作用域 try{ //如果是单例模式 if("singleton".equals(scope)){ //判断容器中是否有这个对象,如果没有 就创建一个对象 if (singletonBeanFactory.get(beanId) == null){ Object instance = clazz.newInstance(); //获取对象时为其成员属性赋值 setFiledValues(clazz, instance); singletonBeanFactory.put(beanId, instance); } return singletonBeanFactory.get(beanId); } if ("prototype".equals(scope)){ Object instance = clazz.newInstance(); setFiledValues(clazz, instance); return instance; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } //如果遭遇异常 return null; }

实现上面用到的自定义异常方法

/** * 自定义异常 显示没有当前的bean对象 */ public class NoSuchBeanDefinitionException extends RuntimeException { private String beanName; public NoSuchBeanDefinitionException(String beanName, String message){ super("No bean named '" + beanName + "' available: " + message); this.beanName = beanName; } }

实现为成员属性赋值的方法setFiledValues()。

/** * 为对象的属性赋值 * 获取成员属性上注解的值 转换为类型后 通过反射为对象赋值 * @param clazz 类定义对象 * @param obj 要为其赋值的实例对象 */ private void setFiledValues(Class<?> clazz, Object obj){ //获取所有的属性 Field[] fields = clazz.getDeclaredFields(); //遍历所有属性 如果属性上面有注解 对其进行赋值 for (Field field:fields){ field.setAccessible(true); if (field.isAnnotationPresent(Value.class)){ //获取注解内的值 String value = field.getAnnotation(Value.class).value(); //获取属性定义的类型 String type = field.getType().getSimpleName(); try{ if ("Integer".equals(type) || "int".equals(type)){ int intValue = Integer.valueOf(value); field.set(obj,intValue); }else if("String".equals(type)){ field.set(obj,value); } } catch (IllegalAccessException e) { e.printStackTrace(); } } } }

实现getBean的重载方法,返回传入的class对象的类型

/** * 重载方法,返回传入的class对象的类型 * @param beanId * @param c * @param <T> * @return */ public <T> T getBean(String beanId, Class<T> c){ return (T)getBean(beanId); }

销毁方法,用于释放资源

/** * 销毁方法 用于释放资源 */ public void close(){ beanDefinationFactory.clear(); singletonBeanFactory.clear(); beanDefinationFactory = null; singletonBeanFactory = null; }

至此,基本实现了注解方式实现ioc,写的比较简单,因为注释比较详细,所以就没有过多的解释,但是很多该有的判断也没有添加,主要是便于理解,如果有什么不对的地方请多多指教。

最新回复(0)