SSM学习笔记(Spring篇 Day01)

一、Spring相关概念

1. Spring4 架构图
(1) 核心层
  • Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
(2) AOP层
  • AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强

  • Aspects:AOP是思想,Aspects是对AOP思想的具体实现

(3) 数据层
  • Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术

  • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis

  • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容

(4) Web层
  • 这一层的内容将在SpringMVC框架具体学习
(5) Test层
  • Spring主要整合了Junit来完成单元测试和集成测试
2. IoC、IoC容器、Bean、DI
(1) 什么是控制反转?

使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。

(2) Spring和IoC之间的关系是什么呢?
  • Spring技术对IoC思想进行了实现

  • Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的"外部"

(3) IoC容器的作用以及内部存放的是什么?
  • IoC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象

  • 被创建或被管理的对象在IoC容器中统称为Bean

  • IoC容器中放的就是一个个的Bean对象

(4) DI(Dependency Injection)依赖注入

在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

二、入门案例

1. IoC入门案例
(1) 关键步骤
① 在配置文件中完成bean的配置
XML 复制代码
    <!--bean标签标示配置bean
    	id属性标示给bean起名字
    	class属性表示给bean定义类型
	-->
	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>

</beans>

核心逻辑:

① 为什么 class 类型指定的是实现类,不是接口?

直接原因:Spring IOC 容器的核心是创建对象实例 ,如果你把 class 指定为接口(比如com.itheima.dao.BookDao),Spring 容器在尝试创建对象时,会和上面的 Java 代码一样失败 ------ 因为它无法实例化一个接口,最终会抛出类似Cannot instantiate interface com.itheima.dao.BookDao的异常。

② 从容器中获取对象进行方法调用
java 复制代码
public class App {
    public static void main(String[] args) {
        //获取IoC容器
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
//        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
//        bookDao.save();
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}
2. DI入门实例
(1) 关键步骤
① 修改配置完成注入
XML 复制代码
    <!--bean标签标示配置bean
    	id属性标示给bean起名字
    	class属性表示给bean定义类型
	-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <!--配置server与dao的关系-->
        <!--property标签表示配置当前bean的属性
        		name属性表示配置哪一个具体的属性
        		ref属性表示参照哪一个bean
		-->
        <property name="bookDao" ref="bookDao"/>
    </bean>

</beans>

注意:配置中的两个bookDao的含义是不一样的

1). name="bookDao"中bookDao的作用是让Spring的IoC容器在获取到名称后,将首字母小写,前面加set找对应的setBookDao()方法进行对象注入

对应 BookServiceImpl 的代码:

java 复制代码
public class BookServiceImpl implements BookService {
    // 👇 这里的变量名是 bookDao,所以 name 必须写 "bookDao"
    private BookDao bookDao;

    // 👇 setter方法名是 setBookDao,Spring 会自动匹配 "bookDao"(去掉set,首字母小写)
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
  • 如果把类里的变量名改成 dao,setter 改成 setDao,那配置里的 name 必须改成 "dao",否则 Spring 找不到要赋值的属性,会报错;
  • 比如:private BookDao dao; + public void setDao(BookDao dao) → 配置要写成 <property name="dao" ref="bookDao"/>

2). ref="bookDao"中bookDao的作用是让Spring能在IoC容器中找到id为bookDao的Bean对象给bookService进行注入

四、IoC相关内容

1. bean基础配置
(1) bean基础配置(id与class)
(2) bean的name属性
① 代码实现

步骤1:配置别名

打开spring的配置文件applicationContext.xml

XML 复制代码
    <!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
    <bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>

    <!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

步骤2:根据名称容器中获取bean对象

java 复制代码
public class AppForName {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
        BookService bookService = (BookService) ctx.getBean("service4");
        bookService.save();
    }
}
(3) bean作用范围scope配置
① 代码实现

1). 单例

XML 复制代码
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="singleton"/>

2). 非单例

XML 复制代码
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
2. bean实例化
(1) 构造方法实例化

