您现在的位置是:首页 >技术杂谈 >Spring详解(超全面)网站首页技术杂谈

Spring详解(超全面)

午觉千万别睡过 2023-06-16 00:00:02
简介Spring详解(超全面)

一、Spring

​ Spring是SSM(Spring、SpringMVC、Mybatis)框架之一。所谓框架,就是高度抽取可重用代码的一种设计,具有高度通用性;

1、 概述

(1)Spring是一个开源框架;

(2)Spring为简化企业级开发而生,使用Spring,JavaBean就可以使代码非常的优雅和简洁;

(3)Spring是一个IoC(DI)和AOP容器框架;

(4)Spring的优良特性

​ 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API

​ 依赖注入:DI(Dependency Injection),控制反转(IoC)最经典的实现;

​ 面向切面编程:AOP(Aspect Oriented Programming)

​ 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期

(5)组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用,在 Spring 中可以使用XML和Java注解组合这些对象;

(6)一站式:在IoC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC);

(7)Spring模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TE3J19zB-1682512561625)(C:UsersadminDesktopssmspring组件图.png)]

解释:

​ 每一个绿色的框,代表一个功能模块;绿色框中的黑色框,代表使用这个功能模块需要哪些jar包。

2、为什么要用Spring

(1)方便解耦,简化开发

​ Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。

(2)方便集成各种优秀框架

​ Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。

(3)降低 Java EE API 的使用难度

​ Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。

(4)方便程序的测试

​ Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

(5)AOP 编程的支持

​ Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。

(6)声明式事务的支持

​ 只需要通过配置就可以完成对事务的管理,而无须手动编程;

​ 作为 Java 程序员,对 Spring 的理解和掌握程度,基本上就是编程能力的体现。

3、Spring初体验

  1. 创建Maven项目

    勾选通过archetype构建,选择maven-archetype-quickstart

  2. 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
  1. 创建接口和实现类
public interface UserDao {
    void queryAll();
}
public class UserDaoImpl implements UserDao {
    public UserDaoImpl(){
        System.out.println("UserDaoImpl对象创建了");
    }

    @Override
    public void queryAll() {
        System.out.println("查询了所有用户信息");
    }
}
  1. 创建spring配置文件

​ 在工程resource目录下创建spring-config.xml,idea中可以直接在resource目录下使用菜单New–>XML Configuration File–> Spring Config创建。

<?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指定创建出来的对象在核心容器中的名字(name),用作唯一标识
        class指定创建对象的类的全类名
    -->
    <bean id="userDaoImpl" class="com.hqyj.cl.dao.impl.UserDaoImpl"/>
</beans>
  1. 测试
public class UserDaoTest {

    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        UserDao userDaoImpl = ac.getBean("userDaoImpl", UserDao.class);
        userDaoImpl.selectAll();
    }
}

二、IoC

1、概念

​ IoC(Inversion of Control)即控制反转。在使用Spring之前,我们获取对象几乎都是用过new的方式,但是呢,在使用Spring之后,我们就不在使用new的方式来创建对象,而是交给Spring来给我们创建,这种对对象控制权的转移就叫做控制反转。

2、IoC容器

  • IoC 容器是 Spring 的核心,也可以称为 Spring 容器。Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。

  • Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。

  • Spring 通过读取 XML 或 Java 注解中的信息来获取哪些对象需要实例化。

  • Spring 提供 2 种不同类型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。

2.1 BeanFactory

​ BeanFactory容器,Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;BeanFactory在启动的时候不会去实例化Bean,当有从容器中拿Bean的时候才会去实例化;

BeanFactory ac = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

2.2 ApplicationContext

​ 应用上下文,继承BeanFactory接口,它是Spring的一个更高级的容器,提供了更多的有用的功能;ApplicationContext 在 BeanFactory 的基础上增加了很多企业级功能,例如 AOP、国际化、事务支持等;ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化; ApplicationContext 接口有两个常用的实现类:

2.2.1 ClassPathXmlApplicationContext

​ 该类从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示:

ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");

