Spring之一_IOC

笔记基于以下教程,包括:Spring之一_IOC、Spring之二_AOP
Spring视频教程:https://www.bilibili.com/video/BV1PJ411w7bX
特别声明:
以下叙述参杂个人理解,以方便自己理解为首要目的,完备的正确性其次,如有谬误烦请指出

一 IOC的概念

IOC就是通过配置文件创建对象【对应Bean标签】。

1.1配置文件是由谁加载和解析?

Spring中ApplicationContext接口的实现类,负责创建IOC核心容器(或者说,Context就是核心容器?)、读取配置文件创建对象,并将对象装入核心容器。所谓的核心容器就是一个Map,创建的对象就是作为Map中的元素,以全限定类名为key,以类对象为value。

1.2 配置文件怎么知道要创建哪些对象?

这个问法存在有问题,在逻辑上应该是,开发中某步需要使用类对象,这时程序员才会在XML配置文件中配置这个对象,也就是说XML配置文件和开发程序是同步进行的。

那什么时候会"需要使用类对象",也就是开发时为什么需要创建实例?无外乎三种:需要调用类对象的成员方法、需要访问类对象的成员变量、需要将类对象赋值给其他变量。

1.3 解析配置文件后调用什么方法创建类对象?

创建类对象有三种方式:直接调用类的构造函数(new方法)、通过其他类的普通方法创建(该方法返回类对象)、通过其他类的静态方法创建(该方法返回类对象)
在配置文件中可以指定用哪种方式来创建类对象【虽然都是对应bean标签,但bean标签使用的方法不同】
注意:这里其实不应该说有哪几种方法创建类,而是在获取一个类对象时我们可能碰到哪些情况。特别是第二、三种。如果是自定义类或者提供了new方法的第三方类,完全没必要绕圈子通过一个中间类去获取想要的类对象,关键就在于如果要获取的是第三方jar包中的类而且没有提供new方法,只提供了由某个第三方的工厂类创建,该怎么办?直接修改第三方jar包?第三方jar包都是.class字节码文件,而不是.java文件,没法直接修改源码。【会有类没有提供构造函数吗?不会,但是要考虑new方法能不能访问到,比如:静态内部类实现单例模式,这种情况就要通过外部类的方法获取内部类的实例,不能直接new】
第二、三种方法就是在解决这个问题,首先获取这个第三方工厂类的对象。然后调用该类的方法获取目标第三方类对象,根据方法是否为静态方法,分别对应第二和第三种方法。

继续扩展:第三方类对象,既没有向外暴露new方法,又没有通过某个方法返回该对象,我们能不能创建该类对象?不能。所以,这里与其说在讲有三种创建类对象的方式,不如说是,只有三种情况下才能获取到类对象。而且,具体是哪种,必须要指明。【其实通过反射获取私有构造方法,然后解除私有,也能创建对象,但不管怎么说,不改变私有是无法创建的】

第一种方式:调用类的构造函数创建对象

<bean id="XXX" class="XXX"></bean>

第二种方式:先创建第三方类对象,然后调用该对象的实例方法

<bean id="XXX" class="XXX"></bean>
<bean id="XXX" factory-bean="XXX" factory-method="XXX"></bean>

第三种方式:直接调用第三方类的类方法,不需要创建第三方类的对象

<bean id="XXX" class="XXX" factory-method="XXX"></bean>

bean标签中用class属性,是反射创建类对象,而反射创建类对象其实就是自动查找类的合适的构造函数。【无论中间怎么绕、封装了多少层,创建类对象最后必定是基于类的构造函数】

1.4 解析配置文件后什么时候去创建对象?

这与类对象本身配置有关,如果类对象是一个单例对象,那么在读取完配置文件后立马创建对象并装入核心容器。如果类对象是一个多例对象,那么真正调用这个对象时才会创建。以上的区别是显然是为了提高效率。【从这里看来,对于创建多例对象并没有将异常从运行时转移到编译时】
在配置文件中可以声明类是单例还是多例。再进一步地讲,对单例和多例对象的创建,spring自动选择不同接口的方法去实现。

