day1_Spring

学习目标

  • 理解Spring是什么
  • 了解工厂解耦
  • 掌握IOC - 控制反转
  • 掌握DI 依赖注入(属性赋值)

一、简介

1. 什么是Spring

  • Spring 是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架,以IoC(Inversion of Control控制反转,目的是解耦)和AOP(面向切面编程,本质是动态代理,目的是增强)为内核
    • Spring家族有很多的框架,涉及到所有层(web |service |dao)
    • 今天学的Spring仅仅是Spring家族里面的其中一个框架 Spring Framework (IOC + AOP)
  • 提供了:
    • 表现层(web层): Spring MVC
    • 业务层(service层) : Spring
    • 持久层(Dao层):Spring JDBCTemplate, Spring Data JPA
  • 能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

2. Spring的发展历程

  • 1997年,IBM提出了EJB的思想
  • 1998年,SUN制定开发标准规范EJB1.0
  • 1999年,EJB1.1发布
    2001年,EJB2.0发布
    2003年,EJB2.1发布
    2006年,EJB3.0发布
  • Rod Johnson(spring之父)
    • Expert One-to-One J2EE Design and Development(2002)
      阐述了J2EE使用EJB开发设计的优点及解决方案
    • Expert One-to-One J2EE Development without EJB(2004)
      阐述了J2EE开发不使用EJB的解决方式(Spring雏形)
  • 2017年9月份发布了spring的最新版本spring 5.0通用版(GA)

3. Spring的优势

  • 方便解耦,简化开发(IoC思想,第1、2天)

    通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 创建对象更简单!

  • AOP编程的支持(第3天)

    通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。

    比如:要求面面项目里,每个方法被调用时,都输出日志到控制台“2020-03-20 11:20:31执行了xxx.xx方法”

  • 声明式事务的支持(第3天) 在xml里面配置事务,在方法上或者类上 打一个注解即可。

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。@Transactional

  • 方便程序的测试(第1天)

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

    例如:Spring整合了Junit

  • 方便集成各种优秀框架(SSM整合,SpringMVC第2天)

    Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。

  • 降低JavaEE API的使用难度(第3天)

    Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。

  • Java源码是经典学习范例

    Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。

4. Spring的体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sqpRMVSx-1641816414121)(img/spring-overview.png)]

二、工厂模式解耦(理解)

1. 耦合性问题

  • 耦合性:程序之间(代码间)的依赖性。代码与代码之间的联系。

    • 编译期依赖:**编译时必须提供依赖的类,否则编译不通过。 **
      • UserService us = new UserService();
      • UserService us02 = new UserServiceImpl();
    • 运行期依赖:运行时必须提供依赖的类,否则不能运行。
      • 接口和实现的写法:

        UserService us02 = Class.forName(“com.itheima.service.impl.UserServiceImpl”).newInstance();

    • 应当减少编译期依赖,使用运行期依赖
  • 耦合性越强,维护成本(时间成本&精力成本)就越高

  • 开发时要求:高内聚,低耦合

    • 低耦合 : 耦合度很低,代码与代码之间耦合度很低。

    • 高内聚: 把具有一样功能的代码,尽可能靠拢起来。 一个业务有很多的方法,这些方法要尽可能靠在一块。方便管理,维护。

      类与类之间的内聚 模块化servlet

      • 注册 ---- RegisterServlet
      • 登录 — LoginServlet
      • 更新用户 — UpdateUserServlet
      • 用户 ----- UserServlet

      方法与方法之间的内聚

      ​ 每个方法里面都有乱码解决…两句话 ---- 过滤器。。

      ​ req.setCharacterEncoding();

      ​ resp.setContentType();

1.1 耦合性问题现象
  • 在web开发中,服务端通常分为三层:web层、service层、dao层
    • web层调用service层完成功能:需要new一个Service对象
      • 以前的写法,直接new对象
        • UserService userService = new UserService();
      • 真正开发的时候是面向接口编程。
        • UserService userService = new UserServiceImpl();
    • service层调用dao层操作数据库:需要new一个dao对象
      • 以前的写法, 直接new对象
        • UserDao userDao = new UserDao();
      • 真正开发的时候是面向接口编程。
        • UerDao userDao = new UserDaoImpl();
  • 三层之间的耦合性比较强:存在编译期依赖
    • service里写死了创建某一个dao对象:一旦dao对象换了,就需要修改service的源码
    • web里写死了创建某一个service对象:一旦service对象换了,就需要修改web的源码