① 编写运行程序

java 复制代码
public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();

    }
}

② BookDaoImpl

在BookDaoImpl类中添加一个无参构造函数,并打印一句话,方便观察结果。

java 复制代码
public class BookDaoImpl implements BookDao {
    public BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }

}

无参构造方法是在执行 new ClassPathXmlApplicationContext("applicationContext.xml") 这行代码时被 Spring 调用的

第一步:执行new ClassPathXmlApplicationContext(...)时,Spring 会做这些事:

  • 解析applicationContext.xml,找到<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>这个配置;
  • 通过反射获取BookDaoImpl的 Class 对象;
  • 调用BookDaoImpl无参构造方法创建实例(这就是 "构造方法实例化" 的核心步骤);
  • 把创建好的实例存入容器,等待后续获取。✅ 这一步,你写在BookDaoImpl无参构造里的System.out.println("book dao constructor is running ....")就会执行 ------ 控制台打印这句话,就是无参构造被调用的铁证。

第二步:执行ctx.getBean("bookDao")

Spring 从自己的 "对象仓库" 里,找到id=bookDao的对象(就是刚才创建好的BookDaoImpl实例),把它返回给你。

第三步:执行bookDao.save()

调用这个对象的save()方法,控制台打印:book dao save ...

(2) 静态工厂实例化(了解即可)

① 准备一个OrderDao和OrderDaoImpl类

java 复制代码
public interface OrderDao {
    public void save();
}

public class OrderDaoImpl implements OrderDao {
    public void save() {
        System.out.println("order dao save ...");
    }
}

② 创建一个工厂类OrderDaoFactory并提供一个静态方法

java 复制代码
//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

③ 编写AppForInstanceOrder运行类,在类中通过工厂获取对象

java 复制代码
public class AppForInstanceOrder {
    public static void main(String[] args) {
        //通过静态工厂创建对象
        OrderDao orderDao = OrderDaoFactory.getOrderDao();
        orderDao.save();
    }
}

④ 在spring的配置文件中添加以下内容

XML 复制代码
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

1). 关键部分解析:

**class:**工厂类的类全名

**factory-mehod:**具体工厂类中创建对象的方法名

2). 对应关系如下图:

⑤ 在AppForInstanceOrder运行类,使用从IoC容器中获取bean的方法进行运行测试

java 复制代码
public class AppForInstanceOrder {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");

        orderDao.save();

    }
}
  • Spring 加载OrderDaoFactory类(无需创建工厂类实例,因为方法是静态的);
  • 调用OrderDaoFactory.getOrderDao()静态方法;
  • 将方法返回的OrderDaoImpl实例存入容器,标识为orderDao
  • 后续ctx.getBean("orderDao")获取的就是这个实例。
(3) 实例工厂与FactoryBean

① 准备一个UserDao和UserDaoImpl类

java 复制代码
public interface UserDao {
    public void save();
}

public class UserDaoImpl implements UserDao {

    public void save() {
        System.out.println("user dao save ...");
    }
}

② 创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法,关键在于此处不是静态方法

java 复制代码
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

③ 编写AppForInstanceUser运行类,在类中通过工厂获取对象

java 复制代码
public class AppForInstanceUser {
    public static void main(String[] args) {
        //创建实例工厂对象
        UserDaoFactory userDaoFactory = new UserDaoFactory();
        //通过实例工厂对象创建对象
        UserDao userDao = userDaoFactory.getUserDao();
        userDao.save();
}

④ 在spring的配置文件中添加以下内容

XML 复制代码
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  • 创建实例化工厂对象,对应的是第一行配置

  • 调用对象中的方法来创建bean,对应的是第二行配置

    • factory-bean:工厂的实例对象

    • factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:

(4) FactoryBean的使用(重要)

① 创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法

java 复制代码
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

② 在Spring的配置文件中进行配置

XML 复制代码
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
3. bean的生命周期
(1) 思路分析
  • bean创建之后,想要添加内容,比如用来初始化需要用到资源

