使用注解和反射实现SpringIoC容器(简洁版)

本文介绍了如何通过自定义注解如@MyBean和@Configuration,配合反射机制,实现在Spring IoC容器中注册和管理Bean。作者详细展示了如何定义注解、创建实体类、业务逻辑和服务实现,并实现了一个MyApplicationContext类来解析和注入这些自定义注解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用注解和反射实现SpringIoC容器(简洁版)

首先明确一下自己的需求:我们需要自定义一些注解,比如类似于Spring的Bean或者Configuration注解,Bean注解主要是放在方法上的,目的是将当前方法的返回值放入SpringIoC容器中,Configuration主要是用于识别当前类是一个配置类的,注解定义好之后,就是利用反射实现,首先我们需要定义一个类,这个类可以识别Configuration、Bean之类的注解,并且可以获取加了@Bean注解的方法,执行方法,将方法的返回值加入到IoC容器中。

代码实现:

1、实体类

public class User {
    private Integer id ;
    private String name ;
    private Integer age ;
    public User(Integer id ,String name, Integer age) {
        this.id = id ;
        this.name = name;
        this.age = age;
    }
    public User(){;}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

2、业务层代码

public interface UserService {
    boolean add(User user) ;
    User modify(int id ) ;
}

public class UserServiceImpl implements UserService {
    @Override
    public boolean add(User user) {
        System.out.println("添加成功");
        return false;
    }
    @Override
    public User modify(int id) {
        System.out.println("修改成功");
        return null;
    }
}

@MyRepository
public class UserServiceImpl2 implements UserService {
    @Override
    public boolean add(User user) {
        return false;
    }

    @Override
    public User modify(int id) {
        return null;
    }
}


3、注解annotation包

/**
 * 当前注解放在方法上,表示根据方法的返回值生成一个bean对象,并放在ioc容器中
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBean {
    String value() default "" ;
}

/**
 * 放在类上,表示当前类是一个配置类
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConfiguration {
}

/**
 * 注解放在类上,可生成当前类的对象,并放在ioc容器中
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyRepository {
    String value() default "" ;
}

4、配置类

/**
 * 表示当前类是一个配置类
 */
@MyConfiguration
public class ClassConfig {
    @MyBean
    public User getUser(){
        return new User() ;
    }

    @MyBean
    public UserService getUserService(){
        return new UserServiceImpl() ;
    }
}

5、最后一步,就是要利用反射实现注解的功能:MyApplicationContext

这里有两个最主要的功能,一个是实现@Bean注解,另一个是实现@MyRepository注解。
(1)@Bean:这里主要的思路是:
第一步:找到配置类(也即类上有@MyConfiguration注解的类),这里在初始化的时候就可以给出了。
第二步:识别配置类上的每一个加了@MyBean注解的方法,将所有方法添加到map容器中。
第三步:执行map容器中的每一个方法,生成配置类中的所有bean
(2)@MyRepoitory:这里的主要思路是:
第一步:首先需要找到@MyResporitory标注的类,这里就需要扫描包了。
第二步:找到某一包下所有标注了@MyRepository的类并返回。
第三步:根据全限定类名生成对应的对象,并放入IoC容器中。

public class MyApplicationContext  {
    private Map<String,Method> beanMethods = new HashMap<>(); // 配置类里面的有MyBean注解的方法
    private Map<String,Object> beans = new HashMap<>() ; // 表示IoC容器里面装的bean
    private Class<?> configClass ;// 配置类

    public MyApplicationContext(){;}

    /**
     * 构造函数,初始化MyApplicationContext里面的配置类
     * @param configClass
     */
    public MyApplicationContext(Class<?> configClass) throws Exception {
        // 首先需要判断这是否时一个配置类,也即是否有configuration注解
        if(isMyConfiguration(configClass)){
            this.configClass = configClass ;
            //System.out.println(this.configClass);
            this.produceBeans() ;
        }else{
            System.out.println("当前类不是配置类");
        }
    }

    public MyApplicationContext(String packageName) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
        if (!packageName.isEmpty()){
            produceBeansByRepository(packageName) ;
        }
    }

    /**
     * 判断当前类是否是一个配置类
     * @return
     */
    public boolean isMyConfiguration(Class<?> clazz) throws Exception {
        boolean flag = false ;
        flag = clazz.isAnnotationPresent(MyConfiguration.class) ; // 底层也是使用了getAnnotation
        if(!flag){
            throw new Exception("配置文件缺少:@MyConfiguration注解") ;
        }
        return true ;
//        Annotation annotation = clazz.getAnnotation(MyConfiguration.class) ;
//        if(annotation!=null){
//            return true ;
//        }
//        return false ;
    }