1.2 解耦的思路
  • 可以使用反射技术,代替new创建对象,避免编译期依赖

    bject obj = Class.forName("全限定类名").newInstance();
    
  • 再把全限定类名提取到配置文件中(xml或properties),读取配置文件信息来反射创建对象

    //读取配置文件,得到要创建对象的全限定类名;再通过反射技术创建对象
    String className = ...;
    Class clazz = Class.forName(className);
    Object obj = clazz.newInstance();
    

2. 使用工厂模式解耦

需求描述
  • UserService调用UserDao,在每一层的方法论里面都打印一句日志即可
  • 使用工厂模式+配置文件的方式,降低它们之间的耦合性
需求分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QmAWAAhu-1641816414123)(img/1563205038328.png)]

需求实现
  1. 创建项目,导入依赖

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    
  2. dao层代码

    • 接口
    package com.itheima.dao;
    
    public interface UserDao {
        void add();
    }
    
    
    • 实现类
    package com.itheima.dao.impl;
    
    import com.itheima.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        public void add() {
            System.out.println("调用了UserDaoImpl的add方法~!");
        }
    }
    
    
  3. service层代码

    • 接口
    package com.itheima.service;
    
    public interface UserService {
        void add() throws Exception;
    }
    
    
    • 实现类
    package com.itheima.service.impl;
    
    import com.itheima.dao.UserDao;
    import com.itheima.dao.impl.UserDaoImpl;
    import com.itheima.factory.BeanFactory;
    import com.itheima.service.UserService;
    
    public class UserServiceImpl implements UserService {
        public void add() throws Exception {
            System.out.println("调用了UserServiceImpl的add方法~!");
    
            //以前的写法:
            /*UserDao userDao = new UserDao();
            UserDao userDao = new UserDaoImpl();
            userDao.add();*/
    
            //现在的写法:
            UserDao userDao = (UserDao) BeanFactory.getBean("ud");
            userDao.add();
        }
    }
    
    
  4. 配置文件beans.properties

在resource下面创建 beans.properties

us=com.itheima.service.impl.UserServiceImpl
ud=com.itheima.dao.impl.UserDaoImpl
  1. 工厂类BeanFactory
package com.itheima.factory;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

/*
    这是专门用来创建对象的工厂类
        1. 使用静态代码块来读取beans.properties

        2. 提供一个静态方法,供外面的人调用获取对象
 */
public class BeanFactory {
    static Map<String , String> map = new HashMap<String , String>();

    //1. 在这里读取beans.properties文件
    static{

        //1. 读取外部的properties文件,只要写名字即可
        ResourceBundle resourceBundle = ResourceBundle.getBundle("beans");

        //2. 读取里面的内容
        Enumeration<String> keys = resourceBundle.getKeys();

        //3. 遍历每一个key value
        while(keys.hasMoreElements()){

            //4. 取出每一个key
            String key = keys.nextElement();

            //5. 得到每一个value
            String value = resourceBundle.getString(key);

            //6. 把key和value保存到map集合
            map.put(key , value );
        }

    }

    /**
     * 提供对象
     * @param name 名称
     * @return
     */
    public static Object getBean(String name) throws Exception {

        //1. 从map集合里面获取全路径
        String className = map.get(name);

        //2. 判定
        if(className != null){
            return Class.forName(className).newInstance();
        }

        return null;
    }
}

  • 测试
package com.itheima.test;

import com.itheima.factory.BeanFactory;
import com.itheima.service.UserService;
import org.junit.Test;

public class TestUserServiceImpl {

    @Test
    public void testAdd() throws Exception {

        //1. 问工厂要对象
       UserService us = (UserService) BeanFactory.getBean("us");
       us.add();
    }
}
小结
  1. 首先得有接口和实现类 : UserDao 和 UserDaoImpl , UserService 和 UserServiceImpl
  2. 使用properties配置文件来记录 ,别名和实现类的全路径
  3. 定义一个工厂类
    1. 在静态代码块里面读取配置文件,使用map集合来存映射关系
    2. 定义一个静态方法,只要有人来获取实例,那么就从map集合里面取出来全路径
    3. 使用反射技术来构建实例返回。

三、控制反转IOC【重点】

  • 什么是IOC inversion of control

