Spring学习笔记


Spring

什么是Spring

Spring是一个轻量级的JavaEE解决方案,它整合了众多优秀的设计模式。

什么是轻量级?

  • 对于运行环境没有额外要求的;

    开源: tomcat、resion、jetty

    收费:weblogic、websphere

  • 代码移植性高:不需要实现额外接口。

工厂设计模式

  • 好处: 解决耦合
  • 耦合: 指定是代码间的强关联关系,⼀方的改变会影响到另⼀方;
  • 问题: 把接口的实现类硬编码在了程序中,不利于代码维护;
UserService userService = new UserServiceImpl();

简单工厂的设计

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
    private static Properties env = new Properties();
    
    static{
        try {
            //第一步 获得IO输入流
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //第二步 文件内容 封装 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
            env.load(inputStream);

            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    
    /*
    对象的创建方式:
        1. 直接调用构造方法 创建对象  UserService userService = new UserServiceImpl();
        2. 通过反射的形式 创建对象 解耦合
        Class clazz = Class.forName("com.2hu0.basic.UserServiceImpl");
        UserService userService = (UserService)clazz.newInstance();
     */

    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class clazz = Class.forName(env.getProperty("userService"));
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return userService;
    }

    public static UserDAO getUserDAO(){
        UserDAO userDAO = null;
        try {
            Class clazz = Class.forName(env.getProperty("userDAO"));
            userDAO = (UserDAO) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return userDAO;
    }

}

配置文件

# Properties 集合 存储 Properties文件的内容
# 特殊Map key=String value=String
# Properties [userService = com.2hu0.xxx.UserServiceImpl]
# Properties.getProperty("userService")

userService = com.2hu0.basic.UserServiceImpl
userDAO = com.2hu0.basic.UserDAOImpl

通用工厂的设计

简单工厂存在代码冗余,需要进一步简化

image-20220406190138294

通用工厂代码

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
    private static Properties env = new Properties();
    static{
        try {
            //第一步 获得IO输入流
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //第二步 文件内容 封装 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
            env.load(inputStream);

            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
     /*
      key 小配置文件中的key [userDAO,userService]
      */
     public static Object getBean(String key){
         Object ret = null;
         try {
             Class clazz = Class.forName(env.getProperty(key));
             ret = clazz.newInstance();
         } catch (Exception e) {
            e.printStackTrace();
         }
         return ret;
     }

}

同时我们需要更新配置文件

# Properties 集合 存储 Properties文件的内容
# 特殊Map key=String value=String
# Properties [userService = com.baizhiedu.xxx.UserServiceImpl]
# Properties.getProperty("userService")

userService = com.baizhiedu.basic.UserServiceImpl
userDAO = com.baizhiedu.basic.UserDAOImpl

通用工厂的使用方式

1. 定义类型(类)
2. 通过配置文件的配置告知工厂
   `applicationContext.properties``key = value`;
3. 通过工厂获得类的对象
   `Object ret = BeanFactory.getBean("key");`

总结:

Spring本质:工厂ApplicationContext(applicationContext.xml)

第一个Spring程序

环境搭建

依赖查询网站

配置Spring的jar包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

Spring的配置文件:

  • 配置文件的放置位置:任意位置,没有硬性要求
  • 命名建议为:applicationContext.xml

Spring的核心API

ApplicationContext

  • 作用:Spring 提供的 ApplicationContext 这个工厂,用于对象的创建;
    好处:解耦合

  • ApplicationContext是接口类型

    接口:屏蔽实现的差异

    非Web环境: ClassPathXmlApplicationContext

    Web环境: XmlWebApplicationContext

  • 重量级资源:

    ApplicationContext 工厂的对象占用大量的内存

    一个应用只会创建一个工厂对象。

    ApplicationContext 工厂:一定是线程安全的

样例

1.创建类型:Person.java

public class Person{}

2.配置文件的配置

<?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="person" class="com.yusael.basic.Person"/>

</beans>

3.通过工厂类获得对象

/**
 * 用于测试Spring的第一个程序
 */
@Test
public void test() {
    // 1、获取spring的工厂
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 2、通过工厂类获得对象
    Person person = (Person)ctx.getBean("person");
    System.out.println(person);
}

细节:

Spring工厂创建的对象,叫做bean或者组件(componet)

Spring工厂常见的方法

getbean:传入id值 和 类名 获取对象,不需要强制类型转换。

// 通过这种方式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);

getbean只指定类名,Spring的配置文件中只能有一个bean是这个类型

// 使用这种方式的话, 当前Spring的配置文件中 只能有一个bean class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);

getBeanDefinitionNames:获取Spring配置文件中所有的bean标签的id值

// 获取的是Spring工厂配置文件中所有bean标签的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
 System.out.println("beanDefinitionName = " + beanDefinitionName);
}

getBeanNamesForType:根据类型获得Spring配置文件中对应的id值

// 根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
 System.out.println("id = " + id);
}