1.5 如何获取IOC容器中的对象?

从IOC容器中获取对象的方法有两种,一种是自动获取,一种是手动获取。

自动获取:仅适用获取对象后赋值给另一个对象的成员变量,必须要求两个对象都要保存在IOC核心容器中,否则无法进行。如:XML配置中,<bean>标签创建类对象时,使用ref属性获取其他对象给成员变量赋值;注解配置中,使用@Autowired等注解。

手动获取:要先获取IOC核心容器,然后通过对象的id从中手动获取对象,获取到对象后的操作与IOC再无关系,甚至于Spring再无关系,可以当作一个普通的对象使用。【根据配置文件创建并获取ApplicationContext接口的实现类对象,返回的就是核心容器】【如果不使用注解,要想在代码中获取IOC容器的对象,貌似只有手动获取一条路】

1.6 创建的对象在哪些地方可以访问?

通过设置对象是单例和多例不仅决定了对象的创建时机,也决定了对象的作用范围。bean的作用范围怎么理解【对应bean标签中的scope属性】?如果是单例,那么代码中所有使用该类的对象的地方都共享这个对象,也就是配置的这个类对象作用范围广。而多例的话,那么代码中每个使用该类的对象的地方都有一个独立的对象。除了以上所讲的范围外,还可以作用在更广的范围:比如,分布式部署在多台物理机上的程序。
【多例对象时,spring怎么分辨哪个对象作用在哪个范围的?貌似配置文件中没有配置多个Bean啊?个人猜测:之前说了核心容器就是一个Map,可以理解为程序中创建对象时,spring先识别到该类对象在配置文件中定义的是单例还是多例,如果是多例的话,用程序中的变量名和新创建的Bean对象(比如地址什么的)作为一个键值对,如果是单例的话,也这样建一个键值对,不过程序中所有变量指向同一个对象。总之,一句话就是,spring自己负责分辨的,不用人配置。】

<bean id="XXX" class="XXX" scope="singleton/propotype/session/global-session"></bean>

1.7 创建对象后什么时候销毁对象呢?

这个还是与对象是单例还是多例有关。多例对象,由java的垃圾回收器决定销毁,当长时间不用又没被其他对象引用就会被销毁。单例对象,spring核心容器销毁时才会被一并销毁,如果不手动销毁容器的话,就是main程序结束的时候销毁。

销毁的时机是固定的,不需要也不能改变,但是在创建后、销毁前可以指定类对象执行某些方法。

<bean id="XXX" class="XXX" scope="singleton" init-method="XXX" destroy-method="XXX"></bean>

1.8 创建对象后怎么为成员变量赋值?

Java是面对对象语言,对象是对事物的抽象和封装。创建类是为了保存和调用数据,也就是对成员变量赋值和调用。在IOC中,为类的成员变量赋值被称为依赖注入DI

注意:
IOC(DI)并不能实现"为任意变量赋值",IOC操作中涉及的只有类、类的成员变量,IOC(DI)只能为类的成员变量赋值,不能为方法中的局部变量赋值。
可以将IOC的作用理解为只有一个:为类的成员变量赋值,创建和管理对象只是为了实现这个作用必须的操作。

1.8.1 成员变量的数据类型

成员变量的数据类型可分为三种:基本数据类型、容器类型、对象,不同的类型需要使用<property>标签不同的属性值来进行赋值。Java是面对对象语言,类的成员变量通常也是其他类的对象,所以最关注第三种情况。另外,要想将一个类对象赋值给成员变量,自然必须先创建该类对象,在IOC中被用于赋值的对象必须存在于IOC核心容器。

1.8.2 成员变量赋值的方法

根据Java中封装思想,对成员变量访问/赋值必须通过对应的gettert/setter方法。IOC中为成员变量赋值实际也是调用setter方法,因此实体类必须先为成员变量创建setter方法,才能进行赋值。

<property></property>

详情略,直接见下面的代码
1.8.3 补充:用构造函数赋值