控制反转,把对象的创建工作交给框架(工厂 Spring),我们不需要自己去new这个对象,只管问工厂要。由原来的主动创建对象,变成自己被动接收 框架创建的对象。

  • IOC的作用

IOC是Spring的核心之一,作用就是为了解耦,降低程序,代码间的耦合度。

1. 快速入门【重点】

需求描述
  • UserDao接口和UserDaoImpl实现类
  • 通过Spring容器(工厂)得到UserDaoImpl的实例对象(IoC方式)
开发步骤
  1. 创建Maven项目,导入依赖坐标:Spring的依赖坐标
  2. 编写dao接口UserDao及实现UserDaoImpl
  3. 创建Spring核心配置文件,并配置UserDaoImpl (作用类似bean.properties)
  4. 测试:使用Spring的API,获取Bean实例对象
需求实现
1. 创建Maven项目,导入依赖坐标
 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
2. 编写dao接口UserDao及实现UserDaoImpl
  • 接口UserDao
package com.itheima.dao;

public interface UserDao {
    void add();
}

  • 实现类UserDaoImpl
package com.itheima.dao.impl;

import com.itheima.dao.UserDao;

public class UserDaoImpl implements UserDao {
    
   
    public  void add() {
        System.out.println("调用了UserDaoImpl的add方法~!~");
    }
}
3. 创建Spring核心配置文件,并配置UserDaoImpl

这个步骤的作用就是告诉spring,要创建哪个类的对象,并且给这个类起一个别名,方便以后我们问spring要对象。它的作用等于我们前面写的beans.properties

  • 配置文件名称,通常叫applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名

        bean标签:
            作用: 用来托管(类)对象
            属性:
                id: 唯一标识,不能出现重复!
                class: 托管类的全路径
     -->
    <bean id="ud"  class="com.itheima.dao.impl.UserDaoImpl" />
</beans>
4. 使用Spring的API,获取Bean实例对象
  • 编写测试类
package com.itheima.test;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserDao {


    @Test
    public void testAdd(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserDao userDao = (UserDao) context.getBean("ud");

        //3. 调用方法
        userDao.add();
    }

}

小结
  1. 首先编写UserDao 和 UserDaoImpl
  2. 在pom.xml里面添加依赖
  3. 在resources下面,创建一个xml文件,名字随意。不要手动创建文件的方式。要选择xml配置文件的方式
  4. 在xml文件里面登记|注册|托管实现类
  5. 问工厂要实例

2. 配置文件详解【了解】

1. bean标签的基本配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
  1. 介绍

    • 用于配置:把对象交给Spring进行控制 , spring会帮助我们创建对象。

    • 默认情况下,Spring是调用类的无参构造来创建对象的;如果没有无参构造,则不能创建成功

  2. 基本属性

    • id:唯一标识
    • class:bean的全限定类名

    了解:bean的id和name的区别

    1. 一个bean只能有一个id;一个bean可以有多个name
    2. bean的name值:多个name之间以, ; 空格 隔开,第1个name作为id,其它作为别名
2. bean标签的作用范围配置
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"></bean>
  • scope属性取值如下:
取值说明
singleton默认,表示单例的,一个Spring容器里,只有一个该bean对象
prototype多例的,一个Spring容器里,有多个该bean对象
requestweb项目里,Spring创建的bean对象将放到request域中:一次请求期间有效
sessionweb项目里,Spring创建的bean对象将放到session域中:一次会话期间有效
globalSessionweb项目里,应用在Portlet环境/集群环境;如果没有Portlet/集群环境,那么globalSession相当于session(新版本中已删除)
  • 不同scope的bean,生命周期:

    • singleton:bean的生命周期和Spring容器的生命周期相同

      • 整个Spring容器中,只有一个bean对象
      • 何时创建:加载Spring配置文件,初始化Spring容器时,bean对象创建
      • 何时销毁:Spring容器销毁时,bean对象销毁
    • prototype:bean的生命周期和Spring容器无关。Spring创建bean对象之后,交给JVM管理了

      • 整个Spring容器中,会创建多个bean对象,创建之后由JVM管理
      • 何时创建:调用getBean方法获取bean对象时,bean对象创建
      • 何时销毁:对象长时间不用时,垃圾回收