containsBeanDefinition:用于判断是否存在指定id值的bean,不能判断name值

// 用于判断是否存在指定id值的bean,不能判断name值
if (ctx.containsBeanDefinition("person")) {
 System.out.println(true);
} else {
 System.out.println(false);
}

配置文件中的细节

如果bean只配置class属性:

<bean class="com.yusael.basic.Person"></bean>
  • 会自动生成一个id

  • 应用场景:

    如果这个bean只需要使用一次,那么就可以省略id值

    如果这个bean会使用多次,或者被其他bean引用则需要设置id值

name属性:

  • 作用:用于在 Spring 的配置文件中,为 bean 对象定义别名(小名)
  • name 与 id 的相同点:
    • ctx.getBean("id")ctx.getBean("name") 都可以创建对象;
    • <bean id="person" class="Person"/><bean name="person" class="Person"/> 等效;
  • name 和 id 的区别
    • 别名可以定义多个,但是 id 属性只能有⼀个值;
    • XML 的 id 属性的值,命名要求:必须以字母开头,可以包含 字母、数字、下划线、连字符;不能以特殊字符开头 /person
    • XML 的 name 属性的值,命名没有要求,/person 可以。
      但其实 XML 发展到了今天:ID属性的限制已经不存在,/person也可以。

思考:

问题:未来在开发过程中,是不是所有的对象,都会交给 Spring 工厂来创建呢?

回答:理论上是的,但是有特例 :实体对象(entity) 是不会交给Spring创建,它由持久层框架进行创建。

Spring整合日志框架

Spring 与日志框架进行整合,日志框架就可以在控制台中,输出Spring框架运行过程中的⼀
些重要的信息。
好处:便于了解Spring框架的运行过程,利于程序的调试。

默认日志框架
Spring 1.x、2.x、3.x 早期都是基于commonslogging.jar
Spring 5.x 默认整合的日志框架 logback、log4j2

导入log4j依赖

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>

创建log4j.properties配置文件

# resources文件夹根目录下
### 配置根
log4j.rootLogger = debug,console

### 日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

注入(Injection)

什么是注入

注入:通过 Spring 工厂及配置文件,为所创建对象的成员变量赋值。

为什么要注入?

  • 通过编码的方式,为成员变量进行赋值,存在耦合。
  • 注入的好处:解耦合
public void test4() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) ctx.getBean("person");
    // 通过代码为变量赋值, 存在耦合, 如果我们以后想修改变量的值, 需要修改代码, 重新编译
    person.setId(1);
    person.setName("zhenyu");
    System.out.println(person);
}

具体步骤

  • 类的成员变量提供 set get 方法
  • 配置 spring 的配置文件
<bean id="person" name="p" class="com.yusael.basic.Person">
    <property name="id">
        <value>10</value>
    </property>
    <property name="name">
        <value>yusael</value>
    </property>
</bean>

Spring 底层通过调用对象属性对应的 set 方法,完成成员变量的赋值,这种方式也称为 Set注入

image-20220406193941930

Set注入详解

