• 2. spring 应用之IOC


     本文是作者原创,版权归作者所有.若要转载,请注明出处

    我们知道Spring Framework 最重要的功能就是IoC (Inversion of Control ),也叫DI(dependency injection),这不是我说的,是官网这么说的,截图如下

    spring官网说IoC,也叫DI,是同一个意思.我会在文章最后写一下自己的个人理解

    首先复习一下spring的IOC相关应用

    1.将对象交给spring管理

    首先是测试类

    public class UserService  {
        
        private String userName;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
    }

    然后是spring的配置文件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"
        default-autowire="no">
    
        
        <bean class="com.lusaisai.service.UserService" id="userService" >
            <!--此处name的值与set方法要一致-->
            <property name="userName" value="lusai"></property>
        </bean>
        
    </beans>

    最后是测试

    public static void main(String[] args) {
    
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");

          System.out.println(userService.getUserName());
     }

     可以看到注入成功

     2.依赖注入

    首先是UserService依赖的对象UserDao

    public interface UserDao {
        void test();
    }

    然后是它的实现类

    public class UserDaoImpl implements UserDao {
        @Override
        public void test() {
            System.out.println("UserDaoImpl");
        }
    }

    在UserService增加依赖的对象UserDao和set方法,注意,这里需要UserService的空构造(不写的时候编译器会帮我们自动构建一个无参构造)

    public class UserService  {
    
        private String userName;
    
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void serviceTest(){
            userDao.test();
        }
    
    }

    然后是xml配置

    <!--必须有空构造-->
        <bean class="com.lusaisai.service.UserService" id="userService" >
            <property name="userName" value="lusai"></property>
            <!--这里的ref指定下方bean标签配置对象的id-->
            <!--此处name的值与UserService的属性userDao的set方法名要一致-->
            <property name="userDao" ref="userDao"></property>
        </bean>
    
        <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>

    然后是测试

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");
            System.out.println(userService.getUserName());
            userService.serviceTest();

    看下结果

    好了,注入成功

    3.我们发现一个问题,我们已经在代码里写了UserDao,还有它的set和get方法,为啥还要在xml里用配置告诉spring应该如何注入呢,其实spring提供了自动装配的功能,如下图

     我们可以看到default-autowire有5个可供选择

     单独的bean标签里也有5个供选择,这里截个官网的图

    下面我们来测试一下,首先是byName,我们把手动装配注释掉,添加上default-autowire="byName"  如下

    <?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"
    default-autowire="byName" >

    <!--必须有空构造-->
    <bean class="com.lusaisai.service.UserService" id="userService" >
    <property name="userName" value="lusai"></property>
    <!--这里的ref指定下方bean标签配置对象的id-->
    <!--此处name的值与UserService的属性userDao的set方法名要一致-->
    <!--<property name="userDao" ref="userDao"></property>-->
    </bean>

    <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>

    </beans>
    UserService中的代码没有改动,测试
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");
            System.out.println(userService.getUserName());
            UserDao userDao = userService.getUserDao();
            System.out.println(userDao);

    看下结果

     好的,成功了,我们改一下set方法的名字

    public void setUserDao1(UserDao userDao) {
            this.userDao = userDao;
        }

    重新运行一次

     可以看到,userDao没有注入进来, 我们把方法名复原再次修改属性名试一下

    private String userName;
    
        private UserDao userDao2;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao2;
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao2 = userDao;
        }

    重新运行

     可以看到,注入成功,因此byName的set方法名要和依赖对象的bean标签的id相同

    接下来看下byType,我们将default-autowire="byType" 再加上一个同一个类不同id的bean标签

    <?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"
        default-autowire="byType" >
    
        <!--必须有空构造-->
        <bean class="com.lusaisai.service.UserService" id="userService" >
            <property name="userName" value="lusai"></property>
            <!--这里的ref指定下方bean标签配置对象的id-->
            <!--此处name的值与UserService的属性userDao的set方法名要一致-->
            <!--<property name="userDao" ref="userDao"></property>-->
        </bean>
    
        <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>
    
        <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao2" ></bean>
    
    </beans>

    userService代码

    private String userName;
    
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }

    再运行一下结果

    十月 17, 2019 11:53:15 下午 org.springframework.context.support.AbstractApplicationContext refresh
    警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
    Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1499)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1379)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
        at com.lusaisai.test.Test.main(Test.java:16)
    Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1484)
        ... 13 more

    报错信息,显示只要一个,但找到2个,我们注释掉一个

    <!--<bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>-->

    重新运行,看下结果

     注入成功了,说明byType的自动装配方式中如果存在多个相同类型不同id的bean标签,则抛出异常,如果没有匹配的bean,则不自动装配

    最后看下构造方法注入

    首先

    default-autowire="constructor"

    然后在UserService中注释掉set方法,添加userDao的构造方法

    private String userName;
    
        private UserDao userDao;
    
        public UserService(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }

    ,运行,看结果

     注入成功了

    我们把userDao的构造方法注释了,再运行看下结果

     没注入成功,但也不报错

    好,到此为止,我们的xml配置将对象交给spring管理就讲完了总结一下

    可以看出,no和Default是不自动装配的,byName和byType是通过set方法自动装配的,同时要确保有空构造存在,我猜底层是用newInstance()实现的具体源码后面再看

    byName是根据set方法名自动装配的,set方法名要和bean标签的id相对应,否则,注入不成功,但不会报错

    byType是根据类型装配的,如果存在多个该属性类型的bean标签,则抛出异常,如果没有匹配的bean,则不自动装配

    constructor是根据构造方法来装配的,如果容器中没有一个构造函数参数类型的bean,则不自动装配

    可以看到这种xml格式其实是非常麻烦的,实际项目中我们一般通过注解来将对象交给spring管理

    只需要将spring的配置文件按以下配置即可,加入包扫描,就能将包下的所有对象通过注解方式来注入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context-3.0.xsd"
          >
    
        <context:component-scan base-package="com.lusaisai"></context:component-scan>
    
    </beans>

    我们来看例子,注意上面的spring配置文件中一个bean标签都没有了

    这里加入了@Component注解,也可以用@Repository,@Service,@Controller注解,四个注解都可以将对象注入到spring容器进行管理,效果是一样的

    @Component
    public class UserDaoImpl implements UserDao {
        @Override
        public void test() {
            System.out.println("UserDaoImpl");
        }
    }

    这里在依赖的对象上加入了@autowire注解,我猜底层是通过filed.set调用的,所以不需要set方法,这个以后看源码了再讲

    @Component
    public class UserService  {
    
        private String userName="lusai";
    
        @Autowired
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
    }

    当然也可以将注解加在set方法上,如下

    @Component
    public class UserService {

    private String userName="lusai";


    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }

    public String getUserName() {
    return userName;
    }

    public void setUserName(String userName1) {
    this.userName = userName1;
    }

    public UserDao getUserDao() {
    return userDao;
    }

    测试一下

    public static void main(String[] args) {
    
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");
            System.out.println(userService.getUserName());
            UserDao userDao = userService.getUserDao();
            System.out.println(userDao);
    
        }

    看下结果

     可以看到,注入成功了

    如果我们再给UserDao接口的另一个实现类也交给spring管理,会不会报错呢?如下

    @Component
    public class UserDaoImpl2 implements UserDao{
        @Override
        public void test() {
            System.out.println("UserDaoImpl2");
        }
    }

    运行一下看结果

    Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:668)
        ... 15 more

    果然报错了,和上面的报错信息差不多,说明同一个接口,最好不要写多个实现类,那我非要写多个实现类怎么办呢

    我们可以用@Resource,并指定它的name属性的别名为类名的首字母小写,如下

    @Component
    public class UserService  {
    
        private String userName="lusai";
    
    
        @Resource(name = "userDaoImpl2")
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
    }

    运行一下,看看结果

     成功了

    总结:先来张网上的图

     @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。两者如果都写在字段上,那么就不需要再写setter方法

    @Autowired默认按类型装配(这个注解是属业spring的),需要导入包org.springframework.beans.factory.annotation.Autowired 默认按照类型来进行装配

    @Resource(这个注解属于java的),需要导入包javax.annotation.Resource。默认按照名称进行装配,名称可以通过name属性进行指定

    最后,现在流行用javaconfig而不是xml来配置spring,这里贴一下javaconfig的代码

    第一种:相当于写bean标签,如下,这里使用@bean注解 将UserService 和UserDaoImpl对象交给spring管理

    @Configuration
    public class SpringConfig {
    
        @Bean
        public UserService userService(){
            return new UserService();
        }
    
        @Bean
        public UserDao userDao(){
            return new UserDaoImpl();
        }
    
    }

    这里注入依赖

    public class UserService  {
    
        private String userName="lusai";
    
        @Autowired
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
    }

    这是测试demo

    public static void main(String[] args) {
    
            AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
            UserService userService = (UserService) annotationContext.getBean("userService");
            System.out.println(userService.getUserName());
            UserDao userDao = userService.getUserDao();
            System.out.println(userDao);
    
        }

    看下结果

    第二种:相当于扫描包,把上面的两个@bean注解注释了,开启扫描

    @Configuration
    @ComponentScan("com.lusaisai")
    public class SpringConfig {
    
        /*@Bean
        public UserService userService(){
            return new UserService();
        }
    
        @Bean
        public UserDao userDao(){
            return new UserDaoImpl();
        }*/
    
    }

    在UserService 和UserDaoImpl对象上加上@Component,将他们交给spring管理,如下

    @Component
    public class UserDaoImpl implements UserDao {
        @Override
        public void test() {
            System.out.println("UserDaoImpl");
        }
    }
    @Component
    public class UserService  {
    
        private String userName="lusai";
    
        @Autowired
        private UserDao userDao;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName1) {
            this.userName = userName1;
        }
    
        public UserDao getUserDao() {
            return userDao;
        }
    
    }

    重新测试

     可以看到,注入成功了.

    我们再提出一个问题 @Autowired与@Resource这两个注解 和xml配置中的 default-autowire="byName" 和byType是不是用的相同的原理的呢,这里就后面看源码的时候再来解释吧

    最后说一下,我自己对IOC和DI的理解:以上文中的UserService和UserDao为例:

    1.UserService和UserDao这两个对象原来我们是自己new出来的,现在交给spring管理,这就叫控制反转,即IOC

    2.UserService中有个属性UserDao,那我们可以说:UserService要使用UserDao,那么UserService就对UserDao产生了依赖,也就是UserService依赖UserDao,而UserDao存在于spring容器中,spring将容器中的UserDao对象交给UserService使用,这就叫依赖注入,即DI,

    3.DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结道:控制的什么被反转了?就是获得依赖对象的方式反转了(获得依赖对象的方式从我们自己new改成交给spring)

  • 相关阅读:
    Tomcat架构解析(五)-----Tomcat的类加载机制
    session与cookie
    freemarker常用标签解释遍历
    freemarker常用标签解释三
    freemarker常用标签解释二
    freemarker常用标签解释
    禁止浏览器自动填充
    使用cookie实现自动登录
    长连接和短连接
    filter防止xxs攻击
  • 原文地址:https://www.cnblogs.com/lusaisai/p/11679088.html
Copyright © 2020-2023  润新知