2.2.2 FileSystemXmlApplicationContext

​ 该类从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示:

ApplicationContext ac = new FileSystemXmlApplicationContext("D:\workspace\spring_demo\src\main\resources\spring-config.xml");

三、Spring Bean的创建

​ 由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。一般情况下,我们都是在Spring的配置文件中来管理Bean。

​ Spring的配置文件中,由一组<beans></beans>标签包裹,在其中使用<bean></bean>标签来管理每一个对象。

1、无参构造创建

​ 使用此方式创建bean一定要有无参构造,否则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">
    <!--
        bean标签进行描述
        id指定创建出来的对象在核心容器中的名字(name),用作唯一标识
        class指定创建对象的类的全类名
    -->
    <bean id="userDaoImpl" class="com.hqyj.dao.impl.UserDaoImpl" lazy-init="true"/>
</beans>

1.1 bean标签属性

属性名称描述
idBean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
namename 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope用于设定 Bean 实例的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
constructor-arg元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
property元素的子元素,用于调用 Bean 实例中的 setter 方法来属性赋值,从而完成依赖注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名
ref 和 等元素的子元索,该元素中的 bean 属性用于指定对某个 Bean 实例的引用
value 和 等元素的子元素,用于直接指定一个常量值
list用于封装 List 或数组类型的依赖注入
set用于封装 Set 类型的依赖注入
map用于封装 Map 类型的依赖注入
entry 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值
init-method容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效

2、动态工厂模式创建

  1. 创建UserDaoFactory工厂
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}
  1. Spring配置文件
<!-- 通过普通工厂创建bean-->
<!-- 第一步,将工厂类本身加入ioc容器-->
<bean id="userDaoFactory" class="com.hqyj.cl.dao.impl.UserDaoFactory"/>
<!-- 第二步,配置bean 
    factory-bean指定由ioc容器创建的工厂bean
    factory-method指定的该工程的工厂方法 -->
<bean id="userDaoImpl" factory-bean="userDaoFactory" factory-method="getUserDao"/>

3、静态工厂模式创建

  1. 创建UserDaoFactory工厂
public class UserDaoFactory {
    public static UserDao getUserDao02(){
        return new UserDaoImpl();
    }
}	
  1. Spring配置文件
<!-- 第二步,配置bean 
	id指定创建出来的对象在核心容器中的名字(name),用作唯一标识
	class指定创建对象的类的全类名
    factory-method指定的该工程的静态工厂方法 -->
<bean id="userDaoImpl" class="com.hqyj.cl.dao.impl.UserDaoFactory" factory-method="getUserDao02"/>

四、Spring Bean的作用域

ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application8将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

4.1 示例代码

4.1.1 singleton

  1. Spring配置文件
<bean id="userDaoImpl" class="com.hqyj.cl.dao.impl.UserDaoImpl" scope="singleton"/>
  1. 测试类
public class UserDaoTest {
    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        UserDao userDaoImpl = ac.getBean("userDaoImpl", UserDao.class);
        UserDao userDaoImpl1 = ac.getBean("userDaoImpl", UserDao.class);
        System.out.println(userDaoImpl==userDaoImpl1);
    }
}

​ 结果输出为true,之前在UserDaoImpl类中写的无参构造输出语句,只会输出一遍,说明对象只创建了一次。

4.1.2 prototype

  1. Spring配置文件
<bean id="userDaoImpl" class="com.hqyj.cl.dao.impl.UserDaoImpl" scope="prototype"/>
  1. 测试类
public class UserDaoTest {
    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        UserDao userDaoImpl = ac.getBean("userDaoImpl", UserDao.class);
        UserDao userDaoImpl1 = ac.getBean("userDaoImpl", UserDao.class);
        System.out.println(userDaoImpl==userDaoImpl1);
    }
}

​ 结果输出为false,之前在UserDaoImpl类中写的无参构造输出语句,会输出两遍,说明对象创建了两次。

