Spring的IOC

一、入门案例

1.导入依赖:


   org.springframework
   spring-context
   5.1.5.RELEASE

2.编写beans.xml




    
    
    
    
    

3.使用

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        //ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");//更常用
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        //com.hcx.service.impl.UserSerivceImpl@3e6fa38a
        System.out.println(userService);
        //com.hcx.dao.impl.UserDaoImpl@66a3ffec
        System.out.println(userDao);
//        UserService userService = (UserService) BeanFactory.getBean("userService");
//        userService.saveUser();
    }
}

二、重要类详解

1.BeanFactory

在构建核心容器时,创建对象采取的策略是延迟加载。即根据id获取对象时才创建对象。
适用场景:多例对象

public class UserController {
    public static void main(String[] args) {
        Resource resource = new ClassPathResource("beans.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        UserService userService = (UserService) beanFactory.getBean("userService");
        System.out.println(userService);
    }
}
Spring的IOC_第1张图片
image.png

2.ApplicationContext

在构建核心容器时,创建对象采取的是立即加载的方式。即一读取完配置文件就马上创建对象。
适用场景:单例对象

public class UserController {
    public static void main(String[] args) {
        //获取核心容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        //com.hcx.service.impl.UserSerivceImpl@3e6fa38a
        System.out.println(userService);
        //com.hcx.dao.impl.UserDaoImpl@66a3ffec
        System.out.println(userDao);
    }
}
Spring的IOC_第2张图片
image.png
Spring的IOC_第3张图片
image.png

ApplicationContext的三个常用实现类:
①ClassPathXmlApplicationContext
可以加载类路径下的配置文件,要求配置文件必须在类路径下,否则无法加载。

②FileSystemXmlApplicationContext
可以加载磁盘任意路径下的配置文件(须有访问权限)

③AnnotationConfigApplicationContext
用于读取注解创建的容器

三、Bean详解

1.创建Bean对象的三种方式

①方式一:采用默认构造函数

spring配置文件中使用bean标签,仅配置id和class属性(没有其他属性和标签),此时采用默认构造函数创建对象,如果类中没有默认构造函数,则对象无法创建。

    
    
    

②方式二:使用普通工厂中的方法创建对象
即使用某个类中的方法创建对象,并存入spring容器
当使用一些外部类时,如引入的jar中的类,都是字节码文件。

InstanceFactory:

/**
 * 模拟工厂类(该类可能存在于jar包中,无法修改代码提供默认构造函数)
 */
public class InstanceFactory {
    public UserService getUserService(){
        return new UserSerivceImpl();
    }
}

此时要获取的是InstanceFactory 类中的getUserService返回值

    
    
    
    

③方式三:使用工厂中的静态方法创建对象
使用某个类中的静态方法,并存入spring容器

StaticFactory:

public class StaticFactory {
    public static UserService getUserService(){
        return new UserSerivceImpl();
    }
}

xml配置:


④注解方式:
作用和xml配置的标签功能一样
@Component:把当前类对象存入spring容器中
属性value用于指定bean的id,不写时默认为类名首字母小写
@Controller:一般用于表现层
@Service:一般用于业务层
@Repository:一般用于持久层

使用注解的方式,需要加入context命名空间,并且要配置告诉spring使用注解的方式,需要spring去扫描对应的包,配置如下:




    
    


Spring的IOC_第4张图片
image.png

2.bean对象的作用范围

①xml配置:
默认情况spring创建的bean都是单例的。
bean的作用范围通过bean标签的scope属性可以指定:
取值:

  • singleton:单例(默认值)
  • prototype:多例
  • request:作用于web应用的请求范围
  • session:作用于web应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,等同于session

②注解配置:
@Scope:指定bean的作用范围
属性value:指定范围的取值,常用取值:singletonprototype

3.bean对象的生命周期

①单例对象
生命周期跟容器的相同,让其创建时对象创建,只要容器还在,对象一直存活,容器销毁,对象死亡。
通过init-methoddestory-method属性指定
UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    public UserSerivceImpl() {
        System.out.println("对象创建了");
    }
    public void saveUser() {
        System.out.println("service实现类被调用了");
//        userDao.saveUser();
    }
    public void init() {
        System.out.println("对象初始化");
    }
    public void destory() {
        System.out.println("对象销毁");
    }
}