3. bean生命周期相关方法的配置【了解】
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" 
      init-method="" destroy-method=""></bean>
  • init-method:指定类中初始化方法名称,该方法将在bean对象被创建时执行

  • destroy-method:指定类中销毁方法名称,该方法将在bean对象被销毁时执行

    注意:

    • prototype类型的bean:Spring容器销毁时,也不会执行销毁方法,因为Spring不负责它的销毁
    • singleton类型的bean:在Spring容器显式关闭时,会执行destroy-method指定的方法
  • dao

package com.itheima.dao.impl;

import com.itheima.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public UserDaoImpl(){
        System.out.println("创建对象了额~!!~");
    }

    public void add() {
        System.out.println("调用了UserDaoImpl的add方法~!");
    }

    //创建好对象的时候调用这个方法
    public  void init(){
        System.out.println("调用了UserDaoImpl的init方法~!");

    }

    //销毁对象的时候调用这个方法
    public  void destroy(){
        System.out.println("调用了UserDaoImpl的destroy方法~!");

    }
}

  • 单元测试
package com.itheima.test;

import com.itheima.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserDaoImpl {

    //测试单例多例
    @Test
    public void testAdd2(){

        //1. 创建工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserDao userDao = (UserDao) context.getBean("ud");

        //3. 关闭工厂!
        context.close();
    }
}
  • xml配置
    <!--
        在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名

        bean标签:
            作用: 用来托管(类)对象
            属性:
                id: 唯一标识,不能出现重复!
                class: 托管类的全路径
                name : 用于给对象起别名,标识以后可以用这个别名来找到对象
                     可以写多个值,使用 分号 , 逗号, 空格 ,tab 来间隔!【一般不用它!】

                scope: 用来设置创建的对象是单例的还是多例
                    singleton: 单例,默认是单例! 【大多数情况下,使用的都是单例对象】
                        何时创建:创建工厂的时候创建了对象
                        何时销毁:销毁工厂的时候,销毁对象
                    prototype: 多例
                        何时创建:问工厂要对象的时候,才创建对象
                        何时销毁:长时间不用之后,就由GC回收对象!
                init-method:
                    当对象初始化的时候,调用这个方法
                destroy-method:
                    当对象销毁的时候,调用这个方法
            spring创建类的对象,默认会执行该类的无参构造方法!

     -->
    <bean id="ud"  class="com.itheima.dao.impl.UserDaoImpl" scope="prototype" init-method="init" destroy-method="destroy"/>


4. bean实例化的三种方式【了解】

我们通常都是问Spring要对象,那么Spring怎么整出来对象的呢?有三种方式。算起来就只有两种办法创建对象:

  1. 由Spring来创建对象
  2. 由我们自己来创建对象,然后spring来拿我们的对象给需要的人。
  • 无参构造方法实例化,默认的:让Spring调用bean的无参构造,生成bean实例对象给我们 【由Spring创建】
  • 工厂静态方法实例化:让Spring调用一个我们自己写好的工厂类的静态方法,得到一个bean实例对象 【由咱们自己创建】
  • 工厂非静态方法实例化(实例化方法):让Spring调用一个工厂对象的非静态方法,得到一个bean实例对象 【由咱们自己创建】
1. 无参构造方法实例化 【spring创建对象】

UserDaoImpl 是由Spring创建的。

<bean id="ud" class="com.itheima.dao.impl.UserDaoImpl"></bean>
2. 工厂静态方法实例化

UserDaoImpl的由我们写好的StaticFactory的类来创建 , Spring工厂没干活,只是问我们的工厂要对象而已。

  • 工厂类如下:com.itheima.factory.StaticFactory
package com.itheima.factory;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;

/*
    使用工厂的静态方法来创建对象
 */
public class StaticFactory {

    /**
     * 创建UserDaoImpl的对象
     * @return
     */
    public static UserDao getBean(){
        System.out.println("来问StaticFactory要对象了~");
        return new UserDaoImpl();
    }
}

  • 配置如下:
    <!--
        使用工厂的静态方法来创建对象
            1. spring工厂并不会去创建UserDaoImpl的对象。
            2. 当有方法拿着ud02来问spring的工厂要对象的时候,spring的工厂会
                找StaticFactory的getBean方法得到对象
            3. 然后把这个对象返回给我们的方法
     -->
   <bean id="ud02" class="com.itheima.factory.StaticFactory" factory-method="getBean"/>

3. 工厂非静态方法实例化

UserDaoImpl的由我们写好的InstanceFactory的类来创建 ,, Spring工厂没干活,只是问我们的工厂要对象而已。

  • 工厂类如下:com.itheima.factory.InstanceFactory
