精华内容
下载资源
问答
  • 什么是IOC容器?为什么需要IOC容器? 假设我们现在正在使用三层架构开发一个项目,其中有一个用户模块,包含登录、注册等功能。现在已经写好了User实体类和UserDao数据访问层: public class User { private ...

    假设我们现在正在使用三层架构开发一个项目,其中有一个用户模块,包含登录、注册等功能。现在已经写好了User实体类和UserDao数据访问层:

    public class User
    {
        private Integer id;
        private String username;
        private String password;
    
        // 以下是getter和setter方法
    }
    
    public interface UserDao
    {
        // 查找用户
        User get(String username, String password);
    
        // 插入用户
        void insert(User user);
    }
    
    public class UserDaoImpl implements UserDao
    {
        @Override
        public User getByUsername(String username, String password)
        {
            ...
        }
    
        @Override
        public void insert(User user)
        {
            ...
        }
    }
    

    UserDao封装了对数据库中的用户表进行操作。现在,需要一个UserService来封装登录、注册这两个业务逻辑:

    public interface UserService
    {
        // 登录
        User login(String username, String password);
    
        // 注册
        void register(User user);
    }
    
    public class UserServiceImpl implements UserService
    {
        @Override
        public User login(String username, String password)
        {
            User user = userDao.get(username, password); // userDao从哪里来?
            if (user == null)
            {
                // 用户名或密码错误
            }
            return user;
        }
    
        @Override
        public void register(User user)
        {
            userDao.insert(user); // userDao从哪里来?
        }
    }
    

    显然,UserServiceImpl需要一个UserDao的实例userDao来访问数据库,那么问题来了:这个userDao如何该获取呢?

    很多人都会用如下代码来获取userDao

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao = new UserDaoImpl();
        ...
    }
    

    直接在UserServiceImpl内部new一个UserDaoImpl,看起来很方便,也可以正常工作,但是它存在一些问题:

    1. 现在UserServiceImpl依赖于UserDaoImpl,如果这两个类是由两个不同的人开发的,则他们无法同时工作,因为在UserDaoImpl完成之前,UserServiceImpl无法通过编译
    2. UserServiceImpl无法被测试,因为它与某个特定的UserDao实现类绑定在了一起,我们不能把它替换成一个用于单元测试的MockUserDao
    3. 如果我们有多套数据库实现(即多个UserDao实现类),那么不能很方便地切换

    为了解决上面几个问题,可以使用一种被称为依赖注入的技巧:

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao;
    
        // 构造函数注入
        public UserServiceImpl(UserDao userDao)
        {
            this.userDao = userDao;
        }
        ...
    }
    
    // 外部程序
    UserService userService = new UserServiceImpl(new UserDaoImpl());
    

    现在,userDao不是由UserServiceImpl本身构造,而是让外部程序通过UserServiceImpl的构造函数传入进来,这种操作称为构造函数注入

    还可以使用另一种注入方式——setter方法注入

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao;
    
        // setter方法注入
        public void setUserDao(UserDao userDao)
        {
            this.userDao = userDao;
        }
        ...
    }
    
    // 外部程序
    UserService userService = new UserServiceImpl();
    userService.setUserDao(new UserDaoImpl());
    

    不论哪种注入方式,其基本逻辑都是一样的:组件不负责创建自己依赖的组件,而是让外部程序创建依赖组件,然后通过构造函数或setter函数注入进来。其实,这里也蕴含着控制反转的思想,因为创建依赖组件的任务从组件内部转移到了外部程序

    使用了依赖注入,前面的几个问题就迎刃而解了,因为UserServiceImpl不再依赖UserDao的具体实现类,我们可以轻松地替换UserDao的实现。

    但是问题又来了:该由谁负责对象的组装呢?

    答案是:应该由应用的最外层负责对象的组装。例如,在三层架构中,可以在controller层负责service类的组装;如果我们的程序有main函数,也可以在main函数中进行相关组件的组装。

    public class UserController
    {
        private UserService userService = new UserServiceImpl(new UserDaoImpl());
    
        public void handleLoginRequest(...)
        {
            userService.login(...);
            ...
        }
    }
    

    按照这种方式写程序,项目中的所有组件都按照依赖注入的方式管理自己的依赖,所有组件都由最外层统一组装,如果想替换掉某个组件的实现也很方便,看起来很美好。但是,当项目逐渐变得庞大,组件之间的依赖变多的时候,某个组件可能需要依赖于几十个大大小小的其它组件,创建这样的组件就成了一种折磨:

    // 创建一个复杂的组件
    Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    

    如果这个组件只需要被使用一次,看起来还是可以接受,但是如果这个组件在很多地方都要使用,那么在每个使用的地方都需要写一遍上面创建的代码,这将会产生大量的代码冗余:

    public class A
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f1()
        {
            // 使用c1
            ...
        }
    }
    
    public class B
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f2()
        {
            // 使用c1
            ...
        }
    }
    
    public class C
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f3()
        {
            // 使用c1
            ...
        }
    }
    

    更糟糕的是,如果组件c1依赖的其中一个组件将要被替换,那么上面所有创建c1的代码都要修改,这简直是维护的噩梦!

    为了避免这个问题,可以把系统中所有的组件放进一个“容器”中统一管理:

    public class Container
    {
        public static Component1 getComponent1()
        {
            ...
        }
    
        public static Component2 getComponent2()
        {
            ...
        }
    
        public static Component3 getComponent3()
        {
            ...
        }
        ...
    }
    

    然后,系统中所有需要使用组件的地方都通过Container类来获取:

    public class A
    {
        Component1 c1 = Container.getComponent1();
    
        public void f1()
        {
            // 使用c1
            ...
        }
    }
    
    public class B
    {
        Component1 c1 = Container.getComponent1();
    
        public void f2()
        {
            // 使用c1
            ...
        }
    }
    
    public class C
    {
        Component1 c1 = Container.getComponent1();
    
        public void f3()
        {
            // 使用c1
            ...
        }
    }
    

    使用这种方法,不论是获取组件还是替换组件都非常方便。但是,现在Container类是通过Java代码来实现的,如果系统中的组件有任何变动,就需要修改代码,然后重新编译项目。在某些场景下,我们可能需要在项目运行时动态地添加、移除或者替换组件。

    为了实现组件的动态管理,可以将如何创建组件以及组件之间的依赖关系等信息写入配置文件中,然后项目启动时通过读取配置文件来动态创建所有组件,再放到Container中。这样就可以在项目运行时修改配置文件中的组件信息,而无需重新编译,甚至无需重启服务器:

    // 创建Container
    Container container = new ContainerFactory("container.xml").create();
    
    // 获取Component1
    Component1 c1 = (Component1) container.create("c1");
    

    其实,上面的Container就是一个简单的IOC容器。IOC表示控制反转,意思是创建组件的工作不再由程序员控制,而是由IOC容器控制,程序员只负责告诉IOC容器如何创建某个组件,如果想要这个组件,直接从容器中取就是了,这就是IOC容器的基本逻辑。

    展开全文
  • 手写简化Spring IOC容器

    千次阅读 2021-11-12 16:51:00
    } 找到所有bean放进集合以后,下一步要做的呢,就是放进ioc容器里面,ioc容器这里用的是 private Map,Object> ioc = new HashMap(); 接下来从集合里面拿出我的BeanDefinition,然后分别拿出beanName和beanCLass,...

    基本上每一句代码我都加上了注释,希望有所帮助

    BeanDefinition,里面有beanName和beanClass,到时候可以封装到这个类里面

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class BeanDefinition {
        private String beanName;
        private Class beanClass;
    }
    

    最重要的类MyAnnotationConfigApplicationContext

    在他的这个构造函数里,我们首先先根据他传入的包的名字,把这个包里面的所有类都扫描一次

    Set<BeanDefinition> beanDefinitions = findBeanDefinitions(pack);
    

    findBeanDefinitions函数

    首先他要扫描包下面的所有类,然后得到这些类以后,我要去遍历一下,找到添加了注解的类,如果我添加了这个注解的话,返回的值就不是null(Component componentAnnotation = clazz.getAnnotation(Component.class)),接下来的话,判断一下有没有自定义的beanName吧,如果没有的话,我就给他改一下

    				//这样子说明我直接就写了component注解而已,后面没给出名字,那我就给他搞个名字
                    //名字的话,我把全类名前面的干掉
                    //再把Account改成account这样子,比较规范
                    if ("".equals(beanName)){
                        String packageName = clazz.getPackage().getName();
                        packageName += ".";
                        String clazzName = clazz.getName();
                        clazzName = clazzName.replaceAll(packageName,"");
                        beanName = clazzName.substring(0,1).toLowerCase()+clazzName.substring(1);
                    }
    

    现在我可以把beanName和得到的类封装成前面写下的BeanDefinition了,然后放进集合里面,为什么要集合呢,因为要去重.

     				//beanDefinitions集合放入通过封装的BeanDefinition对象
                    //BeanDefinition里面的field有bean的名字和类,现在封装好放进集合
                    //之所以选择set,是因为要去重,有一个就足够了
                    beanDefinitions.add(new BeanDefinition(beanName,clazz));
    

    下面是完整的该函数代码

    public Set<BeanDefinition> findBeanDefinitions(String pack){
            Set<BeanDefinition> beanDefinitions = new HashSet<>();
            //获取包下面的所有类
            Set<Class<?>> classes = MyTools.getClasses(pack);
            //遍历这些类,找到添加了注解的类
            Iterator<Class<?>> iterator = classes.iterator();
            while (iterator.hasNext()){
                Class<?> clazz = iterator.next();
                //返回对应注解的Class对象
                Component componentAnnotation = clazz.getAnnotation(Component.class);
                if (componentAnnotation!=null){
                    //获取component注解的值
                    String beanName = componentAnnotation.value();
                    //这样子说明我直接就写了component注解而已,后面没给出名字,那我就给他搞个名字
                    //名字的话,我把全类名前面的干掉
                    //再把Account改成account这样子,比较规范
                    if ("".equals(beanName)){
                        String packageName = clazz.getPackage().getName();
                        packageName += ".";
                        String clazzName = clazz.getName();
                        clazzName = clazzName.replaceAll(packageName,"");
                        beanName = clazzName.substring(0,1).toLowerCase()+clazzName.substring(1);
                    }
                    //beanDefinitions集合放入通过封装的BeanDefinition对象
                    //BeanDefinition里面的field有bean的名字和类,现在封装好放进集合
                    //之所以选择set,是因为要去重,有一个就足够了
                    beanDefinitions.add(new BeanDefinition(beanName,clazz));
                }
            }
            return beanDefinitions;
        }
    

    找到所有bean放进集合以后,下一步要做的呢,就是放进ioc容器里面,ioc容器这里用的是

    private Map<String,Object> ioc = new HashMap<>();
    

    接下来从集合里面拿出我的BeanDefinition,然后分别拿出beanName和beanCLass,通过类对象的getConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象。

    Object object = clazz.getConstructor().newInstance();
    

    clazz.getDeclaredFields()拿到所有的字段,遍历所有的字段,老样子判断添加了@Value注解的字段
    拿到字段名字以后去制作set方法

    						String value = valueAnnotation.value();
                            //拿到字段名字
                            String fieldName = declaredField.getName();
                            //要去做这个字段的set方法
                            String methodName = "set" +fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
                            Method method = clazz.getMethod(methodName,declaredField.getType());
    

    要完成类型赋值的话,得转一下类型,不然的话到时候直接就报错了,类型不匹配

    switch (declaredField.getType().getName()) {
                                case "java.lang.Integer":
                                    val = Integer.parseInt(value);
                                    break;
                                case "java.lang.String":
                                    val = value;
                                    break;
                                case "java.lang.Float":
                                    val = Float.parseFloat(value);
                                    break;
                            }
    

    method.invoke(“要调用的方法的名字所隶属的对象实体”,方法的参数值);

    method.invoke(object,val);
    

    搞定之后放进ioc容器了,用beanName去对应

    ioc.put(beanName,object);
    

    该函数的代码

    public void createBean(Set<BeanDefinition> beanDefinitions){
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()) {
                BeanDefinition beanDefinition = iterator.next();
                //从我封装好的对象里面我拿出我的类
                Class clazz = beanDefinition.getBeanClass();
                //从我封装好的对象里面拿出我的beanName
                String beanName = beanDefinition.getBeanName();
                try {
                    //通过类对象的getConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象
                    Object object = clazz.getConstructor().newInstance();
                    //获取此类中的所有字段
                    Field[] declaredFields = clazz.getDeclaredFields();
    
                    for (Field declaredField : declaredFields) {
                        //getAnnotation(),如果不是这样的注解,就返回null
                        Value valueAnnotation = declaredField.getAnnotation(Value.class);
                        if (valueAnnotation!=null){
                            String value = valueAnnotation.value();
                            //拿到字段名字
                            String fieldName = declaredField.getName();
                            //要去做这个字段的set方法
                            String methodName = "set" +fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
                            Method method = clazz.getMethod(methodName,declaredField.getType());
                            //完成数据类型转换
                            Object val = null;
                            //拿到数据类型的名字,来完成类型赋值
                            switch (declaredField.getType().getName()) {
                                case "java.lang.Integer":
                                    val = Integer.parseInt(value);
                                    break;
                                case "java.lang.String":
                                    val = value;
                                    break;
                                case "java.lang.Float":
                                    val = Float.parseFloat(value);
                                    break;
                            }
                            //传入值
                            //method.invoke("要调用的方法的名字所隶属的对象实体",方法的参数值);
                            method.invoke(object,val);
                        }
                    }
    
                    //搞定之后放进ioc容器了
                    ioc.put(beanName,object);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    

    轮到自动装载了

    老样子,传过去的还是集合,遍历一次所有的字段,看看有没有@Authwired注解的,看看是不是为null

    AutoWired annotation = declaredField.getAnnotation(AutoWired.class);
    

    如果找到了,那就是添加了这个注解的,那我还得再找一下里面有没有@Qualifier注解的

    Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
    

    如果有的话,那我就得通过这个bean的名字去ioc容器拿出来
    接下来拿到字段名字和方法等等这些

    							String beanName = qualifier.value();
                                //通过bean的名字去ioc容器里面把bean拿出来
                                //ioc容器我用的是hashMap
                                Object bean = getBean(beanName);
                                //拿字段名
                                String fieldName = declaredField.getName();
                                String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
                                Method method = clazz.getMethod(methodName, declaredField.getType());
    
    							//method.invoke("要调用的方法的名字所隶属的对象实体",方法的参数值);
                                //把另外一个bean传入,不然的话打印出来是null的
                                method.invoke(object,bean);
    

    该函数的完全代码

    public void autowireObject(Set<BeanDefinition> beanDefinitions){
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()){
                BeanDefinition beanDefinition = iterator.next();
                //因为我的这个beanDefinition对象里面,有一个beanName和一个BeanClass(放类的)
                Class clazz = beanDefinition.getBeanClass();
                //拿出这个类的所有字段
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    //看看这个字段有没有AutoWired这个注解
                    AutoWired annotation = declaredField.getAnnotation(AutoWired.class);
                    if (annotation!=null){
                        //看看有没有Qualifier这种自定义名字的注解
                        Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                        if (qualifier!=null){
                            //我用Qualifier自定义名字了
                            //byName
                            try {
                                String beanName = qualifier.value();
                                //通过bean的名字去ioc容器里面把bean拿出来
                                //ioc容器我用的是hashMap
                                Object bean = getBean(beanName);
                                //拿字段名
                                String fieldName = declaredField.getName();
                                String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
                                Method method = clazz.getMethod(methodName, declaredField.getType());
                                Object object = getBean(beanDefinition.getBeanName());
                                //传入值
                                //method.invoke("要调用的方法的名字所隶属的对象实体",方法的参数值);
                                //把另外一个bean传入,不然的话打印出来是null的
                                method.invoke(object,bean);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }else{
                            //byType
    
                        }
                    }
                }
            }
        }
    

    该类的完全代码

    ```java
    public class MyAnnotationConfigApplicationContext {
        private Map<String,Object> ioc = new HashMap<>();
        public MyAnnotationConfigApplicationContext(String pack){
            //遍历包,返回所有的目标类
            Set<BeanDefinition> beanDefinitions = findBeanDefinitions(pack);
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
    
            //根据原材料来创建Bean
            createBean(beanDefinitions);
    
            //自动装载
            autowireObject(beanDefinitions);
        }
    
        public void autowireObject(Set<BeanDefinition> beanDefinitions){
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()){
                BeanDefinition beanDefinition = iterator.next();
                //因为我的这个beanDefinition对象里面,有一个beanName和一个BeanClass(放类的)
                Class clazz = beanDefinition.getBeanClass();
                //拿出这个类的所有字段
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    //看看这个字段有没有AutoWired这个注解
                    AutoWired annotation = declaredField.getAnnotation(AutoWired.class);
                    if (annotation!=null){
                        //看看有没有Qualifier这种自定义名字的注解
                        Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                        if (qualifier!=null){
                            //我用Qualifier自定义名字了
                            //byName
                            try {
                                String beanName = qualifier.value();
                                //通过bean的名字去ioc容器里面把bean拿出来
                                //ioc容器我用的是hashMap
                                Object bean = getBean(beanName);
                                //拿字段名
                                String fieldName = declaredField.getName();
                                String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
                                Method method = clazz.getMethod(methodName, declaredField.getType());
                                Object object = getBean(beanDefinition.getBeanName());
                                //传入值
                                //method.invoke("要调用的方法的名字所隶属的对象实体",方法的参数值);
                                //把另外一个bean传入,不然的话打印出来是null的
                                method.invoke(object,bean);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }else{
                            //byType
    
                        }
                    }
                }
            }
        }
    
        public Object getBean(String beanName){
            return ioc.get(beanName);
        }
    
    
    
        //根据集合来创建bean
        public void createBean(Set<BeanDefinition> beanDefinitions){
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()) {
                BeanDefinition beanDefinition = iterator.next();
                //从我封装好的对象里面我拿出我的类
                Class clazz = beanDefinition.getBeanClass();
                //从我封装好的对象里面拿出我的beanName
                String beanName = beanDefinition.getBeanName();
                try {
                    //通过类对象的getConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象
                    Object object = clazz.getConstructor().newInstance();
                    //获取此类中的所有字段
                    Field[] declaredFields = clazz.getDeclaredFields();
    
                    for (Field declaredField : declaredFields) {
                        //getAnnotation(),如果不是这样的注解,就返回null
                        Value valueAnnotation = declaredField.getAnnotation(Value.class);
                        if (valueAnnotation!=null){
                            String value = valueAnnotation.value();
                            //拿到字段名字
                            String fieldName = declaredField.getName();
                            //要去做这个字段的set方法
                            String methodName = "set" +fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
                            Method method = clazz.getMethod(methodName,declaredField.getType());
                            //完成数据类型转换
                            Object val = null;
                            //拿到数据类型的名字,来完成类型赋值
                            switch (declaredField.getType().getName()) {
                                case "java.lang.Integer":
                                    val = Integer.parseInt(value);
                                    break;
                                case "java.lang.String":
                                    val = value;
                                    break;
                                case "java.lang.Float":
                                    val = Float.parseFloat(value);
                                    break;
                            }
                            //传入值
                            //method.invoke("要调用的方法的名字所隶属的对象实体",方法的参数值);
                            method.invoke(object,val);
                        }
                    }
    
                    //搞定之后放进ioc容器了
                    ioc.put(beanName,object);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public Set<BeanDefinition> findBeanDefinitions(String pack){
            Set<BeanDefinition> beanDefinitions = new HashSet<>();
            //获取包下面的所有类
            Set<Class<?>> classes = MyTools.getClasses(pack);
            //遍历这些类,找到添加了注解的类
            Iterator<Class<?>> iterator = classes.iterator();
            while (iterator.hasNext()){
                Class<?> clazz = iterator.next();
                //返回对应注解的Class对象
                Component componentAnnotation = clazz.getAnnotation(Component.class);
                if (componentAnnotation!=null){
                    //获取component注解的值
                    String beanName = componentAnnotation.value();
                    //这样子说明我直接就写了component注解而已,后面没给出名字,那我就给他搞个名字
                    //名字的话,我把全类名前面的干掉
                    //再把Account改成account这样子,比较规范
                    if ("".equals(beanName)){
                        String packageName = clazz.getPackage().getName();
                        packageName += ".";
                        String clazzName = clazz.getName();
                        clazzName = clazzName.replaceAll(packageName,"");
                        beanName = clazzName.substring(0,1).toLowerCase()+clazzName.substring(1);
                    }
                    //beanDefinitions集合放入通过封装的BeanDefinition对象
                    //BeanDefinition里面的field有bean的名字和类,现在封装好放进集合
                    //之所以选择set,是因为要去重,有一个就足够了
                    beanDefinitions.add(new BeanDefinition(beanName,clazz));
                }
            }
            return beanDefinitions;
        }
    }
    
    ## 需要的实体类
    ```java
    @Component
    @Data
    public class Account {
        @Value("1")
        private Integer id;
        @Value("张三")
        private String name;
        @Value("22")
        private Integer age;
    
        @AutoWired
        @Qualifier("myOrder")
        private Order order;
    }
    
    @Data
    @Component("myOrder")
    public class Order {
        @Value("jyu")
        private String address;
    }
    
    

    @Component注解

    //这种后面是type的,对应的是接口、类、枚举
    @Target(ElementType.TYPE)
    //runtime,注解不仅被保存到class文件中,就算jvm加载class文件以后,依然存在
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Component {
        String value() default "";
    }
    
    

    @Value注解

    //这种后面对应的是field的,对应的是字段和枚举的常量
    @Target(ElementType.FIELD)
    //runtime,注解不仅被保存到class文件中,就算jvm加载class文件以后,依然存在
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Value {
        String value();
    }
    

    @Qualifier注解

    //这种后面对应的是field的,对应的是字段和枚举的常量
    @Target(ElementType.FIELD)
    //runtime,注解不仅被保存到class文件中,就算jvm加载class文件以后,依然存在
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Qualifier {
        String value();
    }
    

    @AutoWired注解

    //这种后面对应的是field的,对应的是字段和枚举的常量
    @Target(ElementType.FIELD)
    //runtime,注解不仅被保存到class文件中,就算jvm加载class文件以后,依然存在
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoWired {
    }
    
    展开全文
  • Spring 初识 使用XML方式实现Spring IoC

    Spring 初识

    IoC控制反转

    • IoC控制反转,全称Inverse of Control,是一种设计理念
    • 由代理人创建与管理对象,消费者通过代理人来获得对象
    • IoC的目的是降低程序与程序之间直接耦合

    解决什么问题?

    对象直接饮用导致对象硬性关联,程序难以扩展。比如:顾客想要吃各种各样的苹果,就需要顾客取世界各地购买苹果,非常麻烦。

    加入Ioc容器将对象统一管理,让对象关联变为弱耦合。就像上面的场景:如果出现了摊贩,那么顾客就不用跑到各地去买水果了,而是由摊贩购买好之后,顾客根据自己的需求购买响应的苹果。

     DI依赖注入

    • IoC是设计理念,是现代程序设计遵循的标准,是宏伟目标
    • DI(Dependency Injection) 是具体技术实现,是微观实现
    • DI在Java中利用反射技术是心啊对象注入(Injection)

    Spring

    含义 

    • Spring可以从狭义与广义两个角度看
    • 狭义角度的Spring是指Spring框架(Spring Fremework)
    • 广义角度Spring是指Spring生态体系

    狭义的Spring框架

    • Spring框架是企业开发复杂性的恶一站式解决方案
    • SPring框架的核心是IoC容器与AOP面向切面编程
    • Spring IoC负责创建与管理系统对象,并在此基础上扩展功能

    广义Spring生态体系

    Spring IoC容器

    IoC容器是Spring生态的地基,用于统一创建与管理对象依赖

     如上:Spring IoC容器将A的依赖B对象注入进来,使用者只需要从中提取就可以了。

    Spring IoC容器职责

    • 对象的控制权交由第三方统一管理(IoC控制反转)
    • 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
    • 基于配置提高应用程序的可维护性与扩展性

    Spring IoC初体验

    eg: 比如三个孩子 Lily、Andy、Luna分别喜欢吃甜的、酸的、软的苹果。盘子里有三个苹果:红富士、青苹果、金帅。

    那孩子们如何获得喜欢的苹果呢?

    Apple.class

    package com.imooc.spring.ioc.entity;
    
    public class Apple {
        private String title;
        private String color;
        private String origin;
    
        public Apple() {
            //System.out.println("Apple对象已经创建, " + this);
        }
    
        public Apple(String title, String color, String origin) {
            this.title = title;
            this.color = color;
            this.origin = origin;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getOrigin() {
            return origin;
        }
    
        public void setOrigin(String origin) {
            this.origin = origin;
        }
    }
    

    Child.class

    package com.imooc.spring.ioc.entity;
    
    public class Child {
        private String name;
        private Apple apple;
    
        public Child() {
    
        }
    
        public Child(String name, Apple apple) {
            this.name = name;
            this.apple = apple;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Apple getApple() {
            return apple;
        }
    
        public void setApple(Apple apple) {
            this.apple = apple;
        }
    
        public void eat() {
            System.out.println(name + "吃到了" + apple.getOrigin() + "种植的" + apple.getTitle());
        }
    }
    

    传统方式

    Application.class

    public class Application {
        public static void main(String[] args) {
            Apple apple1 = new Apple("红富士", "红色", "欧洲");
            Apple apple2 = new Apple("青苹果", "绿色", "中亚");
            Apple apple3 = new Apple("金帅", "黄色", "中国");
    
            Child lily = new Child("莉莉", apple1);
            Child andy = new Child("安迪", apple2);
            Child luna = new Child("露娜", apple3);
    
            lily.eat();
            andy.eat();
            luna.eat();
    
        }
    }
    

    输出结果:

    但是这样会有一个弊端,如果后面需要更改喜欢的苹果的时候,我们就需要在源代码这里修改。在开发过程中,往往源代码涉及到的内容较多,修改复杂且容易影响其他地方,很容易出错。

    使用IoC容器方式:

    在resources目录下创建applicationontext.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 在IoC容器启动时,自动由Spring实例化Apple对象,取名sweetApple放入到容器中 -->
        <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="红富士"></property>
            <property name="origin" value="欧洲"></property>
            <property name="color" value="红色"></property>
        </bean>
    
        <bean id="sourApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="青苹果"></property>
            <property name="origin" value="中亚"></property>
            <property name="color" value="绿色"></property>
        </bean>
    
        <bean id="softApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="金帅"></property>
            <property name="origin" value="中国"></property>
            <property name="color" value="黄色"></property>
        </bean>
    
        <bean id="rdApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="蛇果"></property>
            <property name="origin" value="美国"></property>
            <property name="color" value="红色"></property>
        </bean>
    
        <bean id="lily" class="com.imooc.spring.ioc.entity.Child">
            <property name="name" value="莉莉"/>
            <property name="apple" ref="softApple"/>
        </bean>
    
        <bean id="andy" class="com.imooc.spring.ioc.entity.Child">
            <property name="name" value="安迪"/>
            <property name="apple" ref="rdApple"/>
        </bean>
    
        <bean id="luna" class="com.imooc.spring.ioc.entity.Child">
            <property name="name" value="露娜"/>
            <property name="apple" ref="sweetApple"/>
        </bean>
    </beans>

    SpringApplication.class

    public class SpringApplication {
        public static void main(String[] args) {
            //创建Spring IoC容器,并根据配置文件在容器中实例化对象
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            Apple sweetApple = context.getBean("sweetApple", Apple.class);
            System.out.println(sweetApple.getTitle());
            //从IoC容器中提取beanId=lily的对象
            Child lily = context.getBean("lily", Child.class);
            lily.eat();
            Child andy = context.getBean("andy", Child.class);
            andy.eat();
            Child luna = context.getBean("luna", Child.class);
            luna.eat();
    
        }
    }

    执行结果:

    这样做的好处是,我们涉及到修改时,只需要修改applicationContent.xml中的内容即可.

     使用XML方式实现Spring IoC

     上面说的applicationContext.xml就是使用xml文件方式实现Spring IoC的一种。

    实现方式:

    1. 基于构造方法对对象实例化
    2. 基于动态工厂实例化
    3. 基于工厂实例方法实例化

    Spring框架组成模块

     ApplicationContext实现类

    • ClassPathXmlApplicationContext
    • AnnotationConfigApplicationContext
    • WebApplicationContext

    基于构造方法对对象实例化

    默认构造方法

    在 applicationContext.xml文件:

    <!--bean标签默认通过默认构造方法创建对象-->
        <bean id="apple1" class="com.imooc.spring.ioc.entity.Apple">

    Apple.class 中修改为:

    public Apple() {
            System.out.println("Apple对象已创建," + this);
        }

    打印输出:

    带参构造方法

    applicationContext.xml

    <!--使用带参构造方法实例化对象-->
        <bean name="apple2" class="com.imooc.spring.ioc.entity.Apple">
            <constructor-arg name="title" value="红富士"/>
            <constructor-arg name="color" value="红色"/>
            <constructor-arg name="origin" value="欧洲"/>
            <constructor-arg name="price" value="19.8"/>
        </bean>
    
        <bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
            <constructor-arg index="0" value="红富士"/>
            <constructor-arg index="1" value="欧洲"/>
            <constructor-arg index="2" value="红色"/>
            <constructor-arg index="3" value="19.8"/>
        </bean>

     Apple.class

    public Apple(String title, String origin, String color, Float price) {
            System.out.println("通过带参构造方法创建对象, " + this);
            this.title = title;
            this.color = color;
            this.origin = origin;
            this.price = price;
        }

    打印输出:

    基于工厂实例化对象

    静态工厂

    /**
     * 静态工厂通过静态方法创建对象,隐藏创建对象的细节
     */
    public class AppleStaticFactory {
        public static Apple createSweetApple(){
            //logger.info("")
            Apple apple = new Apple();
            apple.setTitle("红富士");
            apple.setOrigin("欧洲");
            apple.setColor("红色");
            return apple;
        }
    }
    

    在applicationContext.xml中

    <!--利用静态工厂获取对象-->
        <bean id="apple4" class="com.imooc.spring.ioc.factory.AppleStaticFactory"
              factory-method="createSweetApple"/>

    测试:

     Apple apple4 = context.getBean("apple4", Apple.class);
            System.out.println(apple4.getTitle());

     输出:

    工厂实例

    /**
     * 工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的实例方法创建对象的过程
     */
    public class AppleFactoryInstance {
        public Apple createSweetApple(){
            Apple apple = new Apple();
            apple.setTitle("红富士");
            apple.setOrigin("欧洲");
            apple.setColor("红色");
            return apple;
        }
    }

    在applicationContext.xml中

     <!--利用工厂实例方法获取对象-->
        <bean id="factoryInstance" class="com.imooc.spring.ioc.factory.AppleFactoryInstance"/>
        <bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>

    然后测试输出即可.

    从IoC容器中提取 Bean

    方式一:
    Apple apple = context.getBean("apple", Apple.class);
    
    方式二:
    Apple apple = (Apple)context.getBean("apple");

    推荐使用方式一

    id和name属性相同点

    • bean id 与 name 都是设置对象在IoC容器中唯一标识
    • 两者在同一个配置文件中都不允许出现重复
    • 两者允许在多个配置文件中出现重复,新对象覆盖旧对象

    id和name属性不同点

    • id要求更为严格,一次只能定义一个对象标识(推荐)
    • name更为宽松,一次允许定义多个对象标识
    • tips:id与name的命名要求有意义,按驼峰命名书写
    <bean name="apple2,apple7" class="com.imooc.spring.ioc.entity.Apple">
            <constructor-arg name="title" value="红富士2号"/>
            <constructor-arg name="color" value="红色"/>
            <constructor-arg name="origin" value="欧洲"/>
            <constructor-arg name="price" value="29.8"/>
        </bean>
        <!-- 没有id与name的bean默认使用类名全称作为bean标识 -->
        <bean class="com.imooc.spring.ioc.entity.Apple">
            <constructor-arg name="title" value="红富士3号"/>
            <constructor-arg name="color" value="红色"/>
            <constructor-arg name="origin" value="欧洲"/>
            <constructor-arg name="price" value="29.8"/>
        </bean>

    路径匹配表达式

     ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

     加载多个配置文件:

    String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
            //初始化IoC容器并实例化对象
           ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);

    路径表达式

     对象依赖注入

    依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作

    •  基于setter方法注入对象
    • 基于构造方法注入对象

    基于setter方法注入对象

    <property name="" value=""/> : 静态属性

    <property name=""ref=""/> : 动态属性

    <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
            <!-- IoC容器自动利用反射机制运行时调用setXXX方法为属性赋值 -->
            <property name="title" value="红富士"/>
            <property name="color" value="红色"/>
            <property name="origin" value="欧洲"/>
            <property name="price" value="19.8"/>
        </bean>
    
        <bean id="lily" class="com.imooc.spring.ioc.entity.Child">
            <property name="name" value="莉莉"/>
            <!-- 利用ref注入依赖对象 -->
            <property name="apple" ref="sweetApple"/>
        </bean>

    依赖注入的优势

    举例:

    applicationContext-dao.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="bookDao" class="com.imooc.spring.ioc.bookshop.dao.BookDaoOracleImpl">
    
        </bean>
    </beans>

    applicationContext-serice.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="bookService" class="com.imooc.spring.ioc.bookshop.service.BookService">
            <!--id=bookDao-->
            <property name="bookDao" ref="bookDao"/>
        </bean>
    </beans>

    BookDao

    public interface BookDao {
        public void insert();
    }

    BookDaoImpl

    public class BookDaoImpl implements BookDao {
        public void insert() {
            System.out.println("向MySQL Book表插入一条数据");
        }
    }
    

    BookService

    public class BookService {
        private BookDao bookDao ;
        public void purchase(){
            System.out.println("正在执行图书采购业务方法");
            bookDao.insert();
        }
    
        public BookDao getBookDao() {
            return bookDao;
        }
    
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    }
    

    BookShopApplication

    public class BookShopApplication {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
            BookService bookService = context.getBean("bookService", BookService.class);
            bookService.purchase();
        }
    }

    输出:

    优点: 比如数据库由MySQL迁移到Oracle.这时候只需要将BookDao这个Bean的class更改为Oracle即可.

    注入集合对象

     注入List

    注入set

     

    注入map 

    注入Properties 

     Properties中的key和value只能是String类型

    查看容器内对象 

    //获取容器内所有beanId数组
            String[] beanNames = context.getBeanDefinitionNames();
    
    for (String beanName:beanNames){
                System.out.println(beanName);
                System.out.println("类型:" + context.getBean(beanName).getClass().getName());
                System.out.println("内容:" + context.getBean(beanName));
            }
     Computer computer = context.getBean("com.imooc.spring.ioc.entity.Computer", Computer.class);

    当有多个相同类的Bean时:

     <bean class="com.imooc.spring.ioc.entity.Computer">
            <constructor-arg name="brand" value="微星"/>
            <constructor-arg name="type" value="台式机"/>
            <constructor-arg name="sn" value="8389280012"/>
            <constructor-arg name="price" value="3000"/>
        </bean>
    
        <bean class="com.imooc.spring.ioc.entity.Computer">
            <constructor-arg name="brand" value="华硕"/>
            <constructor-arg name="type" value="笔记本"/>
            <constructor-arg name="sn" value="9089380012"/>
            <constructor-arg name="price" value="6000"/>
        </bean>
    Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#0", Computer.class);
    Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#1", Computer.class);

    Bean对像的作用域及生命周期

    bean scope属性

    • bean scope属性用于决定对象何时被创建与作用范围
    • bean scope配置将影响容器内对象的数量
    • 默认情况下bean会在IoC容器创建后自动实例化,全局唯一

    scope用法

    bean scope属性清单

     singleton的线程安全问题

     

    singleton在容器是单例多线程执行,存在线程安全风险 

    在单线程下:

    当在多线程中:在A用户操作了a.setNum(1)之后,在另一个线程,B用户操了a.setNum(2) .这个时候,A用户打印a.num 就会出现 2,和A用户设置值不同.

    prototype多例 

    prototype在容器中多实例,占用更多资源,不存在线程安全问题 

    singleton和prototype对比

    bean生命周期

     

    细节调整

    •  prototype使对象创建与init_method延迟至执行业务
    • prototype使对象不再受IoC容器管理,不再触发destroy-method
    • 延迟加载lazy-init属性可让对象创建与初始化延迟到执行代码阶段

    生命周期在实战中的应用

    singleton和prototype的初始化

    singleton的初始化:

    applicationContext.xml中:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao"/>
    
        
    </beans>

    在SpringApplication中添加:

    public class SpringApplication {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
         
        }
    }
    

    打印输出:

    也就是说在IoC容器初始化的时候,为我们创建了bean. 

    prototype初始化:

     applicationContext.xml中:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>
    
        
    </beans>

    然后在SpringApplication中测试:

    public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            System.out.println("======IoC容器已初始化=======");
           UserDao userDao1 = context.getBean("userDao", UserDao.class);
    }

    输出为:

    也就是说,在IoC容器创建的时候,并没有为我们初始化bean对像,而是在我们获取对象的时候,才初始化.singleton模式的初始化顺序跟书写顺序一致。

    下面会初始化两个bean对象.

    <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>
    
        <bean id="userService" class="com.imooc.spring.ioc.service.UserService">
            <property name="userDao" ref="userDao"/>
        </bean>

    在多数情况下,Dao层,server层,control层都是单例的。

    实现极简IoC容器

    目录结构:

     Apple类

    package com.imooc.spring.ioc.entity;
    
    public class Apple {
        private String title;
        private String color;
        private String origin;
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getOrigin() {
            return origin;
        }
    
        public void setOrigin(String origin) {
            this.origin = origin;
        }
    }
    

     然后在applicationContext.xml中添加bean的信息:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="红富士"/>
            <property name="color" value="红色"/>
            <property name="origin" value="欧洲"/>
        </bean>
    </beans>

    自定义记载配置文件的方法:

    接口:ApplicationContext

    package com.imooc.spring.ioc.context;
    
    public interface ApplicationContext {
        public Object getBean(String beanId);
    }
    

    实现类ClassPathXmlApplicationContext 

    public class ClassPathXmlApplicationContext implements ApplicationContext {
        private Map iocContainer = new HashMap();
    
        /**
         * 读取配置文件
         */
        public ClassPathXmlApplicationContext() {
            try {
                String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
                // 进行URL解码
                filePath = new URLDecoder().decode(filePath, "UTF-8");
    
                /**
                 * 引入 dom4j和jaxen
                 * Dom4j是Java的XML解析组件
                 * Jaxen是Xpath表达式解释器
                 */
                SAXReader reader = new SAXReader();
                Document document = reader.read(new File(filePath));
                // 读取 "bean" 标签
                List<Node> beans = document.getRootElement().selectNodes("bean");
    
                for (Node node : beans) {
                    Element ele = (Element) node;
    
                    String id = ele.attributeValue("id");
                    String className = ele.attributeValue("class");
    
                    Class c = Class.forName(className);
                    Object obj = c.newInstance();
    
                    List<Node> properties = ele.selectNodes("property");
                    for (Node p : properties) {
                        Element property = (Element) p;
                        String propName = property.attributeValue("name");
                        String propValue = property.attributeValue("value");
    
                        // set方法
                        String setMethodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
                        System.out.println("准备执行" + setMethodName + "方法注入数据");
                        Method setMethod = c.getMethod(setMethodName, String.class);
                        setMethod.invoke(obj, propValue);//通过setter方法注入数据
                    }
                    iocContainer.put(id, obj);
                }
                System.out.println(iocContainer);
                System.out.println("IOC容器初始化完毕");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public Object getBean(String beanId) {
            return iocContainer.get(beanId);
        }
    }
    

    然后进行测试:

    public class Application {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext();
            Apple apple = (Apple)context.getBean("sweetApple");
            System.out.println(apple);
        }
    }

    执行结果:

     

     

    使用注解方式实现Spring IoC

    基于注解的优势

    • 摆脱繁琐的XML形式的bean与依赖注入配置
    • 基于“声明式”的原则,更适合轻量级的现代企业应用
    • 让代码可读性变得更好,研发人员拥有更好的开发体验

    三类注解:

    1. 组件类型注解 - 声明当前类的功能与职能
    2. 自动装配注解 - 根据属性特种自动注入对象
    3. 元数据注解 - 更细化的辅助IoC容器管理对象的注解

    四种组件类型注解

    1. @Compponent  : 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化
    2. @Controller  : 语义注解,说明当前类是MVC应用中的控制器类
    3. @Service. : 语义注解,说明当前类是Service业务服务类
    4. @Repository. : 语义注解,说明当前类是用于业务持久层,通常描述对应Dao类

      

    两类自动装配注解

    1. 按类型装配
      1. @Autowired : 按容器内对象类型动态注入属性,由Spring机构提供
      2. @Inject : 基于JSR-330(Dependency Injection for Java)标准,其他同@Autowired,但不支持required属性
    2. 按名称装配
      1. @Named : 与@Inject配合使用,JSR-330规范,按属性名自动装配属性
      2. @Resource :基于JSR-330规范,优先按名称、再按类型智能匹配

    元数据注解

    @Value 的读取属性文件

    @Value("com.imooc")
    private String config;

    就是相当于在初始化的时候,config的值为"com.inooc",主要用户加载配置文件中的数据: @Value("${config}")

    使用Java Config方式实现Spring IoC

    基于Java Config的优势

    • 完全摆脱XML的束缚,使用独立Java类管理对象与依赖
    • 注解配置相对分散,利用Java Config可对配置集中管理
    • 可以在编译时进行依赖检查,不容易出错

    Java Config核心注解

    Java Config初始化方式

     

     举例:

    package com.imooc.spring.ioc;
    
    import com.imooc.spring.ioc.controller.UserController;
    import com.imooc.spring.ioc.dao.EmployeeDao;
    import com.imooc.spring.ioc.dao.UserDao;
    import com.imooc.spring.ioc.service.UserService;
    import org.springframework.context.annotation.*;
    
    @Configuration //当前类是一个配置类,用于替代applicationContext.xml
    @ComponentScan(basePackages = "com.imooc")
    public class Config {
        @Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
        public UserDao userDao(){
            UserDao userDao = new UserDao();
            System.out.println("已创建" + userDao);
            return userDao;
        }
    
        @Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
        @Primary
        public UserDao userDao1(){
            UserDao userDao = new UserDao();
            System.out.println("已创建" + userDao);
            return userDao;
        }
    
        @Bean
        //先按name尝试注入,name不存在则按类型注入
        public UserService userService(UserDao udao , EmployeeDao employeeDao){
            UserService userService = new UserService();
            System.out.println("已创建" + userService);
            userService.setUserDao(udao);
            System.out.println("调用setUserDao:" + udao);
            userService.setEmployeeDao(employeeDao);
            return userService;
        }
    
        @Bean //<bean id="xxx" clas="xxx">
        @Scope("prototype")
        public UserController userController(UserService userService){
            UserController userController = new UserController();
            System.out.println("已创建" + userController);
            userController.setUserService(userService);
            System.out.println("调用setUserService:" + userService);
            return userController;
        }
    }
    

    测试:

    public class SpringApplication {
        public static void main(String[] args) {
            //基于Java Config配置IoC容器的初始化
            ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
            System.out.println("=========================");
            String[] ids = context.getBeanDefinitionNames();
            for(String id : ids){
                System.out.println(id + ":" + context.getBean(id));
            }
        }
    }

     

    展开全文
  • 学习laravel快小一年了,到现在才去研究laravel 的核心 '容器 IOC' 这些概念. 写项目的时候有大概看看关于IOC 文章, 但是没有深入理解,只是有个概念,赶着写代码, 虽然代码也写的很菜 · - ·这几天花了点时间研究...

    学习laravel快小一年了,到现在才去研究laravel 的核心 '容器 IOC' 这些概念. 写项目的时候有大概看看关于IOC 文章, 但是没有深入理解,只是有个概念,赶着写代码, 虽然代码也写的很菜 · - ·

    这几天花了点时间研究了一下laravel 的核心 ‘服务容器’ 然后理解了一下关于IOC 的概念. 不敢说百分百掌握了,但是比之前是有一定加深. 所以决定把自己理解的分享一下, 把自己的第一次博文献给laravel. 理解不到位的还请各位大牛多多指正.

    1.依赖

    IOC( inversion of controller )叫做控制反转模式,也可以称为(dependency injection ) 依赖注入模式。要理解依赖注入的概念我们先理解下什么依赖

    //支付宝支付

    class Alipay {

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by alipay';

    }

    }

    //微信支付

    class Wechatpay {

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by wechatpay';

    }

    }

    //银联支付

    class Unionpay{

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by unionpay';

    }

    }

    //支付账单

    class PayBill {

    private $payMethod;

    public function __construct( )

    {

    $this->payMethod= new Alipay ();

    }

    public function payMyBill()

    {

    $this->payMethod->pay();

    }

    }

    $pb = new PayBill ();

    $pb->payMyBill();

    通过上面的代码我们知道,当我们创建一个class PayBill 的实例的时候, PayBill的构造函数里面有{ $this->payMethod= new Alipay (); }, 也就是实例化了一个class Alipay . 这个时候依赖就产生了, 这里可以理解为当我想用支付宝支付的时候, 那我首先要获取到一个支付宝的实例,或者理解为获取支付宝的功能支持. 当用我们完 new 关键字的时候, 依赖其实已经解决了,因为我们获取了Alipay 的实例.

    其实在我知道ioc概念之前,我的代码中大部分都是这种模式 ~ _ ~ . 这种有什么问题呢, 简单来说, 比如当我想用的不是支付宝而是微信的时候怎么办, 你能做的就是修改Payment 的构造函数的代码,实例化一个微信支付Wechatpay.

    如果我们的程序不是很大的时候可能还感觉不出什么,但是当你的代码非常复杂,庞大的时候,如果我们的需求经常改变,那么修改代码就变的非常麻烦了。所以ioc 的思想就是不要在 class Payment 里面用new 的方式去实例化解决依赖, 而且转为由外部来负责,简单一点就是内部没有new 的这个步骤,通过依赖注入的方式同样的能获取到支付的实例.

    2.依赖注入

    依赖我们知道了是什么意思,那依赖注入又是什么意思呢,我们把上面的代码拓展一下

    //支付类接口

    interface Pay

    {

    public function pay();

    }

    //支付宝支付

    class Alipay implements Pay {

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by alipay';

    }

    }

    //微信支付

    class Wechatpay implements Pay {

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by wechatpay';

    }

    }

    //银联支付

    class Unionpay implements Pay {

    public function __construct(){}

    public function pay()

    {

    echo 'pay bill by unionpay';

    }

    }

    //付款

    class PayBill {

    private $payMethod;

    public function __construct( Pay $payMethod)

    {

    $this->payMethod= $payMethod;

    }

    public function payMyBill()

    {

    $this->payMethod->pay();

    }

    }

    //生成依赖

    $payMethod = new Alipay();

    //注入依赖

    $pb = new PayBill( $payMethod );

    $pb->payMyBill();

    上面的代码中,跟之前的比较的话,我们加入一个Pay 接口, 然后所有的支付方式都继承了这个接口并且实现了pay 这个功能. 可能大家会问为什么要用接口,这个我们稍后会讲到.

    当我们实例化PayBill的之前, 我们首先是实例化了一个Alipay,这个步骤就是生成了依赖了,然后我们需要把这个依赖注入到PayBill 的实例当中,通过代码我们可以看到 { $pb = new PayBill( payMethod ); }, 我们是通过了构造函数把这个依赖注入了PayBill 里面. 这样一来 $pb 这个PayBill 的实例就有了支付宝支付的能力了.

    把class Alipay 的实例通过constructor注入的方式去实例化一个 class PayBill. 在这里我们的注入是手动注入, 不是自动的. 而Laravel 框架实现则是自动注入.

    3.反射

    在介绍IOC 的容器之前我们先来理解下反射的概念(reflection),因为IOC 容器也是要通过反射来实现的.从网上抄了一段来解释反射是什么意思

    "反射它指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言"

    举个简单的例子

    class B{

    }

    class A {

    public function __construct(B $args)

    {

    }

    public function dosomething()

    {

    echo 'Hello world';

    }

    }

    //建立class A 的反射

    $reflection = new ReflectionClass('A');

    $b = new B();

    //获取class A 的实例

    $instance = $reflection ->newInstanceArgs( [ $b ]);

    $instance->dosomething(); //输出 ‘Hellow World’

    $constructor = $reflection->getConstructor();//获取class A 的构造函数

    $dependencies = $constructor->getParameters();//获取class A 的依赖类

    dump($constructor);

    dump($dependencies);

    dump 的得到的$constructor 和 $dependencies 結果如下

    //constructor

    ReflectionMethod {#351

    +name: "__construct"

    +class: "A"

    parameters: array:1 []

    extra: array:3 []

    modifiers: "public"

    }

    //$dependencies

    array:1 [

    0 => ReflectionParameter {#352

    +name: "args"

    position: 0

    typeHint: "B"

    }

    ]

    通过上面的代码我们可以获取到 class A 的构造函数,还有构造函数依赖的类,这个地方我们依赖一个名字为 'args' 的量,而且通过TypeHint可以知道他是类型为 Class B; 反射机制可以让我去解析一个类,能过获取一个类里面的属性,方法 ,构造函数, 构造函数需要的参数。 有个了这个才能实现Laravel 的IOC 容器.

    4.IOC容器

    接下来介绍一下Laravel 的IOC服务容器概念. 在laravel框架中, 服务容器是整个laravel的核心,它提供了整个系统功能及服务的配置, 调用. 容器按照字面上的理解就是装东西的东西,比如冰箱, 当我们需要冰箱里面的东西的时候直接从里面拿就行了. 服务容器也可以这样理解, 当程序开始运行的时候,我们把我们需要的一些服务放到或者注册到(bind)到容器里面,当我需要的时候直接取出来(make)就行了. 上面提到的 bind 和 make 就是注册 和 取出的 两个动作.

    5. IOC 容器代码

    好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 当然小伙伴完全可以试着运行一下这段代码,然后调试一下,这样会更有助于理解.

    //容器类装实例或提供实例的回调函数

    class Container {

    //用于装提供实例的回调函数,真正的容器还会装实例等其他内容

    //从而实现单例等高级功能

    protected $bindings = [];

    //绑定接口和生成相应实例的回调函数

    public function bind($abstract, $concrete=null, $shared=false) {

    //如果提供的参数不是回调函数,则产生默认的回调函数

    if(!$concrete instanceof Closure) {

    $concrete = $this->getClosure($abstract, $concrete);

    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

    }

    //默认生成实例的回调函数

    protected function getClosure($abstract, $concrete) {

    return function($c) use ($abstract, $concrete) {

    $method = ($abstract == $concrete) ? 'build' : 'make';

    return $c->$method($concrete);

    };

    }

    public function make($abstract) {

    $concrete = $this->getConcrete($abstract);

    if($this->isBuildable($concrete, $abstract)) {

    $object = $this->build($concrete);

    } else {

    $object = $this->make($concrete);

    }

    return $object;

    }

    protected function isBuildable($concrete, $abstract) {

    return $concrete === $abstract || $concrete instanceof Closure;

    }

    //获取绑定的回调函数

    protected function getConcrete($abstract) {

    if(!isset($this->bindings[$abstract])) {

    return $abstract;

    }

    return $this->bindings[$abstract]['concrete'];

    }

    //实例化对象

    public function build($concrete) {

    if($concrete instanceof Closure) {

    return $concrete($this);

    }

    $reflector = new ReflectionClass($concrete);

    if(!$reflector->isInstantiable()) {

    echo $message = "Target [$concrete] is not instantiable";

    }

    $constructor = $reflector->getConstructor();

    if(is_null($constructor)) {

    return new $concrete;

    }

    $dependencies = $constructor->getParameters();

    $instances = $this->getDependencies($dependencies);

    return $reflector->newInstanceArgs($instances);

    }

    //解决通过反射机制实例化对象时的依赖

    protected function getDependencies($parameters) {

    $dependencies = [];

    foreach($parameters as $parameter) {

    $dependency = $parameter->getClass();

    if(is_null($dependency)) {

    $dependencies[] = NULL;

    } else {

    $dependencies[] = $this->resolveClass($parameter);

    }

    }

    return (array)$dependencies;

    }

    protected function resolveClass(ReflectionParameter $parameter) {

    return $this->make($parameter->getClass()->name);

    }

    }

    上面的代码就生成了一个容器,下面是如何使用容器

    $app = new Container();

    $app->bind("Pay", "Alipay");//Pay 为接口, Alipay 是 class Alipay

    $app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill可以当做是Class PayBill 的服务别名

    //通过字符解析,或得到了Class PayBill 的实例

    $paybill = $app->make("tryToPayMyBill");

    //因为之前已经把Pay 接口绑定为了 Alipay,所以调用pay 方法的话会显示 'pay bill by alipay '

    $paybill->payMyBill();

    当我们实例化一个Container得到 $app 后, 我们就可以向其中填充东西了

    $app->bind("Pay", "Alipay");

    $app->bind("tryToPayMyBill", "PayBill");

    当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,我们来看下

    array:2 [

    "App\Http\Controllers\Pay" => array:2 [

    "concrete" => Closure {#355

    class: "App\Http\Controllers\Container"

    this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …}

    parameters: array:1 [

    "$c" => []

    ]

    use: array:2 [

    "$abstract" => "App\Http\Controllers\Pay"

    "$concrete" => "App\Http\Controllers\Alipay"

    ]

    file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"

    }

    "shared" => false

    ]

    "tryToPayMyBill" => array:2 [

    "concrete" => Closure {#359

    class: "App\Http\Controllers\Container"

    this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …}

    parameters: array:1 [

    "$c" => []

    ]

    use: array:2 [

    "$abstract" => "tryToPayMyBill"

    "$concrete" => "\App\Http\Controllers\PayBill"

    ]

    file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"

    }

    "shared" => false

    ]

    ]

    当执行 $paybill = $app->make("tryToPayMyBill"); 的时候, 程序就会用make方法通过闭包函数的回调开始解析了.

    解析'tryToPayBill' 这个字符串, 程序通过闭包函数 和build方法会得到 'PayBill' 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 然后程序还会以类似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = 'PayBill'. 这个时候反射就派上了用场, 大家有没有发现,PayBill 不就是 class PayBill 吗? 然后在通过反射的方法ReflectionClass('PayBill') 获取PayBill 的实例. 之后通过getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依赖

    //$constructor = $reflector->getConstructor();

    ReflectionMethod {#374

    +name: "__construct"

    +class: "App\Http\Controllers\PayBill"

    parameters: array:1 [

    "$payMethod" => ReflectionParameter {#371

    +name: "payMethod"

    position: 0 typeHint: "App\Http\Controllers\Pay"

    }

    ]

    extra: array:3 [

    "file" => "C:\project\test\app\Http\Controllers\IOCController.php"

    "line" => "83 to 86"

    "isUserDefined" => true

    ]

    modifiers: "public"

    }

    //$dependencies = $constructor->getParameters();

    array:1 [

    0 => ReflectionParameter {#370

    +name: "payMethod"

    position: 0

    typeHint: "App\Http\Controllers\Pay"

    }

    ]

    接着,我们知道了有'Pay'这个依赖之后呢,我们要做的就是解决这个依赖,通过 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,还有之前的绑定$app->bind("Pay", "Alipay"); 在build 一次的时候,通过 return new $concrete;到这里我们得到了这个Alipay 的实例

    if(is_null($constructor)) {

    return new $concrete;

    }

    到这里我们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 Alipay. 到这里还没结束

    $instances = $this->getDependencies($dependencies);

    上面的$instances 数组只有一个element 那就是 Alipay 实例

    array:1 [0 =>Alipay

    {#380}

    ]

    最终通过 newInstanceArgs() 方法, 我们获取到了 PayBill 的实例。

    return $reflector->newInstanceArgs($instances);

    到这里整个流程就结束了, 我们通过 bind 方式绑定了一些依赖关系, 然后通过make 方法 获取到到我们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.

    好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.

    $app->bind("Pay", "Wechatpay");

    $app->bind("tryToPayMyBill", "PayBill");

    $paybill = $app->make("tryToPayMyBill");

    $paybill->payMyBill();

    是不是很简单呢, 只要把绑定从'Alipay' 改成 'Wechatpay' 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.

    好了,到这里不知道小伙伴有没有理解呢,我建议大家可以试着运行下这些代码, 这样理解起来会更快.同时推荐大家去看看 《laravel 框架关键技术解析》这本书,写的还是不错的.

    展开全文
  • IOC容器

    2021-05-22 20:01:38
    IOC容器 1、IOC底层原理 什么是IOC 控制反转,把对象创建和对象之间的调用过程交给Spring进行管理 使用IOC目的:为了耦合度降低 IOC底层原理 xml解析、工厂模式、反射 画图讲解IOC底层原理 2、IOC接口...
  • Spring Ioc容器与Bean管理 Spring快速入门 Spring XML配置 对象实例化配置 依赖注入配置 注解与Java Config Spring 单元测试 IoC控制反转 IoC控制反转,全称Inverse of Control,是一种设计理念 由代理人来创建与...
  • 简单IOC容器实现

    2021-03-01 06:51:21
    前言本文是为了学习Spring IOC容器的执行过程而写,不能完全代表Spring IOC容器,只是简单实现了容器的依赖注入和控制反转功能,无法用于生产,只能说对理解Spring容器能够起到一定的作用。开始创建项目创建Gradle...
  • 如何实现一个IOC容器?

    2021-11-09 07:31:04
    如何实现一个IOC容器? IOC(Inversion of Control),意思是控制反转,不是什么技术,而是一种设计思想,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。 在传统的程序设计中,我们直接...
  • Spring IOC容器生命周期阶段总结

    千次阅读 2021-01-19 10:49:31
    Spring容器的生命周期是面试的高频题目,但是这个生命周期非常复杂,想要完全说清楚几乎不可能。但对全局有个把控,知道每个阶段大概所做的事情是很重要的,尤其在编写框架或者扩展Spring的时候尤其重要。
  • Spring的IoC容器

    2021-05-08 17:50:37
    Spring的IoC容器 IoC在程序开发中,实例的创建不再由调用者管理,而是由Spring容器创建。Spring容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了Spring容器中,控制权发生...
  • 这次我们一起实现一个简单IOC容器。让大家更容易理解Spring IOC的基本原理。这里会涉及到一些java反射的知识,如果有不了解的,可以自己去找些资料看看。注意在上一篇文章,我说,启动IOC容器时,Spring会将xml文件...
  • 使用IoC容器是面向对象开发中非常方便的解耦模块之间的依赖的方法。各个模块之间不依赖于实现,而是依赖于接口,然后在构造函数或者属性或者方法中注入特定的实现,方便了各个模块的拆分以及模块的独立单元测试。 在...
  • IOC概念 IOC思想 Spring管理对象 集成依赖 spring的配置文件(Applicationcontext.xml) 创建实体类User Spring对Bean的实例化方式 基于配置形式 1.通过无参构造函数实例化 2.通过静态工厂方式实例化 3....
  • 依赖一个类似工厂的IOC容器 将对象的创建、依赖关系的管理以及生命周期交由IOC容器管理 降低系统在实现上的复杂性和耦合度,易于扩展,满足开闭原则 依赖注入DI,上层控制下层 依赖注入的方式 Setter Interface...
  • C#写简单的IOC容器

    千次阅读 2021-11-16 09:14:06
    文章目录前言一、IOC和DIP1.IOC2.DIP二、写一个超简单的IOC容器实例1.代码准备2.IOC容器3.调用结果总结 前言 IOC个人之前一直搞不明白,不够理解,写这篇文章也是为了加强学习。 一、IOC和DIP 1.IOC IOC中文叫做...
  • IoC容器相关职责和主要实现 控制反转有以下几个设计目的: 任务执行和实现之间解耦(面向对象的设计思想) 专注于某个模块在实施中的任务(这个模块在任务上的设计目的),即要关注这个模块或设计的最终目标,而...
  • IOC容器(一)

    热门讨论 2021-01-10 10:43:00
    IOC底层原理   控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理。使用IOC目的:降低耦合度 底层原理: xml解析、工厂模式、反射 我们先来看看关联两个类最直接的实现方式是怎样的: public class ...
  • 1.加锁,防止在refresh的时候出现启动或销毁容器的操作。 synchronized (this.startupShutdownMonitor) 2.准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符。 prepareRefresh(); ...
  • (1)如何查看容器内有多少个对象? 前面介绍了如何在容器内创建对象和设置对象之间的关系;但是,所有这些信息,都是我们自动脑补去想象的;如果一个项目足够大,对象很多,单靠去想就很吃力,那么我们如何知道...
  • ”还是使用了xml文件,我们可以使用基于注解的spring IOC容器来改造代码,实现零xml配置,代码可能如下: /* public class MyWebAppInitializer implements WebApplicationInitializer { @Override public void ...
  • 一个稍微更复杂但仍然相对简单和容易掌握PHP DI / IoC容器是Pimple.检查其文档更多的例子。 关于OP的代码和问题: >不要对你的容器使用静态类或单例(或者对于任何其他事情);检查痘痘。 >确定您是否希望您的...
  • 拿到容器中的值(单,多实例)2.1:编写Book实体类:2.2:新建spring容器2.3:输出bean:3.运用工厂(静态,实例工厂)3.1:编写静态,实例工厂3.2:spring注入工厂3.3:测试输出: 0.项目结构 1.新建spring项目 ...
  • Spring 源码之 IOC 容器前言IOC 只是一个 Map 集合IOC 三大核心接口IOC 初始化三大步骤定位加载注册总结 前言 在 Spring 框架中,大家耳熟能详的无非就是 IOC,DI,Spring MVC,AOP,这些是 Spring 中最基础的核心...
  • 源码分析_Spring专题_1 Spring IOC容器启动和加载原理 ...
  • Spring IoC容器 Spring IoC容器可以容纳我们所开发的各种Bean,并且可以从中获取各种注册在Spring IoC容器里的Bean。 Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口,其中...
  • Spring的两种IOC容器

    2021-03-15 10:49:00
    文章目录Spring IOC容器BeanFactory怎样创建BeanFactoryBeanFactory中的方法ApplicationContext几种ApplicationContext如何创建ApplicationContext Spring IOC容器 Spring IOC 容器是整个Spring框架的核心部分。容器...
  • springioc容器创建对象,注入依赖。然后将对象的使用权交出去。 如果想要理解控制反转,我们应该了解“谁控制谁?控制什么?为何是反转?(有反转就有正转),哪些方面反转了?” 1. 谁控制谁?控制什么:在传统程序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 191,043
精华内容 76,417
关键字:

IOC容器