五、Spring Bean的生命周期

  • 在传统的 Java 应用中,Bean 的生命周期很简单,使用关键字 new 实例化 Bean,当不需要该 Bean 时,由 Java 自动进行垃圾回收。

  • Spring 中 Bean 的生命周期较复杂,可以表示为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。

  • Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

  • 在 XML 中配置Bean时,可以在Bean标签中使用 init-method 属性和 destory-method属性来分别定义该bean的初始化回调方法和销毁回调方法。

5.1 示例代码

  1. userDao
public class UserDaoImpl implements UserDao {

    public UserDaoImpl(){
        System.out.println("UserDaoImpl对象创建了");
    }

    @Override
    public void selectAll() {
        System.out.println("执行了selectAll方法");
    }

    public void init(){
        System.out.println("init");
    }

    public void destroy(){
        System.out.println("destroy");
    }
}
  1. Spring配置文件
<bean id="userDaoImpl1" class="com.hqyj.cl.dao.impl.UserDaoImpl" scope="prototype"
      init-method="init" destroy-method="destroy"/>
  1. 测试类
public class UserDaoTest {

    @Test
    public void selectAll() {
        AbstractApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        ac.registerShutdownHook();
    }
}

​ 在非Web应用的单例模式中,手工加载Spring IoC容器,要用AbstractApplicationContext接口继承了ApplicationContext接口,其中方法registerShutdownHook/close,可以手动关闭容器,让destroy-method去执行对应的方法

六、DI

​ DI(Dependency Injection),即“依赖注入”:是组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

​ 依赖注入主要有两种实现方式,分别是 setter 注入、构造函数注入。

6.1 setter 注入

​ 指 IoC 容器使用 setter 方法注入被依赖的实例。通过调用无参构造器或无参 static 工厂方法实例化 Bean 后,调用该 Bean 的 setter 方法,即可实现基于 setter 的 依赖注入。

​ 在 Spring 实例化 Bean 的过程中,首先会调用默认的构造方法实例化 Bean 对象,然后通过 Java 的反射机制调用 setXxx() 方法进行属性的注入。因此,setter 注入要求 Bean 的对应类必须满足以下两点要求:

  • 必须提供一个默认的无参构造方法;

  • 必须为需要注入的属性提供对应的 setter 方法。

    使用 setter 注入时,在 Spring 配置文件中,需要使用 <bean> 元素的子元素 <property> 为每个属性注入值;使用构造注入

时,在配置文件中,主要使用 <constructor-arg> 标签定义构造方法的参数,使用其 value 属性(或子元素)设置该参数的值。

6.1.1 示例代码

<property>标签包含 name、ref、value 等属性。name 用于指定参数名称;value 属性用于注入基本数据类型以及字符串类型的值;ref 属性用于注入已经定义好的 Bean

  1. 创建一个Java类User
public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                '}';
    }
}
  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">
    <bean id="user" class="com.hqyj.cl.pojo.User">
        <property name="id" value="1"/>
        <property name="username" value="chenlei"/>
        <property name="password" value="111"/>
    </bean>
</beans>
  1. 测试类
public class UserDaoTest {

    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        User user = ac.getBean("user", User.class);
        System.out.println(user);
    }
}

6.2 构造函数

​ 指 IoC 容器使用构造函数注入被依赖的实例。可以通过调用带参数的构造函数实现依赖注入,每个参数代表一个依赖。

​ 通过使用构造函数注入被依赖的实例有三种方式:通过下标注入、通过属性注入、通过参数名注入。

6.2.1 定义一个User类

public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                '}';
    }
}

6.2.2 通过下标注入

​ 当构造函数有多个参数时,可以使用 index 属性指定参数的位置,index 属性值从 0 开始,如果不指定index,默认使用标签先后顺序。

<bean id="user" class="com.hqyj.cl.pojo.User">
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="chenchen"/>
    <constructor-arg index="2" value="23"/>
</bean>

6.2.3 通过属性注入

​ name属性通过指定构造方法中的参数名来进行注入,最为常用,重点掌握