package com.itheima.factory;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;

public class InstanceFactory {
    /**
     * 创建UserDaoImpl的对象
     * @return
     */
    public  UserDao getBean(){
        System.out.println("来问InstanceFactory要对象了~");
        return new UserDaoImpl();
    }

}

  • 配置如下:
    <!--
        使用工厂的非静态方法来创建对象:
            1. spring的工厂并不会去创建UserDaoImpl的对象。
            2. 它创建了我们工厂的对象
            3. 当有方法拿着ud03来问spring的工厂要对象的时候,spring会拿着instanceFactory去找到工厂对象
                (这个工厂对象是由spring创建出来的),然后调用getBean方法,返回我们创建好的UserDaoImpl对象
      -->
    <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"/>
    <bean id="ud03" factory-bean="instanceFactory" factory-method="getBean"/>

4 实现FactoryBean<T>方式【扩展,了解】
  • 定义UserDaoFactoryBean实现FactoryBean<UserDao>

UserDaoFactoryBean中实例化什么类型的对象泛型就是该类型。

//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }
}
  • applicationContext.xml配置
<!--方式四:使用FactoryBean实例化bean-->
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

注意配置文件中id="userDao"是否重复。

  • 小结
  1. Spring工厂创建实例有三种方式: 默认的无参构造方式 | 静态工厂方式 | 实例工厂方式

  2. 只有无参构造的那种方式是spring创建对象,其他两种都是由我们自己来创建对象

  3. 我们使用spring的IOC ,目的就是为了把对象的创建工作交给Spring,后面这种工厂的方式,反而是我们来创建对象,所以一般不用这两种。

  4. 既然如此,都不怎么用后面得的两种方式了,为什么spring还要提供这两种入口呢?

    这个其实就是为了兼容, 就是为了兼容以前的旧项目,有的旧项目50年前的旧项目,那个没有spring,但是那个时候已经使用了工厂来创建实例了。

3. IOC小结

  1. IOC是什么? 控制反转,把对象的创建工作交给spring的工厂完成,只管问spring要对象即可
  2. 在applicationContext.xml里面注册。
  3. 默认创建的实例是单例,如果想要多例,需要配合scope属性,设置成prototype

四、依赖注入DI【重点】

  • 什么是依赖注入呢?

托管类里面有什么属性需要完成赋值工作,把这个赋值的工作交给spring来做。由spring把属性需要用到的值赋值(注入)进来就称之为依赖注入。

我们通过Ioc把bean对象交给了Spring容器进行管理,降低了耦合性。

但是耦合性不能彻底消除,bean之间还是有一些依赖关系。比如:业务层userService要依赖于持久层userDao。

这样的依赖关系,可以交给Spring帮我们进行依赖的注入,而不用我们自己注入依赖

1. 快速入门 【重点】

需求描述
  • 有dao层:UserDaoUserDaoImpl
  • 有service层:UserServiceUserServiceImpl
  • UserServiceImpl中的方法依赖于UserDaoImpl
  • 使用Spring,把UserDaoImpl注入给UserServiceImpl
开发步骤
  1. 创建Maven项目,导入依赖坐标
  2. 编写dao层UserDaoUserDaoImpl、service层UserServiceUserServiceImpl
  3. 创建Spring核心配置文件,并配置bean和依赖注入
  4. 使用Spring的API,测试
需求实现
1. 创建Maven项目,导入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
2. 编写dao层和service层代码
  • dao层接口UserDao
package com.itheima.dao;

public interface UserDao {
    void add();
}

  • dao层实现类UserDaoImpl
package com.itheima.dao.impl;

import com.itheima.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void add() {
        System.out.println("调用了UserDaoImpl的add方法~!");
    }
}

  • service层接口UserService
package com.itheima.service;

public interface UserService {

    void add();
}
  • service层实现类UserServiceImpl
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;


/*
    需求: service里面的add方法要调用dao的add方法
    分析:
        1. 要想调用dao的add方法,必须持有UserDaoImpl的对象。
        2. 要想拥有UserDaoImpl的对象有两种办法:
            2.1 自己创建 ,自己new   【以前的做法】
                UserDao userDao  = new UserDaoImpl();
                userDao.add();
            2.2 让spring把userDaoImpl的对象给注入进来,注入给UserServiceImpl!【采用这种方式】
     步骤:
        1. 在UserServiceImpl里面定义属性 :  private UserDao userDao;
        2. 提供这个属性的set方法!
        3. 把UserServiceImpl和UserDaoImpl这两个类都交给spring托管。
        4. 在xml里面配置,告诉spring要把UserDao的对象注入到UserServiceImpl里面的这个属性 userDao身上!

 */