  • bean销毁之前,想要添加内容,比如用来释放用到的资源

(2) 代码实现

① 添加初始化和销毁方法

java 复制代码
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init...");
    }
    //表示bean销毁前对应的操作
    public void destory(){
        System.out.println("destory...");
    }
}

② 配置生命周期

XML 复制代码
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destory-method="destory"/>

Spring 创建bookDao这个 Bean 实例后,会自动调用 BookDaoImpl类中名为init的方法(初始化逻辑);当 Spring 容器关闭、销毁bookDao实例前,会自动调用 类中名为destroy的方法(销毁 / 清理逻辑)。

③ close关闭容器

java 复制代码
ClassPathXmlApplicationContext ctx = new 
    ClassPathXmlApplicationContext("applicationContext.xml");

五、DI相关内容

1. setter注入
(1) 注入简单数据类型

需求:给BookDaoImpl注入一些简单数据类型的数据

  1. 在BookDaoImpl类中声明对应的简单数据类型的属性

  2. 为这些属性提供对应的setter方法

  3. 在applicationContext.xml中配置

① 声明属性并提供setter方法

java 复制代码
public class BookDaoImpl implements BookDao {

    private String databaseName;
    private int connectionNum;

    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

② 配置文件中进行注入配置

XML 复制代码
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="databaseName" value="mysql"/>
     	<property name="connectionNum" value="10"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
  • 对于引用数据类型 使用的是<property name="" ref=""/>

  • 对于简单数据类型 使用的是<property name="" value=""/>

2. 构造器注入引用数据类型
(1) 思路分析
  1. 将bookDao的setter方法删除掉

  2. 添加带有bookDao参数的构造方法

  3. 在applicationContext.xml中配置

(2) 代码实现

① 删除setter方法并提供构造方法

原来的BookServiceImpl

java 复制代码
public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    // 原来的 setter 方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

现在改成构造方法注入后:

java 复制代码
public class BookServiceImpl implements BookService{
    private BookDao bookDao;

    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

**核心逻辑:**这个构造方法怪怪的,返回值是什么,方法名又是什么?

java 复制代码
public BookServiceImpl(BookDao bookDao) {
    this.bookDao = bookDao;
}

构造方法和普通方法的核心区别

特征 普通方法(比如 setter 方法) 构造方法(你问的这段代码)
方法名 任意(通常 set/get 开头) 必须和类名完全一致
返回值类型 必须声明(void/int/String 等) 不能声明任何返回值(连 void 都不能写)
作用 执行特定功能(比如赋值) 创建对象时初始化对象
调用方式 对象创建后手动调用(obj.setXxx ()) 由 JVM 自动调用(new)

逐行解释:

  • 方法名 BookServiceImpl必须和类名完全一致 (你的类名就是 BookServiceImpl),这是构造方法的 "身份标识",少一个字母、大小写不一样都不行。

  • 没有返回值类型 :你看不到 voidint 等返回值声明,这是构造方法的强制规则 ------ 因为构造方法的 "返回值" 是创建好的当前类对象 ,这个返回值由 JVM 自动处理,不需要你手动写。比如你写 new BookServiceImpl(bookDao) 时,JVM 会执行这个构造方法,然后自动返回一个 BookServiceImpl 类型的对象,你不用管返回值的声明。

② 配置文件中进行配置构造方式注入

java 复制代码
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
</beans>

核心逻辑: 内层 <constructor-arg> 标签

这是构造方法注入的关键,专门用来 "给构造方法传参数":

  • name="bookDao":指定要传给构造方法的参数名 (对应你之前写的 public BookServiceImpl(BookDao bookDao) 里的 bookDao 参数);
  • ref="bookDao":指定参数的 ------ 这里的 ref 是 "引用" 的意思,告诉 Spring:"这个参数的值,不是新造的,而是去我的对象仓库里找那个名字叫 bookDao 的对象"。

Spring 启动后,会按顺序执行配置里的指令:

1. 执行 <bean id="bookDao" .../>