<bean id="user" class="com.hqyj.cl.pojo.User">
    <constructor-arg type="int" value="2"/>
    <constructor-arg type="java.lang.String" value="chenchen"/>
    <constructor-arg type="java.lang.String" value="123"/>
</bean>

6.2.4 通过参数名注入

<bean id="user" class="com.hqyj.cl.pojo.User">
    <constructor-arg name="id" value="3"/>
    <constructor-arg name="username" value="chenchen"/>
    <constructor-arg name="password" value="123"/>
</bean>

6.3 setter注入和构造函数注入的比较

构造函数注入:

优点:创建时必须要指定构造函数中的全部参数,bean才能被创建,保证了对象创建出来之后,成员变量一定都有值。

缺点:必须要指定全部参数,否则对象无法创建,使用该方式改变对象的创建过程。

setter注入:

优点:对象创建时,无需指定参数,不会限制对象的创建。
缺点:对象具有多个属性可以注入时,无法保证全部或某些属性一定被注入。
setter注入方式较为灵活,使用更方便。

6.4 setter注入对象

​ 有几种情况:普通类型,List集合,Map集合,String类型,对象引用类型,Set集合,数组,Properties,null类型

  1. Student类
public class Student {
    private String name;
    private Counselor counselor;
    private String[] courses;
    private List<String> books;
    private Map<String, String> idCard;
    private Set<String> studentNumber;
    private String wifeName;
    private Properties properties;

    public Student() {
    }

