从零开始学Spring-Spring启示录


Spring启示录

1.OCP开闭原则

OCP是软件七大开发原则中最基本的原则

  • 它指的是对扩展开放
  • 而对于修改是关闭的

OCP开闭原则的核心简单来说就是在扩展系统功能的时候,没有修改以前写的代码,那么就是符合OCP原则的,否则就是不符合的。这是因为在进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行测试,这对于整个系统而言都是非常麻烦的

2.依赖倒置原则DIP

原始的开发方式:这种方式违背了依赖倒置原则,这种情况下,上层的应用直接依赖于下层应用的实现,只要下层应用改动,上层应用就收到牵连。

依赖倒置原则:核心就是倡导接口编程而不是面向具体编程,所谓的面向接口编程,就是你在代码中根本看不到具体的实现类。

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖,其依赖关系是通过接口或者抽象类产生的
  • 接口或者抽象类不依赖于实现类
  • 实现类依赖接口或者抽象类

3.控制反转IOC(Inversion of Control)

控制反转IOC:控制能够有效解决依赖倒置原则

  • 不在程序中采用硬编码的方式来new对象了
  • 不在程序中采用硬编码的方式来维护对象了,对象的维护权也交出去了

是一种核心的编程思路,是一种新型的设计模式.

在传统的面向对象编程的方式中,获取对象的方式通常使用new关键字主动创建一个对象,Spring中的IOC方式对象的生命周期由框架提供的IOC容器进行管理,直接从IoC容器中获取一个对象控制权将应用程序交给了IoC容器

IoC理论上是借助于第三方实现具有依赖关系对象之间的解耦合,也就是把各个对象类封装之后,通过IoC容器来关联这些对象类,这样的话对象与对象之间就通过IoC容器进行联系,而对象与对象之间就没有什么直接联系了。

应用程序在没有引入IoC容器之前,对象A依赖于对象B,那么A对象在实例化或者运行到某一点的时候,必须主动创建对象B得或者使用创建好的对象B,其中无论是创建还是使用已经创建好的对象B,控制权都是应用程序自身,如果应用程序引入了IoC容器之后,对象A和对象B之间失去了直接的联系,那么当对象A进行实例化和运行的时候,如果需要对象B,IoC就会自动创建一个对象B注入到对象A所需要的地方。

由此,对象A获得依赖对象B的过程,由自动行为变为被动行为,把创建对象的任务交给了IoC容器进行了处理,这就是控制反转

4.依赖注入

DIDependency Inject的缩写,依赖注入,所谓依赖注入,就是由IoC容器在运行期间动态地将某种依赖关系注入到对象之中。例如,将对象B注入赋值给对象A的成员变量。

依赖:A对象和B对象的关系

注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。

事实上,依赖注入DI和控制反转IoC是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同,依赖注入是从应用程序的角度描述,即应用程序依赖容器创建并注入它所需要的外部资源

而控制反转是从容器的角度描述,即容器控制应用程序,由容器反向地向应用程序注入应用程序所需要的外部资源。这里所说的外部对象就是外部实例对象,也可以是外部文件对象。

你也可以理解为DI其实就是IoC的具体实现。

  • 可维护性比较好,便于单元测试,调试程序和诊断故障
  • 每个开发团队的成员只需要关注自己需要实现的业务逻辑,完全不需要关心别人的工作。
  • 可复用性好,我们可用把具有普遍性的常用组件独立出来。
  • 生成对象的方式转为外置方式。只要修改配置文件即可,完全具有热插拔的特性。

两种方式

  • set方式注入:执行set方法给属性复制
  • 构造方法注入:执行构造方法给属性赋值

5. Spring的八大模块

Spring是一个轻量级的控制反转IoC和面向切面AOP的容器框架

Spring的最初的出现是为了解决EJB臃肿的设计以及难以测试的问题

让程序员值需要关注核心业务的实现,尽可能的不再关注非业务逻辑代码

