Spring-工厂

作者: zhl 分类: Spring 发布时间: 2023-10-29 21:35

一、简单工厂和通用工厂

EJB(Enterprise Java Bean)技术的问题

  1. 运行环境苛刻
  2. 代码移植性差
    EJB是一个重量级的框架

什么是Spring

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

  • 轻量级
    1. 对于运行环境是没有额外要求
      • 开源 tomcat resion jetty
      • 收费 weblogic websphere
    2. 代码移植性高
      • 不需要实现额外接口
  • JavaEE的解决方案
    • 对Java开发的每一层都有相关的解决方案

  • 整合设计模式
    例如:工厂 代理 模板 策略...

设计模式

  • 广义概念:
    解决面向对象设计中,解决特定问题的经典代码
  • 狭义概念:
    GOF4人帮定义的23种设计模式

工厂设计模式

什么是工厂设计模式

  • 通过工厂类,创建对象

    User user = new User();
    UserDao userDao = new UserDaoImpl();
  • 好处:解耦合

    • 耦合:指定是代码间的强关联关系,一方的改变会影响到另一方
    • 问题:不利于代码维护
    • 例如:UserService userService = new UserServiceImpl();

简单工厂的实现

public void test1(){
    UserService userService = BeanFactory.getService();
    userService.login("name","zhl");
    User user = new User("zhl","123");
    userService.register(user);
}
public class BeanFactory{
    public static UserService getUserService(){
        return new UserServiceImpl();
    }
}

对象的创建方式:

  1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl
  2. 通过反射的形式 创建对象 解耦合

简单工厂的创建

public class BeanFactory {

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