严格来说1.3中的<bean>不是"创建对象",而是"指示创建对象"。因为创建对象必然是调用构造函数,如果构造函数不是默认的无参构造函数的话,必须要指定所需的参数,而1.3中没有指定参数,所以1.3中只能创建"具有无参构造函数"的类对象。
就实体类来说,有参构造函数往往也是为了给必须的成员变量赋初值。所以,有参构造函数也可以看作是为成员变量赋值的一种方式。

<constructor-arg></constructor-arg>
详情略,直接见下面的代码

二 基于XML的IOC

之后开发主要还是用注解方法,但是理解XML配置是理解注解的基础。XML方法相当于手动档,注解相当于自动档。

2.1 maven依赖

spring核心容器context
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.8.RELEASE</version>
</dependency>

2.2 XML配置文件

XML文件对IOC的配置就是对确定上面介绍的几个方面,包括:使用什么方法创建对象、什么时候真正创建对象(单例/多例)、对象的作用范围(作用域)、创建和销毁对象时附带执行什么操作(生命周期)、为类对象的成员变量赋值。

<?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">
    <!-- 配置spring的IOC,将accountService对象加入容器-->
    <!-- 注意:AccountServiceImpl类中只有带有一个参数的构造函数,只能通过下面的利用构造函数注入方式-->
    <!-- constructor-arg标签:基于构造函数注入
        name/type/index属性:分别基于变量名/变量类型/变量索引来匹配构造函数中的变量;
        value属性:变量值;spring框架可自动将配置文件中的字符串转为构造函数中的基本数据类型,但无法转为对象类型
        ref属性:变量引用;用于指定spring容器的其他bean对象(XML文件中配置的),实现为成员变量赋值对象类型的变量值;
     -->
    <!-- 注意:这里是xml文件和AccountServiceImpl类在一个目录下(是吗?),所以xml文件能够自动找到该类-->
    <bean id="accountService" class="AccountServiceImpl">
        <!-- 注入基本数据类型-->
        <constructor-arg name="age" value="12"></constructor-arg>
        <!-- 注入对象类型-->
        <constructor-arg name="birthday" ref="birthday"></constructor-arg>
        <!-- 注入容器类型-->
        <constructor-arg name="myList">
            <!-- list标签可以用于为list、array、set数据类型注入[单个值类型的容器]-->
            <list>
                <!-- 注意:不用加引号了-->
                <value>AAA</value>
                <value>BBB</value>
            </list>
        </constructor-arg>
        <constructor-arg name="myMap">
            <!-- map标签可用于为map、property数据类型注入[键值对类型的容器]-->
            <map>
                <!-- 注意:不用加引号了-->
                <entry key="firtst" value="AAA"></entry>
                <entry key="second" value="BBB"></entry>
            </map>
        </constructor-arg>
    </bean>
    
    <!-- 配置spring的IOC,将AccountServiceImpl2对象加入容器-->
    <!-- 注意:AccountServiceImpl2类中只有默认的无参构造函数但有set方法,只能通过下面的利用set函数注入方式-->
    <!-- property标签:基于set方法注入
        name属性:匹配类中的set方法;取setXXX()方法的XXX字段并将首字母小写得到的字符串即为name匹配的目标
        value属性:变量值;spring框架可自动将配置文件中的字符串转为构造函数中的基本数据类型,但无法转为对象类型
        ref属性:变量引用;用于指定spring容器的其他bean对象(XML文件中配置的),实现为成员变量赋值对象类型的变量值;
    -->
    <bean id="accountService2" class="AccountServiceImpl2">
        <property name="age" value="12"></property>
        <property name="birthday" ref="birthday"></property>
        <!-- 注入容器类型-->
        <property name="myList">
            <!-- list标签可以用于为list、array、set数据类型注入[单个值类型的容器]-->
            <list>
                <!-- 注意:不用加引号了-->
                <value>AAA</value>
                <value>BBB</value>
            </list>
        </property>
        <property name="myMap">
            <!-- map标签可用于为map、property数据类型注入[键值对类型的容器]-->
            <map>
                <!-- 注意:不用加引号了-->
                <entry key="firtst" value="AAA"></entry>
                <entry key="second" value="BBB"></entry>
            </map>
        </property>
    </bean>

    <!-- 使用Date类的无参构造函数创建Date对象-->
    <bean id="birthday" class="java.util.Date"></bean>
