编写一个自己的IOC容器
Posted PPG007
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写一个自己的IOC容器相关的知识,希望对你有一定的参考价值。
写在最前
这个工程旨在练习Java注解和反射,以及体会依赖注入的原理、过程,不以追求可靠、可用为目的,且阅读此博客前应当熟练掌握Java且有一定的Spring使用经验
预期功能
- 模拟Spring中的Bean注册、自动装配
编码部分
自定义注解部分
模拟Spring中的部分注解
- @Bean注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
/**
* 指定Bean的名字
*/
String name() default "";
}
- @Configuration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Configuration {
}
- @Import注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Import {
/**
* 指定要引入的配置类
*/
Class<!--?-->[] classes();
}
- @Qualifier注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
/**
* 自动装配时指定需要的Bean的名字
*/
String value() default "";
}
- @Autowired注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
/**
* 指定自动装配的方式,通过类型还是名字
*/
AutoWiredType type() default AutoWiredType.BY_TYPE;
}
- 装配方式枚举类
public enum AutoWiredType {
/**
* ByType自动装配
*/
BY_TYPE,
/**
* ByName自动装配
*/
BY_NAME
}
自定义异常类
只要继承Exception类即可,异常类如下
容器类
首先我们需要一个静态HashMap变量充当单例Spring容器
/**
* Spring容器
*/
private static HashMap<String, Object> container = null;
然后创建一个向容器中增加Bean的方法addBean
,通过synchronized
关键字确保线程安全
/**
* 添加Bean到容器
* @param name 名字
* @param o 对象
* @throws BeanExistException 异常
*/
private synchronized static void addBean(String name, Object o) throws BeanExistException {
//不允许重复添加
if (container.containsKey(name)) {
throw new BeanExistException("already exist bean with name:\'" + name + "\'");
}
container.put(name, o);
}
编写获取Bean的方法getBean
,并进行重载,分别使用字符串、Class对象做参数
public Object getBean(String name) throws NoSuchNameBeanException {
if (!container.containsKey(name)) {
//如果容器中没有这个名字的Bean就丢出异常
throw new NoSuchNameBeanException("there is no bean with name:" + name);
}
return container.get(name);
}
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> requiredType, String name) throws NoSuchTypeBeanException, NoQualifiedBeanException, MultipleQualifiedBeanException {
Set<Map.Entry<String, Object>> entries = container.entrySet();
boolean byType = false;// ByType自动装配的结果标志
T bean=null;
for (Map.Entry<String, Object> entry : entries) {
if (requiredType.isAssignableFrom(entry.getValue().getClass())) {
byType = true;// 只要有对应类型的Bean就认为ByType成功
if (name == null || name.trim().isEmpty() || name.equals(entry.getKey())) {// ByType成功后进一步判断有没有指定Bean的名字
if (bean != null) {
//如果能找到多个满足条件的Bean就抛出异常
throw new MultipleQualifiedBeanException("there is more than one qualified bean with type:"+requiredType.getName());
}
bean = ((T) entry.getValue());
}
}
}
if (bean!=null){// 如果找到了符合条件的Bean就返回
return bean;
}
if (!byType) {// 如果ByType失败,就抛出ByType失败的异常
throw new NoSuchTypeBeanException("there is no bean with type:" + requiredType.getName());
} else {// 如果ByType成功而根据指定name筛选失败则抛出没有满足条件Bean异常
throw new NoQualifiedBeanException("there is no qualified bean with type:" + requiredType.getName() + ",and with name:" + name);
}
}
编写初始化容器的方法initContainer
,使用synchronized
关键字保证线程安全,另外由于我们有一个@Import
注解,需要递归调用这个初始化方法,所以需要一个HashSet用于记录已经被解析过的配置类,防止两个配置类同时在@Import
中引用对方导致无限递归和重复定义Bean导致抛出异常
private synchronized void initContainer(String name) throws Exception {
if (this.alreadyInitClassName==null){
this.alreadyInitClassName=new HashSet<>();
}
if (this.alreadyInitClassName.contains(name)){
return;
}else {
this.alreadyInitClassName.add(name);
}
// 反射加载
Class<?> aClass = Class.forName(name);
// 判断所选类是否存在@Configuration注解
if (aClass.isAnnotationPresent(Configuration.class)) {
// 创建一个配置类对象
Object config = aClass.newInstance();
// 获取配置类中所有的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
if (container == null) {
container = new HashMap<>(declaredMethods.length*4/3+1);
}
// 遍历
for (Method declaredMethod : declaredMethods) {
// 判断此方法是否存在@Bean注解
if (declaredMethod.isAnnotationPresent(Bean.class)) {
// 如果没有指定Bean的名字
if ("".equals(declaredMethod.getAnnotation(Bean.class).name())) {
// 就使用方法名作为bean的名字并执行这个方法初始化bean并注入到容器
addBean(declaredMethod.getName(), declaredMethod.invoke(config));
} else {
// 否则使用指定的名字初始化bean并注入到容器
addBean(declaredMethod.getAnnotation(Bean.class).name(), declaredMethod.invoke(config));
}
}
}
}
if (aClass.isAnnotationPresent(Import.class)){
Class<?>[] classes = aClass.getAnnotation(Import.class).classes();
for (Class<?> aClass1 : classes) {
initContainer(aClass1.getName());
}
}
}
然后定义自动装配方法autowiredInit
,并进行重载,一个接受字符串参数,一个接受Class对象参数
public synchronized Object autowiredInit(String name) throws Exception {
// 加载这个要装配的类的class对象
Class<?> aClass = Class.forName(name);
return autowiredInit(aClass);
}
public synchronized <T> T autowiredInit(Class<T> clazz) throws Exception{
// 构造这个类的实例对象
T o = clazz.newInstance();
// 获取这个类所有的声明的变量
Field[] declaredFields = clazz.getDeclaredFields();
// 遍历这些变量
for (Field declaredField : declaredFields) {
// 判断这个变量是否有@Autowired修饰
if (declaredField.isAnnotationPresent(Autowired.class)) {
// 修改访问权限,可以修改private的变量
declaredField.setAccessible(true);
AutoWiredType type = declaredField.getAnnotation(Autowired.class).type();
if (type==AutoWiredType.BY_TYPE){// 判断自动装配的方式
if (declaredField.isAnnotationPresent(Qualifier.class)){
String qualifyName = declaredField.getAnnotation(Qualifier.class).value();
declaredField.set(o,this.getBean(declaredField.getType(),qualifyName));
}else{
declaredField.set(o,this.getBean(declaredField.getType(),null));
}
}else {
// 获取变量的名字
String name1 = declaredField.getName();
// 获取对应的Bean
Object bean = getBean(name1);
// 设置值
declaredField.set(o, bean);
}
}
}
// 返回这个被装配完毕的实例对象
return o;
}
最后编写构造函数,同样具有两个重载
public Container(String name) throws Exception {
initContainer(name);
}
public Container(Class<?> configClass) throws Exception {
initContainer(configClass.getName());
}
进行测试
随便编写几个JavaBean即可,只要具有一些属性并且能够正常输出即可
示例:
public class DataSource {
private String url;
private String username;
private String password;
@Override
public String toString() {
return "DataSource{" +
"url=\'" + url + \'\\\'\' +
", username=\'" + username + \'\\\'\' +
", password=\'" + password + \'\\\'\' +
\'}\';
}
public DataSource() {
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
其他Bean如下:
编写service层接口和实现类,验证控制反转和依赖倒转
public interface IDemoService {
/**
* 输出一段话
*/
void demo();
}
实现类一:
public class DemoServiceImpl implements IDemoService {
@Override
public void demo() {
System.out.println("这是第一种实现");
}
}
实现类二:
@Override
public void demo() {
System.out.println("这是第二种实现");
}
}
编写多个配置文件
@Configuration
@Import(classes = {Config2.class,Config3.class})
public class Config {
@Bean(name = "mysql")
public DataSource dataSource(){
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(){
SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
sqlSessionFactory.setDataSource(dataSource());
return sqlSessionFactory;
}
@Bean
public DataSourceTransactionManager transactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
@Bean
public Encoding encoding(){
Encoding encoding = new Encoding();
encoding.setEncoding("UTF-8");
return encoding;
}
}
@Configuration
// 由于容器类做了判断,此处不会出现无限递归及重复定义Bean的异常
@Import(classes = {Config.class,Config3.class})
public class Config2 {
@Bean
public IDemoService iDemoService2(){
// 注册第二个实现类
return new AnotherDemoServiceImpl();
}
}
@Configuration
public class Config3 {
@Bean
public IDemoService iDemoService(){
// 注册第一个实现类
return new DemoServiceImpl();
}
}
编写启动类
public class Client {
@Autowired
private DataSource mysql;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
@Qualifier("iDemoService")// 从多个IDemoService类型Bean中通过name指定
private IDemoService service;
public static void main(String[] args) throws Exception {
//使用指定配置文件初始化容器
Container container = new Container(Config.class);
// 进行自动装配
Client client = container.autowiredInit(Client.class);
// 调用方法
client.service.demo();
System.out.println(client.mysql);
}
}
以上是关于编写一个自己的IOC容器的主要内容,如果未能解决你的问题,请参考以下文章