  • 调用 BookDaoImpl 的无参构造(如果没写,编译器会默认生成),创建 BookDaoImpl 对象;
  • 把这个对象存到容器里,标记为 "id=bookDao"。

2. 执行 <bean id="bookService" .../>

  • 先看里面有 <constructor-arg>,知道要调用 BookServiceImpl带参构造方法
  • 去容器里找到 "id=bookDao" 的那个对象(就是第一步造的);
  • 调用 new BookServiceImpl(bookDao) 创建 BookServiceImpl 对象;
  • 把这个对象存到容器里,标记为 "id=bookService"。
3. 构造器注入多个简单数据类型
(1) 思路分析
  1. 提供一个包含这两个参数的构造方法

  2. 在applicationContext.xml中进行注入配置

(2) 代码实现

① 提供多个属性的构造函数

java 复制代码
public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;

    public BookDaoImpl(String databaseName, int connectionNum) {
        this.databaseName = databaseName;
        this.connectionNum = connectionNum;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

② 在applicationContext.xml中配置注入

XML 复制代码
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="databaseName" value="mysql"/>
        <constructor-arg name="connectionNum" value="666"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>
4. 为什么构造器一定是强制依赖的,setter 不是(重点)
(1) setter 有参但不用传参:本质是 "我压根不调用这个方法"

setter 方法(比如 setBookDao(BookDao bookDao))的参数规则是:如果调用这个方法,就必须传 BookDao 参数;但我可以选择不调用这个方法

java 复制代码
public class BookServiceImpl implements BookService {
    private BookDao bookDao; // 初始值为 null

    // 有参的 setter 方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        bookDao.save(); // 没调用 setter 的话,这里会空指针
    }

    // 测试代码
    public static void main(String[] args) {
        // 1. 创建对象:调用无参构造,完全不用管 setter
        BookServiceImpl service = new BookServiceImpl();
        
        // 2. 可选操作:我可以调用 setter(传参),也可以不调用(不传参)
        // service.setBookDao(new BookDaoImpl()); // 注释掉=不传参、不调用
        
        // 3. 运行方法:没调 setter 的话,bookDao 是 null,运行时报错
        service.save();
    }
}

区分:

  • setter 方法和构造方法是两码事:setter 是用来给成员变量赋值的普通方法(要声明返回值),构造方法是用来创建对象的特殊方法(不用声明返回值)
(2) 构造器有参就必须传参 ------ 因为 "创建对象必须调用构造器"
java 复制代码
public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    // 有参的构造器
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    // 测试代码
    public static void main(String[] args) {
        // 编译报错!因为创建对象必须调用构造器,而构造器要求传 BookDao 参数
        BookServiceImpl service = new BookServiceImpl(); 
    }
}

这里的关键是:创建对象的动作(new)和构造器的调用是绑定的 ------ 只要 new 对象,就必须调构造器,构造器要参数就必须传,躲不开;而 setter 是 new 完对象之后的 "额外动作",调不调全看你。

相关推荐
时代的凡人7 小时前
0208晨间笔记
笔记
今天只学一颗糖8 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
testpassportcn9 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
游乐码11 小时前
c#变长关键字和参数默认值
学习·c#
饭碗、碗碗香12 小时前
【Python学习笔记】:Python的hashlib算法简明指南:选型、场景与示例
笔记·python·学习
Wils0nEdwards13 小时前
初中化学1
笔记
魔力军13 小时前
Rust学习Day4: 所有权、引用和切片介绍
开发语言·学习·rust
wubba lubba dub dub75013 小时前
第三十六周 学习周报
学习
学编程的闹钟14 小时前
PHP字符串表示方式全解析
学习
Lbs_gemini060314 小时前
01-01-01 C++编程知识 C++入门 工具安装
c语言·开发语言·c++·学习·算法