</beans>

2.3 代码实现

public class AccountServiceImpl implements IAccountService{
    private Integer  age;
    private Date birthday;
    private List<String> myList;
    private Map<String,String> myMap;

    public AccountServiceImpl(Integer age,Date birthday,List<String> myList,Map<String,String> myMap){
        this.age=age;
        this.birthday=birthday;
        this.myList=myList;
        this.myMap=myMap;
    }
}
public class AccountServiceImpl2 implements IAccountService {
    private Integer age;                    // 基本数据类型
    private Date birthday;                  // 对象类型
    private List<String> myList;            // 复杂(集合)数据类型,list
    private Map<String,String> myMap;       // 复杂(集合)数据类型,map

    public Integer getAge() {
        return age;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public List<String> getMyList() {
        return myList;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public Map<String, String> getMyMap() {
        return myMap;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
}
public class MyTest {
    public static void main(String[] args) {
        // 1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        // 2.根据对象id手动从IOC容器中获取对象
        IAccountService as1 = (IAccountService) ac.getBean("accountService");
        IAccountService as2 = (IAccountService) ac.getBean("accountService2",IAccountService.class);

        System.out.println(as1.toString());
        System.out.println(as2.toString());
    }
}

三 基于注解的IOC

注解配置与XML配置的关系,粗略地说:XML是集中配置,将所有需要的IOC配置集中在一个文件中;注解是分散配置,哪段代码要使用IOC就在该段代码上进行配置。

3.1 maven依赖

https://www.cnblogs.com/nwu-edu/p/9542074.html

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
    	<!-- 这里只要一个依赖就够了-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.8</version>
        </dependency>
    </dependencies>
</project>

3.2 配置包扫描

包扫描用于告诉spring哪些文件有使用注解(反过来说就是,指明要识别哪些文件中的注解)。也相当于指明了,分散的配置文件(Bean)都在哪里。包扫描的设置可以使用XML配置文件,也可以使用配置类。

3.2.1 基于XML配置包扫描

注意,这是使用注解配置方式时,即使仍然存在XML文件,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"
       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.xsd">
        
    <!-- 告知spring在创建容器时要扫描的包-->
    <context:component-scan base-package="service"></context:component-scan>

</beans>
3.3 基于配置类配置包扫描

用于取代上面的XML配置文件,实现全注解开发

@Configuration: 作用于类上,表明为配置类,代替XML配置文件;

@ComponentScan: 作用于配置类上,用于指定包扫描,代替XML配置文件中的<context:component-scan>;
	basePackages属性指定要扫描的包;
@Configuration
@ComponentScan(basePackages = "service")
public class springConfiguration {
}
  1. 注解要在包扫描之后才会被识别和加载,那么配置类上的@Configuration是如何被识别的呢?
    这个问题类似于XML文件自身是如何被识别和加载的,和加载XML配置文件类似,配置类的加载需要在AnnotationConfigApplicationContext对象中手动加载。该类也是ApplicationContext接口的实现类。
    AnnotationConfigApplicationContext中可指定多个类,表示多个同等级的配置文件。对于被指定的类,spring框架自动视为配置类、自动扫描和加载,不用配置@Configuration,其所在包也不需要被@ComponentScan 扫描。

  2. @Configuration通过设置包扫描可实现多级的配置类,AnnotationConfigApplicationContext中指定的配置类可以通过设置包扫描其他的配置类;除此之外,也可以在配置类上用@Import注解,@Import注解可以将其他类作为配置类。注意:其他类并不需要被@Configuration修饰。
    【@Configuration本身可以和@Controller这些一样被包扫描,可以发现,@Configuration修饰的类本身也是一个Bean,看@Configuration源码可知,这是一个组合注解,其中包含了@Component注解,那么问题来啦,直接@Component+@ComponentScan能不能实现@Configuration+@ComponenScan的效果呢?当然是可以的吧,再看源码,发现里面写着就是Component类的别名。所以重要的不是@Configuration这个注解,而是@ComponentScan这个注解】
    【再再另外,既然@Configuration的作用只是为自定义类创建Bean对象,那么特殊之处在哪,为什么要单独成立这个注解?好吧,其实和@Controller/@Service等相比,这个注解没有任何特殊之处,就像@Controller@Service本质上没有任何区别,只是出于情景和语义上的考虑,为@Component注解起了这两个别名,@Configuration其实也只是@Component的一个别名而已,表明被修饰的类起配置类的作用,具体情景就是:所谓的配置类,就是专门集中创建各种第三方类的对象,这些第三方类往往起到配置的作为,比如:数据源类】
    【总结一下:配置类主要用于配置包扫描,为第三方类创建对象,这些第三方类才是真正起配置作用,比如:数据源类】

主配置类Config1,其他配置类Config2

实现一:
ApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class,Config2.class);

public class Config1{
	...
}

public class Config2{
	...
}

实现二:
ApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class);

@ComponentScan(basePackages = "...")
public class Config1{
	...
}

@Configuration
public class Config2{
	...
}

实现三:
ApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class);