xml:

    

调用:

public class UserController {
    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
//        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        userService.saveUser();
        //手动关闭容器
        applicationContext.close();
    }
}

结果:

对象创建了
对象初始化
service实现类被调用了
对象销毁

②多例对象
出生:使用对象时spring框架创建
存活:对象只要在使用则一直存活
死亡:当对象长时间不用且没有其他对象引用时,由垃圾回收器回收

即使手动关闭容器,对象也不会销毁

    

打印结果:

对象创建了
对象初始化
service实现类被调用了

③使用注解指定生命周期
作用与bean标签中使用init-methoddestroy-method作用相同
PostConstruct:用于指定初始化方法
PreDestroy:用于指定销毁方法

UserSerivceImpl :

@Service("userService")
public class UserSerivceImpl implements UserService {
    @Resource(name = "userDao1")
    private UserDao userDao;
    @PostConstruct
    public void init(){
        System.out.println("初始化方法");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("销毁方法");
    }
    public void saveUser() {
        userDao.saveUser();
    }
}

调用:

public class UserController {
    @Autowired
    private UserService userService;
    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
        //手动关闭才会调用销毁方法
        applicationContext.close();
    }
}

打印结果:

初始化方法
保存用户1
销毁方法

四、Spring的依赖注入

依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现
程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。
ioc 解耦只是降低他们的依赖关系,但不会消除。
例如:我们的业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。
简单的说,就是框架把持久层对象传入业务层,而不用我们自己去获取。

依赖关系的管理:交由spring来维护
当前类需要用到其他类对象时,由spring为我们提供,我们只需要在配置文件中说明。

能注入的数据类型:

  • 基本类型和String
  • 其他bean类型(在配置文件或者注解配置过的bean)
  • 复杂类型(集合类型)

注入的方式:

  • 使用构造器
  • 使用set方法
  • 使用注解

注意:经常变化的一些数据不适用于注入的方式

1.构造函数注入

在bean标签中使用标签constructor-arg,属性:

  • type:指定要注入的数据的数据类型,该数据类型是构造函数中某个或某些参数的数据类型
  • index:指定给构造函数中指定索引位置的参数赋值。(索引从0开始)
  • name:指定给构造函数中指定名称的参数赋值
  • value:赋的值是基本数据类型和 String 类型
  • ref:赋的值是其他 bean 类型(必须是在配置文件中配置过的 bean)

注意:typeindexname用于指定给构造函数中的哪个参数赋值

UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    private String name;
    private Integer age;
    private Date birthday;
    public UserSerivceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了:"+name+","+age+","+birthday);
    }
}

beans.xml

    
        
        
        
    

    
    

使用:

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
    }
}

结果:

service实现类被调用了:极多人小红,24,Wed Dec 11 20:09:09 CST 2019

此种方式的优缺点:
优点:在获取bean对象时,注入数据是必须的操作,否则无法创建对象(如果同时提供无参构造器则没问题)
缺点:改变了bean对象的实例化方式,使得在创建对象时,即使不使用这些数据也必须提供。

2.set 方法注入

通过配置文件给 bean 中的属性传值:使用 set 方法的方式,
property标签:

  • name:找的是类中 set 方法后面的部分(并不是属性的名称)
  • ref:给属性赋值是其他 bean 类型的
  • value:给属性赋值是基本数据类型和 string 类型的

UserSerivceImpl:

 public class UserSerivceImpl implements UserService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了:"+name+","+age+","+birthday);
    }
}

beans.xml:

    
    
        
        
        
    

    
    

优点:创建对象时没有明确的限制,可以直接使用默认构造函数
缺点:如果某个成员必须有值,则获取对象时有可能set方法没有执行。

复杂类型注入

用于给List结构注入的标签包括:listarrayset
用于给Map结构集合注入的标签包括:mapproperties
即结构相同的数据类型,标签都通用。

UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    private String[] strArrs;
    private List strList;
    private Set strSet;
    private Map strMap;
    private Properties properties;
    public void setStrArrs(String[] strArrs) {
        this.strArrs = strArrs;
    }
    public void setStrList(List strList) {
        this.strList = strList;
    }
    public void setStrSet(Set strSet) {
        this.strSet = strSet;
    }
    public void setStrMap(Map strMap) {
        this.strMap = strMap;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了");
        System.out.println(Arrays.toString(strArrs));
        System.out.println(strList);
        System.out.println(strSet);
        System.out.println(strMap);
        System.out.println(properties);
    }
}