两大核心模块:AOP+IoC,而AOP是依赖于IoC执行的。

  • Spring-core:这是Spring框架最基础的部分,它提供了依赖注入DI特征来实现容器对Bean的管理,核心容器的主要组件就是BeanFactory,是任何Spring应用的核心,它将IoC将应用配置和依赖从实际的应用代码中分离出来了。
  • Spring-Context:如果说BeanFactory使得Spring称为容器的话,那么上下文模块就是Spring成为框架的原因。因为它扩展了BeanFactory,增加了对国际化消息,事件传播、验证的支持。
  • SpringAOP:Spring在它的AOP模块中提供了面向切面编程的丰富支持,SpringAOP模块为基于Spring的应用程序提供了事务管理服务,提供使用SpringAOP,不用依赖于组件,就可以将声明性事务集成到应用程序中。可以自定义拦截器、切点、日志等操作
  • SpringDAO:提供了JDBC的抽象层和异常层次结构,消除了繁琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC
  • SpringORM:Spring提供了ORM模块,Spring并不试图实现自己的ORM方案,而是为ORM框架提供了集成方案
  • SpringWebMVC:Spirng为构建web应用提供了一个功能完全的MVC框架,使用IoC对控制逻辑和业务对象提供了完全的分离
  • SpringWebFlux模块:SpringFramework支持反映流ReactiveStream背压

6. Spring的特点

  • 轻量级:从大小与开销两方面而言Spring都是一个轻量级的框架,完整的Spring框架的开销是微不足道的,Spring是非侵入式的,Spring应用中的对象不依赖于Spring的特定类。

什么是侵入式设计

假设我开发了一个框架,有一个类,类中有一个方法,该方法上有一个参数HttpServlet,而你这个参数是需要依赖于别的API的。

  • 控制反转:Spring通过一种称作控制反转的技术IoC的技术促进了松耦合,当应用了IoC,一个对象依赖的其他对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象,你可以认为IoCJNDI是相反的,JNDI提供了一种规范使得Tomcat等web容器能够在容器中查找相关对象。而IOC不是从容器中查找依赖,而是容器在对象初始化的时候不等对象请求就主动将这个依赖传递给它
  • 面向切面:Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务,进行内聚性的开发。应用对象只实现它们应该做的。它们并不负责其他系统级的关注点。
  • 容器:Spring包含并且管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建,基于一个可配置的原型prototype,你的bean可以创建一个单独的实例或者每次需要的时候都生成一个新的实例,以及它们都是如何关联的。
  • 框架:Spring可以将简单的组件配置、组合为复杂的应用,在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里,Spring也提供了很多基础功能,将应用逻辑的开发留给了程序员。

7. Spring程序开发

7.1 配置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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--idea提供的配置文件-->
    <!--这个文件名不一定叫做spring.config,最好放在类路径当中方便后期的移植-->
    <!--放到了classpath下-->
    <!--配置bean对象-->
    <!--id是豆子的身份证号,绝对不能够重复-->
    <!--class是类的全限定类路径,必须带有包名-->
    <bean id = "studentBean" class = "domain.Student"></bean>

</beans>

配置基础的配置文件

7.2 编写测试工具类

  • ApplicationContext翻译为应用上下文,其实就是Spring容器