@Import(Config2.class)
public class Config1{
	...
}

public class Config2{
	...
}
  1. @PropertySource:用于指定配置application.properties文件位置;作用于@Configuration修饰的配置类上;value属性,用于指定properties文件名称和路径;由于部署时配置文件会放到类路径下,所以要使用classpath关键字。application.properties配置文件一般用于为类的成员变量赋基本数据类型的值,因此一般和@Value注解一起用。
  2. @Bean注解用于将方法的返回值中的对象加入IOC容器中,注意了:@Bean所修饰的方法的类,必须要先有对象加入到IOC容器中,不然@Bean不会起作用;换句话说,要想在方法上使用@Bean,先要保证类上有@Component/@Configuration/...

3.3 实现代码

XML中IOC、DI关注的问题有4个:创建对象、注入值、作用范围、生命周期。分别对应:bean标签、property标签、bean标签的scope属性、bean标签的destroy属性。
注解实现也还是解决上面4个问题。

3.3.1 创建核心容器
public class MyTest {
    public static void main(String[] args) {
        // 1.获取核心容器对象
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        // 2.手动获取IOC中的对象,getBean的参数是IOC容器中对象的id,默认值是对象类名(首字母小写)
        DataSource datasource1 = (DataSource) ac.getBean("datasource1");
        DataSource datasource2 = (DataSource) ac.getBean("datasource2");
    }
}
3.3.2 创建对象
@Bean: 
用于把当前方法的返回值作为bean对象存入spring的ioc容器中
在spring容器中,name属性作为key,bean对象作为value,name属性默认值为当前方法名;【注意:key是当前方法名!!!不是类名】
【注意:当方法被@Bean修饰时,类上必须被@Component等价的注解修饰,不然@Bean不会起作用】

@Component: 
作用:作用于类上,用于将当前类对象存入spring容器;默认创建单例对象。
在spring容器中,value属性作为key,类对象作为value;value属性默认值为当前类名且首字母改小写;
注意:注解中只有一个value属性,属性名value可省略;
@Controller@Service@Repository@Configuration四个注解和@Component完全一样,互相之间可以代替;

@Scope:
作用于@Bean@Component作用的方法或类上,用于设置创建对象的作用范围;

@PreDestroy@PostConstruct 作用在类的成员方法上;

根据类成员方法的返回值创建对象 @Bean

  1. 当成员方法是含参的,Spring框架会自动在ioc容器中查找合适的对象作为参数,查找方式同@Autowired。如以下代码中的第一处。注意:经试验,如果ioc有多个该类型bean对象的话,没有按照@Autowire那样继续用变量名匹配,直接报错了,这和教程中说的不同。【注意:@Bean修饰含参的成员方法,这点一直每注意】
  2. 以下是基于注解时创建第三方类对象的方法:自己额外定义一个类,类中成员方法调用第三方类的构造函数,创建并返回第三方类对象。
