需求说明:
自己写一个简单的 Spring 容器, 通过读取类的注解(@Component @Controller@Service @Reponsitory) ,将对象注入到 IOC 容器,自己使用 IO+Annotaion+反射+集合 技术实现
思路分析:
一、新建一个包component并在包下创建bean类
环境配置:导入jar包
新建component包
这里将Servlet层,Service层等等的对象放入component包下,因为手写的Spring基于注解配置的程序是一个简化的程序,没有原生的Spring功能强大,这里就将各个对象放入一个包下面便于io读取
UserAction类:
package com.study.Spring.component;import org.springframework.stereotype.Controller;
//控制层Servlet
@Controller
public class UserAction {
}
UserComponent类:
package com.study.Spring.component;import org.springframework.stereotype.Component;
//使用component注解可以将该类扫描放入ioc容器中
@Component
public class UserComponent {
}
UserDao类:
package com.study.Spring.component;import org.springframework.stereotype.Repository;
//这是持久层、dao
@Repository
public class UserDao {
}
UserService类:
package com.study.Spring.component;import org.springframework.stereotype.Service;
//这是Service层
@Service
public class UserService {
}
二、自定义注解(相当于原配置文件中的component-scan)
新建一个包用于存放自己手写的程序
创建一个自定义注解:ComponentScan
package com.study.Spring.mySpringByAnnotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 这个自定义的注解的作用相当于原配置文件中的component-scan* @Target(ElementType.TYPE)表示该注解作用的对象* @Retention(RetentionPolicy.RUNTIME)表示该注解的生命周期* String value() default "";表示注解的属性value用于存放包名(即:要扫描的包路径),默认是空字符串*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}
三、创建一个类代替beans.xml文件
创建一个类:MySpringConfig作用相当于beans.cml文件
package com.study.Spring.mySpringByAnnotation;/*** MySpringConfig这个类作用相当于beans.xml文件* "com.study.Spring.component"这个包名传给该注解的value属性*/
@ComponentScan("com.study.Spring.component")
public class MySpringConfig {
}
四、创建MySpringApplicationContext类作为容器对象
这个类相当于容器对象:扫描配置文件,初始化Bean,反射创建Bean,保存Bean都在这里执行
package com.study.Spring.mySpringByAnnotation;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;/*** 这个类相当于容器对象:扫描配置文件,初始化Bean,反射创建Bean,保存Bean都在这里执行*/
public class MySpringApplicationContext {//容器的属性:MySpringConfig是配置类的Class对象,用于反射创建注解读取类名//ioc属性相当于原生的SingleTonObjects用于存储单例bean对象private Class MySpringConfig;private final ConcurrentHashMap<String,Object> ioc =new ConcurrentHashMap<>();public MySpringApplicationContext(Class mySpringConfig){this.MySpringConfig=mySpringConfig;//反射获取自定义的注解对象,再获取注解中的包名ComponentScan componentScan =(ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();path= path.replace(".", "/");//根据包名获取实际工作目录的路径,再通过io流获取扫描的类的绝对路径
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component//这里拿到类加载器用于获取实际工作目录ClassLoader classLoader = MySpringConfig.getClassLoader();URL realPath = classLoader.getResource(path);
// /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/componentString filePath = realPath.getFile();File file = new File(filePath);//通过目录获取目录下的所有Class文件if (file.isDirectory()){File[] files = file.listFiles();for (File item : files) {String name = item.getName();if (name.contains(".class")){//通过字符串的拼接获取包下面的全类名path = path.replace("/", ".");String className = name.substring(0, name.indexOf("."));//获取全类名String fullPath=path+"."+className;
// 再根据Class文件进行一次筛选:判断是否有四种注解的类才进行创建实例对象并存储在容器中try {//通过classloader获取的Class对象时轻量级的,不会初始化时调用静态方法Class<?> aClass = classLoader.loadClass(fullPath);//根据不同类的Class对象判断是否有指定的注解if (aClass.isAnnotationPresent(Component.class)||aClass.isAnnotationPresent(Controller.class)||aClass.isAnnotationPresent(Service.class)||aClass.isAnnotationPresent(Repository.class)){//如果存在指定的注解那么就通过反射创建Class对象并创建实例对象Class<?> aClass1 = Class.forName(fullPath);//反射创建实例对象作为valueObject instance = aClass1.newInstance();//默认将首字母是小写的类名作为keyclassName = StringUtils.uncapitalize(className);//将bean保存到容器中ioc.put(className,instance);}} catch (Exception e) {throw new RuntimeException(e);}}}}}public Object getBean(String id){return ioc.get(id);}
}
解释:
获取配置文件类MySpringConfig的Class对象,就可以用反射创建annotation注解对象,用注解对象获取包名,有了包名后可以调用ClassLoader的方法得到实际工作目录,再根据io流的方法+字符串的拼接得到包下面的各个类的全类名,再通过反射得到各个类的Class对象,再进一步筛选,通过反射Class对象调用isAnnotationPresent方法判断是否存在指定的注解来选择性初始化对象,最后将对象存储在concurrentHashmap中,在MySpringApplicationContext类中添加getBean方法获取bean对象
五、编写测试类MySpringApplicationContextTest
package com.study.Spring.mySpringByAnnotation;public class MySpringApplicationContextTest {public static void main(String[] args) {MySpringApplicationContext ioc = new MySpringApplicationContext(MySpringConfig.class);Object userAction = ioc.getBean("userAction");Object userComponent = ioc.getBean("userComponent");Object userDao = ioc.getBean("userDao");Object userService = ioc.getBean("userService");System.out.println(userAction);System.out.println(userComponent);System.out.println(userDao);System.out.println(userService);}
}
运行结果:
通过调试也可以看出已经初始化了四个bean对象