beans.xml:


        
            
                arr1
                arr2
                arr3
            
        

        
            
                list1
                list2
                list3
            
        

        
            
                set1
                set2
                set3
            
        

        
            
                
                
                    map2
                
            
        

        
            
                properties1
                properties2
            
        
    

调用:

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
    }
}

打印结果:


Spring的IOC_第5张图片
image.png

3.注解注入

@Autowired:按照类型注入
只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就能注入成功。
如果容器中没有bean的类型和要注入的变量匹配,报错。
如果容器中有多个类型匹配时,报错。

Spring的IOC_第6张图片
image.png

注意:当有多个类型都匹配上时,会再次按照名称匹配,如果相同了,也能匹配上。
即注入时的类的名称private UserDao userDao2;userDao2与声明时的注解@Repository("userDao2")匹配上了。

@Qualifier:按照类型注入的基础之上再按照名称注入。
(给方法参数注入时可以单独使用,给类成员注入时则不能单独使用,必须配合@Autowired注解)
value属性:用于指定注入bean的id

@Service("userService")
public class UserSerivceImpl implements UserService {
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;
    public void saveUser() {
        userDao.saveUser();
    }
}

@Resource:按照bean的id注入,可以单独使用
属性name:用于指定bean的id

@Service("userService")
public class UserSerivceImpl implements UserService {
//    @Autowired
//    @Qualifier("userDao1")
    @Resource(name = "userDao1")
    private UserDao userDao;
    public void saveUser() {
        userDao.saveUser();
    }
}

总结:以上三个注解只能注入bean类型的数据。

@Value:用于注入基本类型和String类型数据
属性value:指定数据的值,可以使用SpEL(spring的el表达式)
SpEL写法:${表达式}

位置:变量、方法

五、使用纯注解的方式配置(没有beans.xml配置文件)

beans.xml:




    
    

    
        
    

    
        
        
        
        
    

@Configuration:指定当前类是一个配置类
当配置类作为AnnotationConfigApplicationContext对象创建的参数传递时,可以省略该注解。
@ComponentScan(basePackage="com.hcx"):指定spring在创建容器时需要扫描的包
属性value和basePackages:作用相同,都是用于指定创建容器时需要扫描的包。
使用此注解就相当于在xml中配置了:
@Bean用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性name:指定bean的id,默认值为当前方法名
@import:导入其他配置类
属性value:用于指定其他配置类的字节码(使用了该注解的类就是主配置类)
@PropertySource:用于指定配置文件的路径
属性value:指定文件名称和路径(classpath:类路径)

Spring的IOC_第7张图片
image.png

SpringConfiguration:

@Configuration
@ComponentScan("com.hcx")
public class SpringConfiguration {

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
            comboPooledDataSource.setUser("root");
            comboPooledDataSource.setPassword("root");
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

public class UserServiceTest {
    @Test
    public void testSelectAll(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        List userList = userService.findAll();
        for(User user : userList){
            System.out.println(user);
        }
    }
}

注意:使用注解配置,方法如果有参数,spring会寻找有没有对应的bean对象,查找方式与Autowired一样。

@import注解

当有多个配置类时,可以通过@import注解导入其他的配置类
主配置类:

@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}

Jdbc配置类:

public class JdbcConfig {

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
            comboPooledDataSource.setUser("root");
            comboPooledDataSource.setPassword("root");
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

@PropertySource注解

使用读取配置文件的方式读取数据库配置信息:
将数据库的配置抽取到单独的properties文件中:
jdbcConfig.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springdemo
jdbc.useranme=root
jdbc.password=root

JdbcConfig:

public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl(url);
            comboPooledDataSource.setUser(username);
            comboPooledDataSource.setPassword(password);
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

在配置类中指定读取该配置:
SpringConfiguration:

@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}

在方法参数上使用@Qualifier注解

当有多个数据源时,可以在创建对象时通过@Qualifier注解参数指定具体使用哪一个:

Spring的IOC_第8张图片
image.png

你可能感兴趣的:(Spring的IOC)