    /**
     * 获取自动配置类里面的方法
     * @return
     */
    private Map<String,Method> getMethods(){
        Method[] methods = configClass.getDeclaredMethods() ;// 获取当前配置类的所有方法
        for(Method method:methods){
            MyBean beanAnno = method.getAnnotation(MyBean.class) ;
            if(null!=beanAnno){ // 当方法上有MyBean注解的时候,
                String methodName = "" ;
                if(beanAnno.value().equals("")){
                    // 如果MyBean注解上的value值为默认值,那么将方法名作为bean的名称
                    methodName = method.getName() ;
                }else{
                    methodName = beanAnno.value() ;
                }
                //System.out.println(methodName+"   "+method);
                beanMethods.put(methodName,method) ;
            }
        }
        return beanMethods ;
    }




    /**
     * 获取类的实例,这里调用的是无参构造函数,如果当前类不含有无参构造函数,将抛出异常NoSuchMethodException
     * @param <T>
     * @return
     */
    private <T> T getInstance(Class<T> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        return clazz.getDeclaredConstructor(null).newInstance() ;
    }

    /**
     * 生成所有配置类里面的bean
     */
    private void produceBeans() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException   {
        Map<String,Method> beanMethods = this.getMethods() ;
        Set<String> beanNames = beanMethods.keySet() ;// 得到所有bean的名字
        Object instance = getInstance(configClass) ;
        for(String name:beanNames){
            Method method = beanMethods.get(name) ;
            beans.put(name,method.invoke(instance)) ;
        }
    }

    /**
     * 生成所有通过MyRepository注解添加的bean
     */
    private void produceBeansByRepository(String packageName) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        // 第一步,扫描包,获取包下的所有类
        List<Class<?>> classList =  getClassesByPackage(packageName) ;
        String beanName = "" ;
        for (Class<?> aClass : classList) {
            MyRepository myRepository = aClass.getAnnotation(MyRepository.class) ;
            if(myRepository!=null){
                if(myRepository.value().equals("")){
                    beanName = aClass.toString().substring(aClass.toString().lastIndexOf(".")+1,aClass.toString().length()) ;
                    beanName = beanName.substring(0,1).toLowerCase()+beanName.substring(1,beanName.length());
                    System.out.println(beanName);
                }else {
                    beanName = myRepository.value() ;
                }
            }
            beans.put(beanName,getInstance(aClass));
        }
    }

    /**
     * 获取指定包名下的所有class
     * @param packageName
     */
    public List<Class<?>> getClassesByPackage(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> list = new ArrayList<>() ;
        String packageDirName = packageName.replace(".","/") ;
        // 获取当前包名的运行时的路径
        Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (dirs.hasMoreElements()){
            URL url = dirs.nextElement() ;
            // 将包名路径译码,转换成文件路径
            String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
            File file = new File(filePath) ;
            String[] files = file.list() ;
            for(int i=0;i<files.length; i++){
                files[i] = files[i].substring(0,files[i].lastIndexOf(".")) ;
                Class<?> clazz = Class.forName(packageName+"."+files[i]);
                list.add(clazz) ;
            }
        }
        return list;
    }


    /**
     * 通过bean的名称获取容器中的bean
     * @param <T>
     * @return
     */
    public <T> T getBean(String beanName){
        Set<String> beanNames = beans.keySet() ;
        for(String name:beanNames){
            if(name.equals(beanName)){
                return (T)beans.get(name) ;
            }
        }
        System.out.println("找不到"+beanName+"对应的Bean");
        return null ;
    }
}

6、测试类

public class Test {
    public static void main(String[] args) throws Exception {
        /**
         * 配置类的方式生成bean
         */
        MyApplicationContext context = new MyApplicationContext(ClassConfig.class) ;

        UserService userService = context.getBean("getUserService") ;
        System.out.println(userService);
        UserService userService1 = context.getBean("userService") ;
        System.out.println(userService1);

        /**
         * 扫描包,通过类上的注解的方式生成bean
         */
        MyApplicationContext context1 = new MyApplicationContext("com.example.springioc.ioc.service.impl") ;
        UserService userService2 = context.getBean("userServiceImpl2") ;
        System.out.println(userService);
    }
}

至此:注解和反射实现SpringIoC容器已经实现了,但是还有需要改进和完善的地方,比如控制IoC容器中一个类是单例的还是多例的,或者实现@Autowired自动注入等注解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值