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> beanDefinationFactory = new ConcurrentHashMap<>();
//存储单例对象
private Map 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
* @return
*/
public T getBean(String beanId, Class c){
return (T)getBean(beanId);
}
销毁方法,用于释放资源
/**
* 销毁方法 用于释放资源
*/
public void close(){
beanDefinationFactory.clear();
singletonBeanFactory.clear();
beanDefinationFactory = null;
singletonBeanFactory = null;
}
至此,基本实现了注解方式实现ioc,写的比较简单,因为注释比较详细,所以就没有过多的解释,但是很多该有的判断也没有添加,主要是便于理解,如果有什么不对的地方请多多指教。