@Configuration
public class DataBaseConfig {

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

    @Bean(name="datasource")
    public DataSource createDatasource(){
        ComboPooledDataSource ds= new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("XXX");
            ds.setUser("root");
            ds.setPassword("123456");
            return ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

根据类的构造函数创建对象 @Component

上面 @Configuration 已经起到这个作用了
3.3.3 根据application.yml 配置文件为成员变量赋值(基本数据类型)

数据注入:【注意:@Autowired等还能作用在成员方法的参数上,一直没注意】

@Autowired:
作用:自动按照类型注入,在spring容器中找和目标变量类型相同的bean对象;可作用于成员变变量、成员方法上的参数;
如果ioc容器中有多个符合类型的bean对象,再将bean对象的变量名称(key)与目标变量匹配,如果有相同的则选中并注入,如果没有相同的则报错;
所谓的“类型相同”,可以是类型完全相同,也可是目标变量为接口、ioc容器中为接口实现类;

@Qualifier:
作用:先按照类型注入,再按照变量名注入;可作用于成员变量、成员方法的参数
value属性用于指定bean对象变量名
作用与类成员变量时,无法单独存在,必须和@Autowired一起写;
感觉很鸡肋,对比@Autowired就是多了个value属性

@Resource:
作用:按照变量名注入;name属性用于指定bean对象变量名;可作用于类成员变量、方法参数

@Value:用于为成员变量赋基本类型的值;值的来源往往是配置文件,只要写成变量形式,spring会自动在指定位置找XXX.properties配置文件,在配置文件中找对应变量名,用于赋值;

@Autowired过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// jdbcConfig.properties 配置文件

jdbc.driver= com.mysql.jdbc.Driver
jdbc.url= XXX
jdbc.username= root
jdbc.password= 123456
@Configuration
@ComponentScan(basePackages = "...")
@Import(JdbcConfiguration.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration{
}

public class JdbcConfiguration {
    // el表达式,指定application.properties配置文件中的变量名
    @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(){
        ComboPooledDataSource ds= new ComboPooledDataSource();
        try {
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

==================================================

四 杂项

4.1 为什么要使用IOC?

个人觉得,IOC最显著的作用在于将异常从运行时提前到编译时了。如何理解?其实只要看一下ioc干了一件什么事就清楚了:ioc就干了一件事,通过配置文件创建类对象。
那么问题来了,什么时候创建的呢?(以单例对象为例)配置文件一加载完就创建了。
那么问题又来了,spring项目中配置文件是什么时候加载的?项目一启动就加载配置文件了。那么问题就清楚了
IOC使得项目一启动时就先创建了整个项目中要用到的类对象。而不是像原来,项目启动后,某个方法被调用,方法中new了某个对象,这个时候才创建对象。
那么spring ioc是怎么知道项目中总共要创建哪些类对象呢?理一下就能发现,其实是程序员在写项目代码时,每碰到需要创建一个对象,就把这歌对象记录到统一的XML文件中。等项目写完了,XML中也就记录了所有需要创建的对象。
顺着理一下过程就是:程序员在开发过程中,每碰到要创建对象就把这个对象记录到XML文件中,(并且代码写成直接从一个容器中获取该对象),等项目写完了,XML中就保存了整个项目中所有需要创建的对象。之后,程序开始运行时,首先读取这个XML文件看需要创建哪些对象,直接先创建并存到一个容器中。等程序执行到要创建对象的地方,直接从容器中获取已经预先创建好的对象。
这么看,多例用延迟加载的原因就很清楚了,他读完配置文件后都无法知道要创建多少个相同的类对象,自然只能等调用的时候才创建

那么,ioc的另一个作用降低耦合是怎么回事呢?

https://blog.csdn.net/qq_36226453/article/details/82790375

首先得明白耦合是怎么回事,简单地说,一个类里面调用另一个类,如果被调用的类发生某种改变,所有用到它的地方都要修改,那就是耦合紧密
就拿ioc的场景来说,某个地方是通过new来创建类对象,如果该类构造函数的参数变了,比如说多加了一个参数,那所有用new方法创建该类对象的地方都要修改,这就是耦合紧密
那这个场景中怎么解决这个问题呢?也就是怎么降低耦合呢?
只要不直接使用类的new方法,而是表述为“调用该类的初始化方法创建一个类对象”就可以了,不管构造函数怎么变,都是“初始化方法”,都能被这个表述覆盖到。也就是说,只给类名,自动去搜索该类的init方法。
那这种表述怎么实现呢?这种方法其实就叫通过反射创建类对象,通过类的全权限定类名来创建。
这里如果类的全限定类名变了的话还是无法创建类队形,但是要注意:如果连类名都变了,或者类的路径变了,那就已经不是原来的那个类了,那自然无法创建对象了。类的全限定类名是类的唯一标识

个人认为,如果要讲清楚ioc,应该先讲将异常从运行时移到了加载时,然后讲降低耦合。
降低耦合可以看作是统一创建对象后的一个细节,也就是根据配置文件创建对象,具体是怎么创建的。
统一创建时,使用new、反射,都能创建,都能实现将异常从运行时移到加载时,但是反射创建还降低了耦合,这也是spring ioc实际使用的创建方式。
其实不论spring ioc实际通过new还是反射创建类对象,配置文件中肯定是要写全限定类名的,因为这才是类的唯一标识,不然都找不到要创建那个类对象。
当然,即使不使用spring ioc,咱们自己也能在每个使用new的地方改为通过反射创建,只不过写的更繁琐了。
从这个角度看,统一创建和反射创建(不是同一个维度)是相互促进的关系,反射创建使得统一创建的鲁棒性更强了,统一创建使得反射创建更加简洁了(集中了、且只用写一次)

====================================================
依赖注入DI【对应property标签】:
依赖注入DI是和控制反转IOC联合使用的,且基于IOC。
先看一下IOC干了什么事:开发者事先将程序中所有要创建的对象记录到XML配置文件中,Spring根据配置文件程序一启动就先创建这些对象。由于程序员已经打算好了由框架自己创建,所以在代码中只定义了类对象的引用变量,并没有实际创建类对象【创建类对象的控制权由程序员转到框架】。
仔细看一下上面这段就会发现一个问题:由框架负责创建类对象没问题,我们假设框架还是通过new方法来创建(实际是反射),框架怎么知道new里面要填哪些参数,也就是类初始化时成员变量的值框架怎么知道?如果不知道这些值的话,那即使有创建的权力,也没法真正创建类对象。依赖注入就是解决这个问题,用于指定类的成员变量的值【当然,如果能为初始化必须的成员变量赋值,那自然也能为其他的成员变量赋值了,也就是说依赖注入实际是控制类对象所有成员变量的值,而不仅仅是new所需要的】。也就是说,IOC使框架拿到类对象的创建权,DI使框架拿到为类对象中成员变量的赋值权。再顺着理一下,程序员写程序时碰到要创建类对象,所以记录到XML文件中了,既然要创建类对象,自然要为类对象的各个成员变量赋值,程序员也把这些值记录到XML文件中,之后程序启动时,框架自动根据XML记录创建对象并为对象的成员变量赋值。【IOC和DI其实算是一体的,如果类中没有无参构造函数,无论是new还是反射创建类对象,都必须为类的成员变量指定值。】

那怎么为那些在类创建时必须赋值的成员变量,以及其他成员变量赋值呢?
这其实取决于类中本身定义和类本身存在的为成员变量赋值的方法,一是:构造函数中指定【需要沟站函数中包括该成员变量】,二是:直接用点的方式访问类的成员变量并赋值【需要该属性为public】,三是:通过set方法为成员变量赋值【需要定义了该成员变量的set方法】

方法二不符合java数据封装的要求,不考虑。只有为一个类对象中的成员变量赋值就两种方法。【无论是手动还是框架都无外乎这两种】
而且,到底能使用哪种方式还要取决于类定义中对哪种方式可以支持:如果类中只有一个有参的构造函数,那么无论怎么样(框架或者是自己手动)都只能通过这个构造函数创建类对象。如果类中没有构造函数(也就是使用默认无参构造函数),那就不能基于构造函数来指定变量值了,只能用set方法【如果也没定义set方法的话,那就是无法通过配置文件注入】。如果类中既有有参构造函数,又有无参构造函数,那么两种方式都可以。
【只有一个有参构造函数,能通过set注入吗?我猜不能,确实不能】
可以基于构造函数,直接为构造函数中的成员变量指定值;
可以基于类的set方法,

=====================================================
依赖注入不是解决对上号的问题的,咦,那对上号的问题是怎么解决的,特别是多例对象?
从上面可以看到一个问题:框架创建了全部类对象,程序中定义了类对象的引用变量。怎么让类对象和引用变量对上号呢?怎么让引用变量指向正确的类对象呢?这个工作就叫依赖注入,让引用变量和类对象对上号,也即为引用变量注入值【依赖注入】。
那这个对上号的工作到底怎么实现的呢?或者说,根据什么来对号?

============================================
补充:
ApplicationContext是一个接口,有三个常用实现类:
ClassPathXmlApplicationContext:加载类路径下的XML配置文件,创建核心容器【常用】
FileSystemXmlApplicationContext:根据磁盘目录加载XML配置文件,创建核心容器
AnnotationConfigApplicationContext:根据注解创建核心容器

ApplicationContext和Beanfactory两个接口的关系:
ApplicationContext和Beanfactory两个接口都是用于创建核心容器,基于IOC创建类对象。而且ApplicationContext继承自Beanfactory,大概是Beanfactory只有延迟加载创建类对象的方法,ApplicationContext增加了立即加载创建类对象的方法,而且默认采用立即加载。使用ApplicationContext接口时,如果在配置中指定了对象是单例还是多例,spring也会自动对单例用立即加载,多例用延迟加载。【不指定就是默认延迟加载】
【注意:不管是单例还是多例,实际上都可以用立即加载和延迟加载,只不过出于效率考虑,让单例用立即加载,多例用延迟加载】

IDEA:怎么看依赖包的相互依赖关系?怎么查看类的继承关系?怎么看接口的实现类?
1.maven——>show dependencies
2.选中类——>diagrams——>show diagrams
3.在上一步的基础上,——> show implementations

补充二:
上面那个main程序很有意思,用了多态。
注意,一是:ac.getBean返回的是Object类型对象,导致只能使用Object类型的方法,所以要进行强制类型转换。二是:这里是用接口的类型进行强制转换,而不是实现类的类型。不知道为啥,貌似都是这么写的。另外,由于是转为接口的类型(相当于父类类型),转换后还是只能使用接口中已经定义了的方法,无法使用实现类中特有的方法。比如这里:as2 无法调用getXXX方法。

对@Bean注解测试如下:

@Bean注解不一定要和@Configuration搭配,只要和@Component等价的注解都行;类上必须要加@Component等价的注解;

public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        // @Component加入的Bean对象,默认key为类名(首字母小写)
        Test2 test2 = (Test2) ac.getBean("test2");
        System.out.println(test2.test.val);
        // @Bean加入的Bean对象,默认key为方法名
        Test test = (Test) ac.getBean("getTest");
        System.out.println(test.val);
    }

}

@Configuration
@ComponentScan(basePackages = {"com.example.demo"})
public class MyConfig {
}

public class Test {
    public int val = 1;
}

@Component // 这里必须加一个注解,不然 @Bean 不会生效
public class Test1 {
    @Bean
    public Test getTest(){
        return new Test();
    }
}

@Component
public class Test2 {
    @Autowired
    Test test;
}

输出:
1
1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值