public class UserServiceImpl implements UserService {

    //1. 定义属性
    private UserDao userDao;

    //2. 提供set方法
    public void setUserDao(UserDao userDao) {
        System.out.println("来调用Set方法了·~");
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl的add方法~!");
        userDao.add();
    }
}

3. 创建Spring核心配置文件,并配置bean和依赖注入
<?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">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <!--2. 把UserServiceImpl交给spring管理-->
    <bean id="us" class="com.itheima.service.impl.UserServiceImpl">
        <!--告诉spring,把id名字叫做ud的对象,赋值给UserServiceImpl里面的userDao属性-->
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>
4. 使用Spring的API,测试
package com.itheima.test;

import com.itheima.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserServiceImpl {

    @Test
    public void testAdd(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserService us = (UserService) context.getBean("us");

        //3. 调用方法
        us.add();

    }

}

小结
  1. 有接口,有实现类

    1. 给属性提供set方法
    2. 在托管|登记类的时候,要添加property标签。如果注入的是对象,那么要使用ref属性,如果注入的是普通的数据,那么要使用value属性。

2. 三种常见注入方式

1. set方法注入(最常用)
1) 介绍

在类中提供需要注入的成员(依赖项)的set方法,在配置文件中注入属性的值

<bean id="" class="">
	<property name="属性名" value="属性值"></property>
    <property name="属性名" ref="bean的id"></property>
</bean>
  • property标签:用在bean标签内部,表示要给某一属性注入数据
    • name:属性名称
    • value:要注入的属性值,注入简单类型值
    • ref:要注入的属性值,注入其它bean对象
2) 示例
  • service
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;


/*
    三种注入方式之一:  set方法
    要求:一定要提供属性的set方法!

 */

public class UserServiceImpl01 implements UserService {

    private String address;
    private UserDao userDao;

    public void setAddress(String address) {
        this.address = address;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl01的add方法~!"+address);
        userDao.add();
    }
}


  • 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl01交给spring管理
            2.1 采用set方法完成属性的注入工作。
                property :  用来告诉spring,有哪些属性要赋值,代码里面一定要提供set方法
                    name :属性名
                    ref : 注入值,针对的是对象类型属性
                    value : 注入值,针对的是普通的属性(基本数据类型 &  字符串)
     -->
    <bean id="us" class="com.itheima.service.impl.UserServiceImpl01">
        <property name="userDao" ref="userDao"/>
        <property name="address" value="深圳"/>
    </bean>

</beans>
2. 构造方法注入
1) 介绍

在类中提供构造方法,构造方法的每个参数就是一个依赖项,通过构造方法给依赖项注入值。

<bean id="" class="">
	<constructor-arg name="构造参数名称" value="构造参数的值"></constructor-arg>
    <constructor-arg name="构造参数名称" ref="bean的id"></constructor-arg>
</bean>
  • name:构造参数的名称
  • type:构造参数的类型
  • index:构造参数的索引
  • value:要注入的值,注入简单类型值
  • ref:要注入的值,注入其它bean对象
2) 示例
  • service
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;


/*
    三种注入方式之一:  有参构造
    要求:一定要提供有参构造方法!

 */

public class UserServiceImpl02 implements UserService {

    private String address;
    private UserDao userDao;

    public UserServiceImpl02(String address, UserDao userDao) {
        this.address = address;
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl02的add方法~!"+address);
        userDao.add();
    }
}

  • 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl02交给spring管理
            constructor-arg : 用于匹配有参构造函数,
                name: 参数名
                value : 给参数赋值,针对的是普通的参数(基本类型 &  字符串)
                ref : 给参数赋值, 针对的是对象的参数
     -->
    <bean id="us" class="com.itheima.service.impl.UserServiceImpl02">
        <constructor-arg name="address" value="深圳" />
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

</beans>
3. p名称空间注入
1) 介绍

p名称空间注入,本质仍然是set方法注入

在xml中引入p名称空间的约束

然后通过p:属性名称=""来注入简单数据、使用p:属性名称-ref=""注入其它bean对象,它的本质仍然是set方法注入

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="" class="" p:属性名="简单值" p:属性名-ref="bean的id"></bean>
    