    public Student(String name, Counselor counselor, String[] courses, List<String> books, Map<String, String> idCard, Set<String> studentNumber, String wifeName, Properties properties) {
        this.name = name;
        this.counselor = counselor;
        this.courses = courses;
        this.books = books;
        this.idCard = idCard;
        this.studentNumber = studentNumber;
        this.wifeName = wifeName;
        this.properties = properties;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Counselor getCounselor() {
        return counselor;
    }

    public void setCounselor(Counselor counselor) {
        this.counselor = counselor;
    }

    public String[] getCourses() {
        return courses;
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public List<String> getBooks() {
        return books;
    }

    public void setBooks(List<String> books) {
        this.books = books;
    }

    public Map<String, String> getIdCard() {
        return idCard;
    }

    public void setIdCard(Map<String, String> idCard) {
        this.idCard = idCard;
    }

    public Set<String> getStudentNumber() {
        return studentNumber;
    }

    public void setStudentNumber(Set<String> studentNumber) {
        this.studentNumber = studentNumber;
    }

    public String getWifeName() {
        return wifeName;
    }

    public void setWifeName(String wifeName) {
        this.wifeName = wifeName;
    }

    public Properties getProperties() {
        return properties;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", counselor=" + counselor +
                ", courses=" + Arrays.toString(courses) +
                ", books=" + books +
                ", idCard=" + idCard +
                ", studentNumber=" + studentNumber +
                ", wifeName='" + wifeName + ''' +
                ", properties=" + properties +
                '}';
    }
}
  1. Counselor类
public class Counselor {
    private String name;

    public Counselor() {
    }

    public Counselor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Counselor{" +
                "name='" + name + ''' +
                '}';
    }
}
  1. Spring配置文件
<!--引用注入-->
<bean id="counselor" class="com.hqyj.cl.pojo.Counselor">
    <property name="name" value="张老师"/>
</bean>
<!--注入学生类-->
<bean id="student" class="com.hqyj.cl.pojo.Student">
    <!--普通值注入-->
    <property name="name" value="chenchen"/>
    <!--引用注入-->
    <property name="counselor" ref="counselor"/>
    <!--数组注入-->
    <property name="courses">
        <array>
            <value>计算机组成原理</value>
            <value>计算机网络</value>
            <value>数据结构</value>
        </array>
    </property>
    <!--List集合注入-->
    <property name="books">
        <list>
            <value>Java入门到放弃</value>
            <value>Python大数据挖掘与分析</value>
        </list>
    </property>
    <!--Map集合注入-->
    <property name="idCard">
        <map>
            <entry key="idCard" value="29121821212"/>
            <entry key="phone" value="123121212121"/>
        </map>
    </property>
    <!--Set集合注入-->
    <property name="studentNumber">
        <set>
            <value>20200301123</value>
        </set>
    </property>
    <!--null注入-->
    <property name="wifeName">
        <null/>
    </property>
    <!--Properties注入-->
    <property name="properties">
        <props>
            <prop key="username">chenchen</prop>
            <prop key="password">112112</prop>
        </props>
    </property>
</bean>
  1. 测试类
public class UserDaoTest {

    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        Student student = ac.getBean("student", Student.class);
        System.out.println(student);
    }
}

七、基于注解的配置方式

​ 在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

​ Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。

​ Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加标签 <context:annotation-config/>,启用注解支持;

<context:annotation-config/>:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作

<context:component-scan/>:除了兼具 <context:annotation-config/> 功能外,还具有自动将带有@component@service@Repository等注解的对象注册到spring容器中的功能

​ 配置包扫描注解:

​ <context:component-scan base-package=“com.hqyj”/>

1、创建Bean相关的注解

1.1 @Component

​ 可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

1.2 @Repository

​ 用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

1.3 @Service

​ 通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

1.4 @Controller

​ 通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

2、Bean生命周期相关的注解

2.1 @Scope

​ 注解在类名上,用于指定Bean作用范围,等同于xml文件中配置Bean时使用scope属性。常用值singleton、prototype

2.2 @PostConstruct

​ 注解在方法上,指定Bean创建时回调方法。相当于xml配置文件中<bean>标签中使用init-method属性的功能。

2.3 @PreDestroy

​ 注解在方法上,指定Bean销毁时回调方法。相当于xml配置文件中<bean>标签中使用destroy-method属性的功能。

2.4 @Configuration

全注解实现,@Configuration类似xml配置文件beans标签@bean类似于bean标签

import com.hqyj.pojo.Dog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBean {  //方法返回值就是定义bean的类型 方法名就是id
    @Bean
    public Dog dog(){
        return new Dog("哈士奇","黑白色");
    }
}
public static void main(String[] args) {
    //通过注解Component
    ApplicationContext ac = new AnnotationConfigApplicationContext(MyBean.class);
    Dog dog = ac.getBean("dog", Dog.class);
    System.out.println(dog);
}

3、依赖注入相关的注解

3.1 @Autowired

自动装配:

​ 自动装配就是指 Spring 容器在不使用 <constructor-arg><property>标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

​ 自动装配只需要较少的代码就可以实现依赖注入。但不能自动装配简单数据类型,比如 int、boolean、String 等,相比较显式装配,自动装配不受程序员控制。

​ @Autowired注解可以应用到 Bean 的属性变量、属性的 setter 方法、非 setter 方法及构造函数等,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。自动装配可以不用到对应的set方法。

​ 在配置文件的bean标签中也有autowired属性

​ byType:保证 bean 的 id 是唯一的,且该 bean 的类型也是唯一的

​ byName:保证 bean 的 id 是唯一的,且该 bean 的 id 是此类属性对应的 set方法的方法名

User类

@Data   //提供了getter  setter方法
@AllArgsConstructor   //提供了有参构造方法
@NoArgsConstructor   //提供了无参构造方法
@Component
public class User {
    @Value("zhangsan")
    private String username;
    @Value("111")
    private String password;
    @Autowired
    private Address address;
}

Address类

@Component
public class Address {
    @Value("chengdu")
    private String addr;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Address{" +
                "addr='" + addr + ''' +
                '}';
    }
}

配置文件bean.xml

<bean id="User" class="com.hqyj.pojo.User">
</bean>
<bean id="address" class="com.hqyj.pojo.Address">
</bean>

测试类

public class UserTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        User user = ac.getBean("User", User.class);
        System.out.println(user);
    }
}

3.2 @Resource

​ 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。

​ @Resource 中有两个重要属性:name 和 type。

​ Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。

User类

@Data   //提供了getter  setter方法
@AllArgsConstructor   //提供了有参构造方法
@NoArgsConstructor   //提供了无参构造方法
@Component
public class User {
    @Value("zhangsan")
    private String username;
    @Value("111")
    private String password;
    /*@Autowired
    @Qualifier("address1")*/
    @Resource(name = "address1")  //相当于@Autowired+@Qualifier
    private Address address;
}

3.3 @Qualifier

​ 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。(可以指定注入的bean)

Address类(去掉@Component注解)

public class Address {
    private String addr;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Address{" +
                "addr='" + addr + ''' +
                '}';
    }
}

配置文件

<context:component-scan base-package="com.hqyj.pojo"/>
<bean id="User" class="com.hqyj.pojo.User">
</bean>
<bean id="address" class="com.hqyj.pojo.Address">
    <property name="addr" value="chengdu"></property>
</bean>
<bean id="address1" class="com.hqyj.pojo.Address">
    <property name="addr" value="haerbin"></property>
</bean>

User类

@Data   //提供了getter  setter方法
@AllArgsConstructor   //提供了有参构造方法
@NoArgsConstructor   //提供了无参构造方法
@Component
public class User {
    @Value("zhangsan")
    private String username;
    @Value("111")
    private String password;
    @Autowired
    @Qualifier("address1")  //指定要注入的bean
    private Address address;
}

测试类

public class UserTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        User user = ac.getBean("User", User.class);
        System.out.println(user); //拿到haerbin
    }
}