public interface ApplicationContext{
    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

它本身是一个接口规范,其下面有相当多的实现子类,其中有一个实现叫做ClassPathXmlApplicationContext,这个实现子类能够从resources中加载核心配置文件,将其封装为一个重量级的应用上下文对象。

这个过程就是启动Spring。

然后程序员就能够按照这套规范中提供的方法,运用相关方法获取对象

//1.获取Spring容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.根据容器id获取这个对象
Object studentBean = applicationContext.getBean("studentBean");
System.out.println(studentBean);

Bean的id可以重复吗?

Spring的Bean的id是不能够重复的,如果重复了,Spring容器在启动(加载配置文件,封装应用上下文对象)的时候就会报错,报错信息为你定义的beanId已经存在了。

底层是如何创建对象的?

通过反射机制,调用无参构造方法来实例化对象,因此无参构造方法是需要在Bean中存在的。

把创建好的对象存储到一个什么样的数据结构当中了呢?

通过id=>object中获得相关的内容,但是这张表中的id是不能重复的,通过实验发现,如果你的beanId有重复的话,那么这时候,你的ApplicationContext就会构建失败。所以的话,如果你配置了相同的id,这个对象压根不会被封装。

在配置文件中配置的类必须是自定义的吗?可以使用JDK提供的类吗?

可以使用JDK提供的类

<bean id = "nowTime" class="java.util.Date"/>

普通的getBean(String id)只能获取Object对象,我不想强转怎么办?

可以使用如下方法实现泛型转型

StudentDAO studentDAO = applicationContext.getBean("studentDAO", StudentDAO.class);

这个方法底层是通过泛型实现的。

<T> T getBean(String var1, Class<T> var2) throws BeansException;
public interface BeanFactory {}
//IoC的顶级接口,能够生产Bean对象的一个工厂对象
//Spring底层实际上使用的是工厂模式,XML解析+工厂模式+反射机制
public interface ListableBeanFactory extends BeanFactory {}

Bean是在调用对应的getBean()方法的时候才创建的吗?

不是,这个的话是在容器启动的时候(类内部初始化的时候解析XML文件),封装上下文对象的时候创建的。

7.3 启用日志框架

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.19.0</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置文件修改时重新加载,默认true -->
<configuration>
    <loggers>
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </root>
    </loggers>
    <appenders>
        <console name = "spring6log" target="SYSTEM_OUT">
            <PattermLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS}[%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>
</configuration>

8. Spring对IoC的实现

8.1 IoC控制反转

控制反转是一种思想,它是为了降低程序的耦合度,提升程序的扩展力,达到OCP原则,达到DIP原则

控制反转,反转的是什么?

  • 将对象的创建权利交出去,交给第三方容器负责
  • 将对象和对象之间的关系的维护权交出去,交给第三方容器负责

控制反转这种思想如何实现?

  • DI(Dependency Injection):依赖注入

8.2 依赖注入

依赖注入实现了控制反转的思想

Spring通过依赖注入的方式来完成Bean管理的

Bean管理说的是Bean对象的创建以及Bean对象中属性的赋值

依赖注入

  • 依赖指的是对象和对象之间的关联关系
  • 注入指的是数据的传递行为,通过注入来让对象和对象之间产生关系

依赖注入常见的实现有两种

  • set注入
  • 构造注入

8.3 set注入

set注入是基于setXxx()实现的,底层会通过反射调用属性的set方法来对属性进行赋值, 这些方式要求属性必须对外提供set方法。因此我们必须在spring的配置中提供相关的依赖注入方法来让IoC容器为这些Bean构造他们的依赖方法

//定义set方法
public void setStudentDAO(StudentDAO studentDAO) {
    this.studentDAO = studentDAO;
}
<!--配置dao和service,我们的目的是将dao注入到service中去-->
<bean id="StudentDAOBean" class="com.orm.dao.impl.StudentDAOImpl"/>
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl">
    <!--name属性指定的值是set方法的方法名,然后把剩下的单词首字母变成小写-->
    <!--指定属性名称,你只需要告诉容器,你需要哪个对象的id,然后它就会找到这个对象-->
    <!--找到这个对象之后,然后通过set方法,构造方法set进去-->
    <property name="studentDAO" ref="StudentDAOBean"/>
</bean>

编写测试类

@Test
public void testService(){
    Teacher teacher = new Teacher("1","张三师");
    Student student = new Student("10","张三",teacher);
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring.xml");
    StudentService studentService = applicationContext.getBean("StudentServiceBean", StudentService.class);
    studentService.saveStudent(student);
}
  • 所以的话,我们可以总结,这个property实际上是基于你的setXxx()方法中的Xxx()来执行的。

通过property标签获取到属性名:studentDAO,然后组装一个set方法名称来invoke

"set"+property.toUppcase().charAt(0).property.subString(1);
//然后用之前的instance进行invoke
method.invoke(instance,setMethod,...)

上面这段代码可以总计为:

通过属性名推断出set方法名

通过反射机制调用setXxx()给属性赋值

property标签的属性是属性名规范,当然你也可以不这样写

property标签的ref是要注入的bean对象的id

8.4 构造注入

核心方法:通过调用构造方法来对指定的bean进行注入

<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl">
    <!--name属性指定的值是set方法的方法名,然后把剩下的单词首字母变成小写-->
    <!--指定属性名称,你只需要告诉容器,你需要哪个对象的id,然后它就会找到这个对象-->
    <constructor-arg index="0" ref="StudentDAOBean"/>
</bean>
  • 当然你可以向一个对象中注入其他对象,根据你的构造方法来对这些对象进行注入。
public StudentServiceImpl(StudentDAO studentDAO){
    this.studentDAO  = studentDAO;
}

注意这里是需要自己编写相关代码的。

这里可以推断一下底层的执行过程,它会根据你提供的构造方法,传入相关的类型参数,然后执行构造方法。

除此之外,还可以通过参数的名字来进行注入,这里的名字指的是构造方法上的参数的名字

另外如果我们不指定参数的下标不指定参数的名字,Spring也能进行自动类型推断,从而把ref注入到指定的对象中。

这里对于不指定参数的来做一个小小的实验,如果我们对于这些构造参数不指定参数,而这些构造类型又是相同的会发生什么

<bean id="StudentDAOBean1" class="com.orm.dao.impl.StudentDAOImpl"/>
<bean id="StudentDAOBean2" class="com.orm.dao.impl.StudentDAOImpl"/>
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl">
    <!--name属性指定的值是set方法的方法名,然后把剩下的单词首字母变成小写-->
    <!--指定属性名称,你只需要告诉容器,你需要哪个对象的id,然后它就会找到这个对象-->
    <constructor-arg ref="StudentDAOBean1"/>
    <constructor-arg ref="StudentDAOBean1"/>
</bean>
连接数据库成功
执行了构造方法...构造了一个对象
2022-11-23 15:19:11 003 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'StudentDAOBean2'
执行了构造方法...构造了一个对象
2022-11-23 15:19:11 004 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'StudentServiceBean'
2022-11-23 15:19:11 047 [main] INFO com.orm.service.StudentService - 执行了保存对象的方法....
程序结束,正在关闭数据库
数据库连接已经关闭

可以看到它构造了两个不同的对象出来进行注入,所以的话这样做是危险的,如果你对于不同的bean,有不同的要求,这时候你注入进去的对象具有不确定性,因此最好采用的是指定类型,指定参数,最为稳妥。

8.5 set注入外部bean

外部bean的特点是将bean定义在外面,在property标签使用ref属性进行注入。

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
    <property name="userDao" ref="userDaoBean"/>
</bean>

8.6 set注入内部bean

而内部的bean的特点是将bean定义在被注入的bean的里边

<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao">
            <bean class="com.powernode.spring6.dao.UserDao"/>
        </property>
    </bean>

8.7 注入简单的类型

<!-- 注入简单的类型-->
<bean id="StudentBean" class="com.orm.domain.Student">
    <!--如果是简单类型的属性赋值,你就要用value标签来标志这些属性了-->
    <property name="id" value="14"/>
</bean>
public static boolean isSimpleValueType(Class<?> type) {
    return Void.class != type && 
        Void.TYPE != type && 
        (ClassUtils.isPrimitiveOrWrapper(type) || 
         Enum.class.isAssignableFrom(type) || 
         CharSequence.class.isAssignableFrom(type) || 
         Number.class.isAssignableFrom(type) || 
         Date.class.isAssignableFrom(type) || 
         Temporal.class.isAssignableFrom(type) ||//关于时区的特性
         URI.class == type || 
         URL.class == type || 
         Locale.class == type || 
         Class.class == type);
}

BeanUtils为我们提供了判定是否是简单类型的方法,这里的定义是8种基本数据类型以及基本包装类型

  • 基本数据类型
  • 基本数据类型及其包装类
  • String或者其他CharSequence子类
  • Number子类
  • Date子类
  • Enum子类
  • URI
  • URL
  • Temporal子类
  • Locale
  • Class
  • 另外还包括以上简单类型对应的数组类型

8.8 经典应用案例

我们可以使用IoC容器来管理我们的数据源对象

public class MyDataSource implements DataSource {
    private String driver;
    private String url;
    private String username;
    private String password;
}
<!--让spring来管理我们的数据源-->
<bean id = "myDataSource" class="com.orm.common.MyDataSource">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/orm?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

8.9 级联属性赋值

  • 可以通过这样内部bean注入的方式进行级联属性赋值
<bean id="StudentBean" class="com.orm.domain.Student">
    <!--如果是简单类型的属性赋值,你就要用value标签来标志这些属性了-->
    <property name="id" value="15"/>
    <property name="name" value="张三"/>
    <property name="tutor">
        <bean class="com.orm.domain.Teacher">
            <property name="name" value="张三师"/>
            <property name="id" value="1"/>
        </bean>
    </property>
</bean>
  • 也可以通过外部bean注入的方式进行级联属性赋值
<bean id="StudentBean" class="com.orm.domain.Student">
    <!--如果是简单类型的属性赋值,你就要用value标签来标志这些属性了-->
    <property name="id" value="15"/>
    <property name="name" value="张三"/>
    <property name="tutor" ref="TeacherBean"/>
</bean>
<bean id="TeacherBean" class="com.orm.domain.Teacher">
    <property name="id" value="1"/>
    <property name="name" value="张三师"/>
</bean>
  • 还可以通过这样的方式进行赋值
<bean id="StudentBean" class="com.orm.domain.Student">
    <!--如果是简单类型的属性赋值,你就要用value标签来标志这些属性了-->
    <property name="id" value="15"/>
    <property name="name" value="张三"/>
    <property name="tutor" ref="TeacherBean"/>
    <property name="tutor.id" value="2"/>
    <property name="tutor.name" value="李四师"/>
</bean>
<bean id="TeacherBean" class="com.orm.domain.Teacher">
    <property name="id" value="1"/>
    <property name="name" value="张三师"/>
</bean>

而且通过实验可知,最终是以StudentBean中的你定义的属性值为准的

2022-11-23 15:57:29 602 [main] INFO SpringTest - Student{id='15', name='张三', tutor='Teacher{id='2', name='李四师'}'}
2022-11-23 15:57:29 602 [main] INFO SpringTest - Teacher{id='2', name='李四师'}

最终下面这个bean的值会被覆盖掉

使用级联属性进行复制需要注意亮点

  • 配置的顺序不能颠倒,必须是ref的值在下面
  • 你.的那个属性必须有get方法

8.10 注入数组类型

<!--如何给数组的属性进行赋值?-->
<bean id="UserBean" class="com.orm.domain.User">
    <property name="names">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
</bean>
  • 非简单类型怎么办?
<bean id="TeacherBean2" class="com.orm.domain.Teacher">
    <property name="id" value="1"/>
    <property name="name" value="张三师"/>
</bean>
<bean id="TeacherBean3" class="com.orm.domain.Teacher">
    <property name="id" value="1"/>
    <property name="name" value="张三师"/>
</bean>
<bean id="UserBean" class="com.orm.domain.User">
    <property name="teachers" >
        <array>
            <ref bean="TeacherBean"/>
            <ref bean="TeacherBean2"/>
            <ref bean="TeacherBean3"/>
        </array>
    </property>
</bean>

8.11 集合元素如何注入?

<property name="address">
    <list>
        <value>哈哈</value>
        <value>哈哈</value>
        <value>哈哈</value>
        <value>哈哈</value>
    </list>
</property>
<property name="books">
    <set>
        <value>哈哈1</value>
        <value>哈哈2</value>
        <value>哈哈3</value>
        <value>哈哈4</value>
    </set>
</property>
<property name="phone">
    <map>
        <entry key="1" value="110"/>
        <entry key="2" value="110"/>
        <entry key="3" value="110"/>
    </map>
</property>

8.12 Property如何注入

publicclass Properties extends Hashtable<Object,Object> {}
public class Hashtable<K,V> extends Dictionary<K,V> 
    implements Map<K,V>, Cloneable, java.io.Serializable {}

Properties本质上也是一个Map集合,它的父类是HashTable,实现了Map接口

<property name="properties">
    <props>
        <prop key="1">110</prop>
        <prop key="2">110</prop>
        <prop key="3">110</prop>
        <prop key="4">110</prop>
    </props>
</property>

8.13 注入空字符串或者null

注入空字符串使用:<value/> 或者 value=""

<bean id="vipBean" class="com.powernode.spring6.beans.Vip">
        <!--空串的第一种方式-->
        <!--<property name="email" value=""/>-->
        <!--空串的第二种方式-->
        <property name="email">
            <value/>
        </property>
    </bean>

注入null有两种方式

  • 不给属性进行复制
  • 使用<null/>
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="vipBean" class="com.powernode.spring6.beans.Vip">
        <property name="email">
            <null/>
        </property>
    </bean>

</beans>

8.14 含有特殊符号怎么办?

特殊字符 转移字符
> &gt;
< &lt;
&apos;
&quot;
& &amp;

使用CDATA

<bean id="mathBean" class="com.powernode.spring6.beans.Math">
        <property name="result">
            <!--只能使用value标签-->
            <value><![CDATA[2 < 3]]></value>
        </property>
    </bean>

使用CDATA的时候只能使用value标签而无法使用value属性,是XML文件解析的规范

8.15 p命名空间注入

目的:是为了简化set注入配置

使用p命名空间注入的前提条件包括两个

  • 第一,在XML头部信息中添加关于p命名空间的配置信息xmlns:p="http://www.springframework.org/schema/p"
  • 第二:p命名空间的注入是基于setter方法的,所以需要对应的属性提供setter方法。

p命名空间注入的底层还是set注入,只不过p命名空间的注入可以让spring的配置变得更加简单

<!--头部加上p命名空间-->
<!--对于基本数据类型可以用直接等于值的方式-->
<!--对于难处理的基本数据类型或者非基本数据类型可以使用ref-->
<!---底层依然是set方法注入-->
<bean id="dogBean" class="com.orm.domain.Dog" p:age="15" p:name="大黄" p:birth-ref="birthBean"/>

<bean id="birthBean" class="java.util.Date"/>

8.16 c命名空间注入

目的是为了简化构造注入的一种方式,所以底层还是构造注入

在头部添加相关信息xmlns:c="http://www.springframework.org/schema/c"

<!--c命名空间-->
<bean id="personBean" class="com.orm.domain.People" c:age="15" c:name="哈哈"/>

8.16 util命名空间

使用util命名空间可以让配置复用

   <!--连接数据库的信息-->
   <util:properties id="prop">
       <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
       <prop key="url">jdbc:mysql://localhost:3306/orm?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</prop>
       <prop key="username">root</prop>
       <prop key="password">root</prop>
   </util:properties>
<bean id="dataSource1" class="com.powernode.spring6.beans.MyDataSource1">
       <property name="properties" ref="prop"/>
   </bean>

   <bean id="dataSource2" class="com.powernode.spring6.beans.MyDataSource2">
       <property name="properties" ref="prop"/>
   </bean>

其实在看到这个,感觉其实能够新建一个bean,然后在bean中新建一个pro变量,设置这个bean,然后所有的类都去复用这个bean,就可以了。

9. 基于XML的自动装配

Spring还可以完成自动化的注入,主动注入又被称为自动装配,它可以根据名字进行自动装配,又可以根据类型进行自动装配

手动装配的例子

<!--配置dao和service,我们的目的是将dao注入到service中去-->
<bean id="StudentDAOBean" class="com.orm.dao.impl.StudentDAOImpl"/>

<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl">
    <constructor-arg index="0" ref="StudentDAOBean"/>
</bean>

9.1 根据名字的自动装配

它也是基于set注入实现的。

<!--根据名字进行自动装配-->
<bean id="studentDAO" class="com.orm.dao.impl.StudentDAOImpl"/>
<!--id也称为是bean的名称-->
<!--这时候其内部的装配有两步-->
<!--根据你的内部所需要对象,抽取出所需要的属性名-->
<!--然后根据你的属性名,在大map里面通过这个属性名,它认为你的beanId就是这个属性名,然后拿到这个东西-->
<bean id="StudentServiceBean"  class="com.orm.service.impl.StudentServiceImpl" autowire="byName">

9.2 根据名称进行自动装配

<!--根据类型进行自动装配-->
<bean class="com.orm.dao.impl.StudentDAOImpl"/>
<bean id="StudentServiceBean"  class="com.orm.service.impl.StudentServiceImpl" autowire="byType">

在有效的配置文件中,某种类型的实例只能有一个。

对于实现子类来说,也是一样的,只能够有一个bean的实例

<!--根据类型进行自动装配-->
<!--像这样的实现子类的自动注入就会报错-->
<bean class="com.orm.dao.impl.StudentDAOImpl"/>
<bean class="com.orm.dao.impl.StudentDAOImpl2"/>
<bean id="StudentServiceBean"  class="com.orm.service.impl.StudentServiceImpl" autowire="byType">
</bean>

10.Spring 引入外部属性配置文件

<!--引入外部配置文件-->
<!--使用此标签即可引入资源,location默认从类的根路径下开始加载资源-->
<context:property-placeholder  location="jdbc.properties"/>
<bean id="ds" class="com.orm.common.MyDataSource">
    <property name="driver" value="${driver}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>
public class MyDataSource implements DataSource {
    private String driver;
    private String url;
    private String username;
    private String password;
}

这里一定要注意!!!

2022-11-23 19:08:08 728 [main] INFO SpringTest - MyDataSource{driver='com.mysql.cj.jdbc.Driver', url='jdbc:mysql://localhost:3306/orm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai', username='12743', password='root'}

Spring在加载${}中的属性的时候,它默认会去找windows系统的环境变量!!!因此推荐的做法是在你的key值那里加个前缀。


文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录