</beans>
2) 示例
  • service
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;


/*
    三种注入方式之一:  p名称空间
    要求:一定要提供属性的set方法!

 */

public class UserServiceImpl03 implements UserService {

    private String address;
    private UserDao userDao;

    public void setAddress(String address) {
        this.address = address;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl03的add方法~!"+address);
        userDao.add();
    }
}

  • 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl03交给spring管理

     -->
    <bean id="us" class="com.itheima.service.impl.UserServiceImpl03" p:address="中粮商务公园" p:userDao-ref="userDao"/>

</beans>
4. 依赖注入方式选择
  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入
小结
  1. 注入方式有三种,setter注入 ,构造方法注入,p名称空间
  2. 最常用的是setter注入。
  3. 以后如果使用注解了,方法也不需要写了。

3.依赖自动装配【理解】

问题导入

如何配置按照类型自动装配?

2.1 自动装配概念
  • IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
  • 自动装配方式
    按类型(常用)
    按名称
    按构造方法
    不启用自动装配
2.2 自动装配类型
依赖自动装配

配置中使用bean标签autowire属性设置自动装配的类型

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
依赖自动装配特征
  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

4. 注入集合数据

介绍
  • 前边我们介绍了如何注入简单数据类型和bean对象,但是在实际开发中,可能会需要给集合属性注入数据,比如:给数组、List、Set、Map等注入数据
示例
代码
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;

import java.util.*;


/*
    注入集合数据: 数组、list、set、map、properties

 */

public class UserServiceImpl04 implements UserService {

    private String [] array;
    private List<String> list;
    private Set<String> set;
    private Map<String , String> map;
    private Properties properties;

    public void setArray(String[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl04的add方法~!");
        System.out.println("array="+Arrays.toString(array));
        System.out.println("list = " + list);
        System.out.println("set = " + set);
        System.out.println("map = " + map);
        System.out.println("properties = " + properties);
    }
}

配置注入数据
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl04交给spring管理
     -->
    <bean id="userviceImpl" class="com.itheima.service.impl.UserServiceImpl"/>

    <bean id="us" class="com.itheima.service.impl.UserServiceImpl04">

        <!--1. 数组-->
        <property name="array">
            <array>
                <value>array01</value>
                <value>array02</value>
                <value>array03</value>
            </array>
        </property>

        <!--2. list-->
        <property name="list">
            <list>
                <value>list01</value>
                <value>list02</value>
                <value>list03</value>
            </list>
        </property>

        <!--3. set-->
        <property name="set">
            <set>
                <value>set01</value>
                <value>set02</value>
                <value>set03</value>
            </set>
        </property>

        <!--4. map-->
        <property name="map">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
                <entry key="key3" value="value3"/>
            </map>
        </property>

        <!--5. properties-->
        <property name="properties">
            <props>
                <prop key="username">张三</prop>
                <prop key="password">123456</prop>
            </props>
        </property>

    </bean>

</beans>

所有单列结构的数据集合,标签可以互换使用。例如:List、Set、数组等

所有键值对结构的数据集合,标签可以互换使用。例如:Map、Properties等

小结
  1. 所有的DI数据类型里面,最常用的是对象数据。
  2. 最常用的方式 set方法。
  3. 数组 、 list 、set写法基本一样, map 和 properties基本一样。

5.引入properties文件

如果需要在applicationContext.xml中引入properties文件:

  • 准备一个properties文件放在resources目录里:db.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://localhost:3306/spring01
db.user=root
db.password=root
  • applicationContext.xml中引入并使用db.properties
    • Spring的名称空间(建议使用idea自动生成的,如果idea抽风了,就自己手写)
<beans
       xmlns:名称空间="http://www.springframework.org/schema/名称空间"
       xsi:scehmaLocation="
          http://www.springframework.org/schema/名称空间
         http://www.springframework.org/schema/名称空间/spring-名称空间.xsd">
</beans>
  • 使用context名称空间提供的标签,引入外部的properties文件

context的标签,硬着头皮写出来就可以了,不要害怕!

   <!--导入进来db.properties-->
    <context:property-placeholder location="db.properties"/>

    <!--在下面使用 ${}来取值-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${jdbcUrl}"/>
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
    </bean>

6.分模块提供配置文件

在大型项目开发中,如果把所有的配置都写在一个配置文件applicationContext.xml中,会导致:

  • 配置文件过于臃肿
  • 不利于分模块开发,不利于模块之间的解耦

Spring提供了分模块配置的方式,即:每个模块|层提供一个配置文件,在核心配置文件中引入模块配置:

  • dao模块有一个配置文件:applicationContext-dao.xml 只配置dao相关的对象
  • service模块有一个配置文件:applicationContext-service.xml 只配置service相关的对象
  • 有一个总的核心配置文件:applicationContext-all.xml如下
<import resource="classpath:applicationContext-service.xml"/>
<import resource="classpath:applicationContext-dao.xml"/>

五、相关API介绍(了解)

1. ApplicationContext的继承体系

  • ApplicationContext:接口,代表应用上下文,可以通过其实例对象获取Spring容器中的bean对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7TQ0Ihz-1641816414125)(img/1563065014422.png)]

2. ApplicationContext

2.1 XmlBeanFactory 和 ApplicationContext的区别
  • ApplicationContext 是现在使用的工厂

    ApplicationContext context = 
        new ClassPathXmlApplicationContext("applicationContext.xml");
    
  • XmlBeanFactory 是老版本使用的工厂,目前已经被废弃【了解】

    BeanFactory beanFactory = 
        new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    
  • 两者的区别:

    • ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面

    • XmlBeanFactory加载方式是用到bean时再加载(目前已经被废弃)

2.2 ApplicationContext的实现类
2.2.1 ClassPathXmlApplicationContext
  • 是从类加载路径里,加载xml配置文件的
  • 什么是类加载路径:代码编译之后的那个classes文件夹,
    • 开发中可以认为Maven项目的:Java文件夹、resources文件夹,都是类加载路径
2.2.2 FileSystemXmlApplicationContext
  • 从磁盘路径里,加载xml配置文件的
2.2.3 AnnotationConfigApplicationContext
  • 用注解配置Spring时,通过此类加载配置类创建Spring容器,它用于读取类上的注解配置
2.3 getBean()方法
  • ApplicationContext提供了多种getBean方法的重载,常用的如下:
方法参数返回值
getBean(String beanId)bean的idObject,bean对象
getBean(String beanId,Class beanType)bean的Class类型bean对象
getBean(Class beanType)bean对象
getBeanDefinitionNamesString[] 获取工厂管理的对象的名字
   package com.itheima.test;

import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl04;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestApplicationContext {

    @Test
    public void testGetBean(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");

        //2. 问工厂要对象=getBean()

       /**/ //1. 按照id来找对象,没有问题的!
        UserService us = (UserService)context.getBean("us");
        us.add();

        //2. 按照真实的自己的类型来找对象!
        UserService us2 = context.getBean(UserServiceImpl04.class);
        us2.add();

        //3. 按照接口的类型来找对象!
        UserService us3 = context.getBean(UserService.class);
        us3.add();

    }

    // 得到所有对象的id值
    @Test
    public void testGetBeanNames(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");


        //2. 获取spring工厂里面的所有对象的id值。
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println("name = " + name);
        }

    }

}

总结

IOC:

1. IOC是什么?
	控制反转,让spring帮助我们创建对象,我们只要问spring要对象即可
2. IOC怎么用?
	2.1 导入spring的依赖
	2.2 创建接口和实现类
	2.3 在resource下创建一个xml文件
		<bean id="us" class="类的全路径"/>
	2.4 单元测试里面
		a. 创建工厂

		b. 问工厂要对象

		c. 调用方法即可!

3. 细节:
	3.1 默认情况下,spring创建的对象是单例的!如果想做成多例的对象,需要搭配
		属性scope="prototype"

	3.2 默认情况下,spring创建类的对象,会执行该类的无参构造方法!

	3.3 spring创建单例对象是在工厂创建的时候,就创建对象了。但是如果是多例对象
		是需要对象的时候,才会创建。

DI:

1. 什么是DI?
	DI 就是依赖注入,让spring给托管类的属性,赋值!
2. 怎么用?

	2.1 定义属性
	2.2 提供属性的set方法
	2.3 在xml里面配置<property>标签

	<bean id="us" class="com.itheima.service.impl.UserServiceImpl">
		<property name="userDao" ref="ud"/>
		<property name="address" value="深圳"/>
	</bean>
3. 常用的方式:
	set方法
	有参构造
	p名称空间

4. 注入数据类型:
	简单数据类型
	对象数据类型
	集合数据类型
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值