    static {
        try {
            InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.properties");
            properties.load(is);

            is.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static UserService getUserService(){
        UserService userService = null;
        try {
            Class clazz = Class.forName(properties.getProperty("userService"));
            userService = ((UserService) clazz.newInstance());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return userService;
    }
    public static UserDao getUserDao(){
        UserDao userDao = null;
        try {
            Class clazz = Class.forName(properties.getProperty("userDao"));
            userDao = ((UserDao) clazz.newInstance());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return userDao;
    }
}

通用工厂的实现

在一个项目中,需要创建的对象很多,为了避免代码的冗余,重复操作。
解决方案:创建一个通用工厂

/**
 * 通用工厂的实现
 * @param key applicationContext.properties配置文件中 key的值
 * @return Object对象
 */
public static Object getBean(String key){
    Object ret = null;
    try {
        Class clazz = Class.forName(properties.getProperty(key));
        ret = clazz.newInstance();
    }catch (Exception e){
        e.printStackTrace();
    }
    return ret;
}

二、第一个Spring程序

  1. 导入pom依赖
    <!--导入SpringFrameWord的依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.24</version>
    </dependency>
  2. Spring的配置文件
    1. 配置文件的放置位置:任意位置 没有硬性要求
    2. 配置文件的命名 :没有要求 建议:applicationContext.xml

Spring的核心API

  • ApplicationContext
    作用:Spring提供的ApplicationContext这个工厂,用于对象的创建

优点:解耦合

  • ApplicationContext接口类型
    • 接口:屏蔽实现的差异
    • 非web环境:ClassPathXmlApplicationContext(main junit)
    • web环境:XmlWebApplicationContext
  • 重量级资源
    • ApplicationContext工厂的对象占用大量内存
    • 不会频繁的创建对象:一个应用只会创建一个工厂对象
    • ApplicationContext工厂是线程安全的(多线程并发访问)

程序开发步骤:

  1. 创建类型
  2. 配置文件的配置 applicationContext.xml
    id属性 名字(唯一标识)
    class属性 配置全限定类名
    \
  3. 通过工厂类,获得对象
//ApplicationContext 
//    |-ClassPathXmlApplicationContext    
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml);
Person person = (Person) ctx.getBean("person");
/**
 * Spring工厂提供的其他方法
 */
@Test
public void test3(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    //不需要手动进行强制类型转换
    Person person = ctx.getBean("person", Person.class);
    System.out.println("person = "+ person);    //person = com.mysite.bean.Person@1e7c7811

    //保证Spring的配置文件中,只能由一个bean Class 是Person类型
    Person person = ctx.getBean(Person.class);
    System.out.println("person = "+ person);    //person = com.mysite.bean.Person@77f99a05*/

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

    //获取某类型的所有名字
    String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
    for (String beanName : beanNamesForType){
        System.out.println("beanName = " + beanName);
    }

    //判断是否存在指定Id 值的bean ,不能判断name值
    System.out.println(ctx.containsBeanDefinition("person"));

    //判断是否存在指定Id 或name(别名)属性 值的bean
    System.out.println(ctx.containsBean("person"));
}
  • 配置文件中需要注意的细节
    1. 只配置class属性
      使用上述这种配置 Spring默认会生成一个id 值:com.mysite.bean.Person#0
      应用场景:如果这个bean 只需要使用一次,那么就可以省略id值
      如果这个bean会使用多次,或者被其他bean引用则需要设置id值
  1. name 属性
    作用:用于在Spring的配置文件中,为bean 对象定义别名 (小名)
    相同:
    1. ctx.getBean("id|name") -> Object
      1. <bean id="" class=""
      2. <bean name="" class=""
        区别:
    2. 别名可以定义多个,id 属性只能有一个值
    3. 目前 id 的命名是没有要求的
      name属性的值,命名没有要求 ,一般很少应用
      name属性会应用在特殊命名的场景下:/person (spring + structs1)

Spring工厂的底层实现原理(简易版)

三、Spring5.x与日志框架的整合

Spring 与日志框架进行整合,日志框架就可以在控制台中,输出Spring框架运行过程中的一些重要的信息。

  • Spring如何整合日志框架
    • Spring 1.2.3x 早期都是以 commons-logging.jar
    • Spring5.x默认整合的日志框架 logback log4j2
  • Spring5.x整合log4j
    1. 引入log4j jar包
    2. 引入log4.properties 配置文件
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>
# 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工厂及配置文件,为所创建对象的成员变量赋值

为什么需要注入?

如何进行注入?[开发步骤]

  • 类的成员变量提供get,set方法
  • 配置Spring的配置文件
    <bean id="person" class="com.mysite.bean.Person">
    <property name="name">
        <value>lisi</value>
    </property>
    <property name="id">
        <value>20</value>
    </property>
    </bean>

    注入好处

    解耦合

Spring注入的原理分析(简易版)

Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值

注入的两种方式

五、Set注入详解

JDK内置类型

  1. String + 8种基本类型
    使用\标签
    <property name="name">
    <value>lisi</value>
    </property>
  2. 数组
    <property name="emails">
    <list>
        <value>zhangsan@foxmail.com</value>
        <value>lisi@foxmail.com</value>
        <value>wangwu@foxmail.com</value>
    </list>
    </property>
  3. set集合
    <!--Set<String> tels-->
    <property name="tels">
    <set>
        <value>138888888</value>
        <value>122222222</value>
        <value>100000000</value>
        <value>100000000</value>
        <value>100000000</value>
    </set>
    </property>
  4. List集合
    <!--List<String> addresses-->
    <property name="addresses">
    <list>
        <value>郑州</value>
        <value>zpark</value>
        <value>zzuli</value>
        <value>zzuli</value>
    </list>
    </property>
  5. map集合
    <!--Map<String,String> qqs-->
    <!--注意: map  entry  key 特有的标签 
          值 根据对应类型选择 对应类型的标签-->
    <property name="qqs">
    <map>
        <entry>
            <key><value>zhl</value></key>
            <value>23223333</value>
        </entry>
        <entry>
            <key><value>zzz</value></key>
            <value>99999999</value>
        </entry>
    </map>
    </property>
  6. Properties
    Properties 类型 特殊的Map key=String value=String ,只能存储String类型
    value写在标签体之间
    <property name="p">
    <props>
        <prop key="key1">value1</prop>
        <prop key="key1">value1</prop>
    </props>
    </property>
  7. 复杂的JDK类型(Date)
    需要程序员自定义类型转化器,处理。

用户自定义类型

第一种方式

  • 为成员变量提供set get方法
  • 配置文件中进行注入(赋值)
    <bean name="userService" class="xxx.UserServiceImpl">
    <property name="userDao">
        <bean class="xxx.UserDaoImpl"/>
    </property>
    </bean>

第二种方式

第一种方式存在问题,配置文件的代码冗余,被注入对象(UserDao),多次创建,浪费(JVM)内存资源

  • 为成员变量提供set get方法
  • 配置文件中进行配置

Set注入的简化写法

基于属性简化

<!--JDK类型注入-->
<property name="name">
    <value>zhangsan</value>
</property>

<property name="name" value="zhangsan">

<!--用户自定义类型-->
<property name="userDao">
    <ref bean="userDao">
</property>

<property name="userDao" ref="userDao">

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

基于p命名空间简化

<!--JDK类型注入-->
<bean id="person" class="xxx.Person">
    <property name="name">
        <value>zhangsan</value>
    </property>
</bean>

<bean id="" class="" p:name="zhangsan"/>

<!--用户自定义类型-->
<bean id="userService" class="xxx.UserServiceImpl">
    <property name="userDao">
        <ref bean="userDao"/>
    </property>
</bean>

<bean id="userService" class="xxx.UserServiceImpl" p:userDao-ref="userDao"/>

六、构造注入

注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring调用Set方法,通过配置文件,为成员变量赋值
构造注入:Spring调用构造方法,通过配置文件,为成员变量赋值

开发步骤

  • 提供有参构造方法
    public class Customer implements Serializable {
    private String name;
    private Integer age;
    public Customer(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    }
  • Spring的配置文件
    <bean id="customer" class="com.mysite.bean.Customer">
    <constructor-arg>
        <value>wangwu</value>
    </constructor-arg>
    <constructor-arg>
        <value>32</value>
    </constructor-arg>
    </bean>

构造方法重载

参数个数不同时

通过控制\标签的数量进行区分

参数个数相同时

通过在标签中引入 type 属性 进行类型的区分 \

注入的总结

未来的实战中,应用set注入还是构造注入?
答案:set注入更多

  1. 构造注入麻烦(重载)
  2. Spring底层大量 使用了 set注入

七、反转控制与依赖注入

反转控制(IOC Inverse Of Control)

控制:对于成员变量赋值的控制权

反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和Spring配置文件中完成

好处:解耦合

底层实现:工厂设计模式

依赖注入(DI Dependency Injection)

注入:通过Spring的工厂及配置文件,为对象(bean,组件)的成员变量赋值

依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring配置文件进行注入 (赋值)

好处:解耦合

八、Spring工厂创建复杂对象

  • 什么是复杂对象
    复杂对象:不能直接通过new 构造方法创建的对象,如 Connection、
    SqlSessionFactory

Spring工厂创建复杂对象的三种方式

FactoryBean接口

  • 开发步骤
    • 实现FactoryBean接口
    • Spring配置文件的配置
      <bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean"/>

      如果Class 中指定的类型 是FactoryBean 接口的实现类,那么通过id 值获得的是这个类所创建的复杂对象

正常情况:

<bean id="user" class="com.mysite.bean.User">

ctx.getBean("user"); 获得的是User这个类的对象 User对象

易错点:

<bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean">

错误认知:
ctx.getBean("conn"); 获得的是ConnectionFactoryBean这个类的对象
FactoryBean接口的实现类:ConnectionFactoryBean
ctx.getBean("conn"); 获得的是它所创建的复杂对象Connection

  • 细节

    • 如果就想获得FactoryBean类型的对象
    • 通过 ctx.getBean("&conn"); 获取ConnectionFactoryuBean对象
    • isSingleton方法
    • 返回true只会创建一个复杂对象
    • 返回false 每一次都会创建新的对象
    • 问题:根据这个对象的特点,决定是返回true(SqlSessionFactory) 还是 false(Connection)
    • 依赖注入的体会(DI)
    • 把ConnectionFactoryBean中依赖的4 个字符串信息,进行依赖注入
    • 好处:解耦合
      <bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean">
      <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
      <property name="user" value="root"/>
      <property name="password" value="15737979065"/>
      </bean>
  • FactoryBean的实现原理[简易版]

    • 接口回调
    • 为什么Spring规定Factory接口 并且 实现 getObject()
    • ctx.getBean("conn"); 获得是复杂对象 Connection 而没有获得 ConnectionFactoryBean()
    • Spring内部执行流程
      1. 通过conn 获得 ConnectionFactoryBean类的对象,进而通过instanceof 判断出是FactoryBean接口的实现类
      2. Spring按照规定 getObject() ----> Connection
      3. 返回Connection

  • FactoryBean总结
    • Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续使用Spring整合其他框架,大量应用FactoryBean

实例工厂

  1. 避免Spring框架的侵入
    1. 避免 implements FactoryBean
  2. 整合遗留系统
    public class ConnectionFactory {
    public Connection getConnection(){
    }
    }
    // 接盘的系统 是遗留下来的。我们想要通过这种方式 创建复杂对象,但是由于一些原因 
    // 此文件可能是.class 文件,我们不能对它直接修改。
    // 这时我们可以通过 Spring配置文件的方式 ,以实例工厂的方式 使用此方法创建复杂对象
    • 开发步骤
      <bean id="connFactory" class="com.mysite.factorybean.ConnectionFactory"/>
      <bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
      <!--创建 ConnectionFactory的对象connFactory调用其中的 getConnection的方法创建 Connection复杂对象-->

静态工厂

  • 开发步骤
    <bean id="conn" class="com.mysite.factorybean.StaticConnectionFactory" factory-method="getConnection">

不需要创建对象,与实例工厂的区别就是 静态工厂里的方法是 静态的,Spring配置文件中不需要实例化工厂,都是为了避免Spring框架的侵入

九、控制Spring工厂创建对象的次数

在复杂对象的创建中:
实现FactoryBean接口,通过isSingleton可以控制工厂创建对象的次数

如何控制简单对象的创建次数?

<bean id="account" scope="singleton|prototype" class="xxx.Account">

singleton:只会创建一次简单对象

prototype:每一次都会创建新的对象

默认值是 singleton

如何控制复杂对象的创建次数?

public class xxx implements FactoryBean{
    public boolean isSingleton() {
        return false|true;
    }
}

实现FactoryBean接口的方式 可以 通过isSingleton方法中

返回值为 true ,表示只会创建一次

返回值为 false ,每一次都会创建新的

除了使用这个方法,都可以通过 scope属性,进行对象创建次数的控制

为什么要控制对象的创建次数?

有些对象只需要创建一次,例如SqlSessionFactory: 一个数据库对应一个单例对象。还有Dao,Service

有些对象需要创建多次,例如Connection:涉及到控制事务,一次连接就创建一个Connection对象。还有SqlSession,Session

  • 好处:节省不必要的内存浪费

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注