Set注入的变量类型:

  • JDK内置类型
    8种基本类型 + String、数组类型、set集合、list集合、Map集合、Properties集合。
  • 用户自定义类型

针对于不同类型的成员变量,在<property标签中,需要嵌套其他标签:

JDK内置类型

String+8种基本类型
<property name="id">
 <value>10</value>
</property>
<property name="name">
 <value>yusael</value>
</property>
数组
<property name="emails">
    <list>
        <value>abc@qq.com</value>
        <value>123@qq.com</value>
        <value>hello@qq.com</value>
    </list>
</property>
Set集合
<property name="tels">
 <set>
  <value>138xxxxxxxxxx</value>
  <value>139xxxxxxxxxx</value>
  <value>138xxxxxxxxxx</value><!--set会自动去重-->
 </set>
</property>
List集合
<property name="addresses">
 <list>
  <value>China</value>
  <value>Earth</value>
  <value>hell</value>
 </list>
</property>

Map集合

<property name="qqs">
    <map>
        <entry>
            <key><value>hello</value></key>
            <value>12312312312</value>
        </entry>
        <entry>
            <key><value>world</value></key>
            <value>21314214214</value>
        </entry>
    </map>
</property>

Properties

<property name="p">
    <props>
        <prop key="key1">value1</prop>
        <prop key="key2">value2</prop>
        <prop key="key3">value3</prop>
    </props>
</property>
复杂JDK类型(Date、)

需要程序员自定义类型转换器

用户自定义类型

第一种方式
  • 为成员变量提供 set get 方法
  • 配置文件中进行注入(赋值)
<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <bean class="com.yusael.dao.UserDAOImpl"/>
    </property>
</bean>
第二种方式

第⼀种赋值方式存在的问题:

  1. 配置文件代码冗余;
  2. 被注入的对象 (UserDAO)多次创建,浪费(JVM)内存资源。

[开发步骤]:

  • 为成员变量提供 set get 方法;
  • 配置文件中进行配置;
<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

Set注入简化

基于属性的简化

JDK类型注入

<property name="id">
 <value>10</value>
</property>

JDK类型注入简化:value 属性只能简化 8种基本类型 + String 注入标签;

<property name="id" value="10"/>

用户自定义类型注入:

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

用户自定义类型注入简化:

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>
基于p命名空间的简化

JDK类型注入

<bean id="person" name="p" class="com.yusael.basic.Person">
 <property name="id">
  <value>10</value>
 </property>
 <property name="name">
  <value>yusael</value>
 </property>
</bean>

JDK类型注入-基于p命名空间的简化

<bean id="person" name="p" class="com.yusael.basic.Person" 
p:name="yusael" p:id="10"/>

用户自定义类型注入

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

用户自定义类型注入 - 基于p命名空间的简化

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl" 
p:userDAO-ref="userDAO"/>

构造注入

  • 注入:通过 Spring 的配置文件,为成员变量赋值;
  • Set注入:Spring 调用 Set 方法 通过 配置文件 为成员变量赋值;
  • 构造注入:Spring 调用 构造方法 通过 配置文件 为成员变量赋值
具体实现
  • 提供有参构造方法

    public class Customer {
        private String name;
        private int age;
    
        public Customer(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Customer{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

spring配置文件

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
    <constructor-arg>
        <value>21</value>
    </constructor-arg>
</bean>

构造方法重载

参数个数不同

参数个数不同时,通过控制 <constructor-arg> 标签的数量进行区分;

如果只有一个参数的话,只需要一对 <constructor-arg> 标签:

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
</bean>
参数相同
<bean id="customer" class="com.yusael.constructor.Customer">
 <constructor-arg type="int">
     <value>20</value>
 </constructor-arg>
</bean>

总结

未来的实战中,应用 set注入 还是 构造注入

答:set 注入更多。

  • 构造注入麻烦(重载)
  • Spring 框架底层大量应用了 set注入。

文章作者: 2hu0
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 2hu0 !
评论
代码块功能依赖 代码语言 代码块复制 代码块收缩
  目录