额外说明:

<context:component-scan base-package="com.hqyj.pojo"/>
<bean id="User" class="com.hqyj.pojo.User" autowire="byType">
</bean>
<bean id="address" class="com.hqyj.pojo.Address" >
    <property name="addr" value="chengdu"></property>
</bean>
<!--<bean id="address1" class="com.hqyj.pojo.Address">
    <property name="addr" value="haerbin"></property>
</bean>-->

可以在user中去掉@autowire注解 在配置文件中加入,可以byType注入,但是注入的bean需要唯一。

<context:component-scan base-package="com.hqyj.pojo"/>
<bean id="User" class="com.hqyj.pojo.User" autowire="byName">
</bean>
<bean id="address" class="com.hqyj.pojo.Address" >
    <property name="addr" value="chengdu"></property>
</bean>
<bean id="address1" class="com.hqyj.pojo.Address">
    <property name="addr" value="haerbin"></property>
</bean>

autowire=“byName”,是根据pojo中get和set方法的参数名获取对应的值(可以手动编写User类的getter和setter方法测试)

想要注入对应的bean,需要修改set方法的方法名,例如:

public void setAddress1(Address address) {
    this.address = address;
}

3.4 @Value

​ @Autowired注入另外的bean,而@Value注解则是注入值。由于在java源代码中注入常量值会带来硬编码问题,该注解使用较少。

4、示例代码

  1. UserService
public interface UserService {
    void selectAll();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    // 注入值
    @Value("张三")
    private String username;

    @Override
    public void selectAll() {
        System.out.println(username);
        userDao.selectAll();
    }
}
  1. Spring配置文件
<context:component-scan base-package="com.hqyj.cl" />
<bean id="man" class="com.hqyj.cl.pojo.Man" />
<bean id="student" class="com.hqyj.cl.pojo.Student">
    <property name="name" value="student1"/>
</bean>
<bean id="student1" class="com.hqyj.cl.pojo.Student">
    <property name="name" value="student1"/>
</bean>
<bean id="counselor" class="com.hqyj.cl.pojo.Counselor">
    <property name="name" value="张老师" />
</bean>
  1. Man类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Man {

    @Value("张三")
    private String name;

/*    @Autowired
    @Qualifier("student1")*/
    @Resource(type = Student.class)
    private Student student;

    @Autowired
    private Counselor counselor;
}
  1. 测试类
public class UserDaoTest {

    @Test
    public void selectAll() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        Man man = ac.getBean("man", Man.class);
        System.out.println(man);
    }
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。