admin管理员组

文章数量:1547231

文章目录

  • Main References
  • 1. Spring简介
    • 1.1 简介
    • 1.2 Spring的优点
    • 1.3 Spring的组成
    • 1.4 拓展
  • 2. 控制反转(IOC)原理讲解
    • 2.1 IOC原型
    • 2.3 IOC本质
    • 2.4 IOC思想的代码实现
      • 2.4.1 HelloSpring
        • 这个过程就叫控制反转:
        • ApplicationContext 的 getBean 方法
        • SpringContextsUtil 工具类(拓展)
      • 2.4.2 改造2.1的IOC原型代码
  • 3. IOC创建对象的方式
  • 4. Spring 配置
    • 4.1 别名
    • 4.2 Bean的配置
    • 4.3 import
  • 5. 依赖注入(DI)
    • 5.1 构造器注入
    • 5.2 set方式注入(重点)
        • 基于构造方法 还是 基于setter 的 DI?
    • 5.3 拓展方式
        • p命名空间 和 c命名空间
    • 5.4 bean的作用域
  • 6. bean的自动装配
      • 6.1 场景搭建
      • 6.2 byName自动装配
      • 6.3 byType自动装配
      • 6.4 使用注解实现自动装配
        • 6.4.1 @Autowired
        • 6.4.2 @Resource
  • 7. 使用注解开发
    • 7.1 场景搭建
    • 7.2 如何使用注解开发
      • 7.2.1 bean
          • @Component 组件 (Spring的注解)
      • 7.2.2 属性如何注入
          • @Value 值(Spring的注解)
      • 7.2.3 衍生的注解
  • 8. 使用JavaConfig实现配置(零配置)
    • 8.1 AnnotationConfigApplicationContext
      • 8.1.1 以下我会例举一些场景,并作出我个人的测试小结:
    • 8.2 @Bean
      • 8.2.1 完整的@Configuration:
      • 8.2.2 “精简”@Bean 模式(lite模式)
      • 8.2.3 自定义bean的命名
      • 8.2.4 bean的依赖
      • 8.2.5 接受生命周期的回调
      • 8.2.6 拓展
    • 8.3 @Configuration
      • 8.3.1 注入Bean间的依赖
      • 8.3.2 查找方法注入
      • 8.3.3 使用@Import 注解
      • 8.3.4 在导入的@Bean 定义上注入依赖项
    • 8.4 @ComponentScan
      • 8.4.1 组件扫描 @ComponentScan
      • 8.4.2 scan() - **AnnotationConfigApplicationContext **
    • 8.5 实现方式-简单场景演示:
      • 8.5.1 @Configuration + @Bean
      • 8.5.2 @Component + @Configuration + @ComponentScan
  • Debug

Main References

  1. 暑假集训授课同学的帖子(上)
  2. Bilibili 狂神讲Spring
  3. Spring Framework官网
  4. MVN Repository :maven项目架构的各种依赖

PS:笔记部分大致是按照狂神的进度来记录的,有时候会插入一些从网上贴子和官网的内容

gitee:https://gitee/torchW/spring-study

1. Spring简介

学习线路 Spring(内涵SpringMVC) =》SpringBoot =》SpringCloud

1.1 简介

History

  • 2002,首次推出了Spring框架的雏形:interface21 框架
  • 2004.3.24,Spring框架以interface21 框架为基础,经过重新设计,并不断丰富其内涵,正式发布1.0正式版。
  • Rod Johnson,Spring Framework创始人

Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。

如何整合的,早年的两套系统:

SSH(现在不再流行了):Struct2 + Spring + Hibernate(全自动可持久化)

SSM:SpringMVC + Spring + Mybatis(半自动可持久化)

官网:https://spring.io/projects/spring-framework#overview

官方下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/

GitHub:https://github/spring-projects/spring-framework

Spring是一个轻量级的控制反转(IoC)和面向切面编程(AOP)的框架,是为了解决企业级应用开发的复杂性而创建的。

范围:任何Java应用

核心描述
IoCInverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。
AOPAspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

maven项目,在pom.xml中导包:Spring-WebMVC 是个比较庞大的包,会涉及其他很多的包。一般还会用上jdbc的依赖。

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.21</version>
        </dependency>
		<dependency>
            <groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.3.20</version>
        </dependency>
</dependencies>

1.2 Spring的优点

  • 开源的免费框架(容器)
  • 轻量级的、非入侵式(使用框架不会改变项目)的框架
  • 控制反转(IOC)、面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

1.3 Spring的组成

上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业应用开发的需求(无论是JavaSE或JavaEE等等应用场景),在开发过程中可以根据需求有选择性地使用所需要的模块。

  1. 核心容器(SpringCore)

    核心容器提供Spring框架的基本功能。spring以bean的方式组织和管理Java应用的各个组件及其关系,spring使用BeanFactory来产生和管理Bean,是工厂模式的实现,BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开

  2. 应用上下文(Spring Context)

    Spring上下文是一个配置文件,向spring提供上下文信息,spring上下文包括企业服务

  3. Spring面向切面编程(Spring AOP)

    通过配置管理特性,SpringAOP模块直接将面向方法的编程功能集成在了Spring框架中,Spring管理的任何对象都支持AOP,SpringAOP模块基于Spring的应用程序中的对象提供了事务管理服务,通过使用SpringAOP,不用依赖EJB组件,就可以将声明性事务管理集成在应用程序中

  4. JDBC和DAO模块(Spring DAO)

    JDBC、DAO的抽象层,提供了有意义的异常层次结构实现,可用该结构来管理异常处理,和不同数据库提供商抛出的错误信息,异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

  5. 对象实体映射(Spring ORM)

    Spring插入了若干个ORM框架,提供了ORM对象的关系工具,其中包括Hibernate,JDO和IBatisSQL Map等,所有这些都遵从Spring的通用事务和DAO异常层次结构

  6. Web模块(Spring Web)

    web上下文模块建立应用程序上下文模块之上,基于web的应用程序提供了上下文,所以spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作

  7. MVC模块(SpringWebMVC)

    MVC框架是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程高度可配置的,MVC容纳了大量视图技术,其中包括JSP,POI等,模型由JavaBean来构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码,由c的事情。spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境,spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用

1.4 拓展

在Spring的官网有这个介绍:现代化的Java开发,就是基于Spring的开发

  • SpringBoot
    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速的开发单个微服务
    • 约定大于配置
  • SpringCloud
    • SpringCloud是基于SpringBoot实现的

因为现在大多数公司都在是同SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC,承上启下的作用!

Spring弊端:发展了太久之后,违背了原来的理念!杂的太多了,配置十分繁琐,人称:“配置地狱”!

2. 控制反转(IOC)原理讲解

2.1 IOC原型

原来业务的实现:

  1. 项目结构

    1. UserDao接口

      public interface UserDao {
          void getUser();
      }
      
    2. UserDaoImpl实现类

      public class UserDaoImpl implements UserDao{
      
          @Override
          public void getUser() {
              System.out.println("默认获取用户的数据");
          }
      }
      
    3. UserService业务接口

      public interface UserService {
          void getUser();
      }
      
    4. UserServuceImpl业务实现类

      public class UserServiceImpl implements UserService{
          private UserDao userDao = new UserDaoImpl();
      
          @Override
          public void getUser() {
              userDao.getUser();
          }
      }
      
    5. MyTest 测试类

      public class MyTest {
          public static void main(String[] args) {
              //用户实际调用的是业务层,dao层他们不需要接触!
              //业务层实现功能,类似一个转接作用
              UserService userService = new UserServiceImpl();
              userService.getUser();
          }
      }
      
  2. 项目创建

    1. pom.xml中添加依赖

      <dependencies>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.3.21</version>
          </dependency>
      </dependencies>
      
    2. 删除src文件夹,将初始的模块作为总模块,方便管理后续的子模块

  3. 假如需求改变,创建了 UserDaoMySQLImpl 新的实现类,就需要将业务实现类中 创建的Dao层接口实现类 进行修改。

    private UserDao userDao = new UserDaoMySQLImpl();
    

    这只是简单的改动,但是当项目工程足够大的时候,需求的变动使得程序员改动众多的代码,是十分不合理的。

    因此服务实现类中 除了new的方法,可以**通过set的方法进行注入**。

    UserServuceImpl业务实现类:

    public class UserServiceImpl implements UserService{
        private UserDao userDao;
    
        //利用set进行动态实现值的注入
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    

    MyTest 测试类:

    public class MyTest {
        public static void main(String[] args) {
            UserServiceImpl userService = new UserServiceImpl();
    
            userService.setUserDao(new UserDaoImpl());
            userService.getUser();
    
            userService.setUserDao(new UserDaoMySQLImpl());
            userService.getUser();
        }
    }
    
    • 之前是程序主动创建对象,控制权掌握在程序员手上!

    • 在set注入之后,程序不具有主动性,而是变成了被动接受,主动权交给客户!

      = 》 控制反转

    这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。降低系统耦合性,可以更加专注在业务的实现上。在传统实现上,我们修改业务还需要修改服务层代码,现在我们横向扩展业务之后,只需要专注扩展业务就行了。只需要给用户接口,只需要用户创建接口注入服务层即可使用,我么的代码架构就不需要任何的变化。

    下图明确的显示了主动权的转移:

这就是Spring底层的基本机制。这仅仅是IOC的原型

2.3 IOC本质

控制反转IoC,是一种设计思想,DI(依赖注入)是实现IoC 的一种方法(prefered),也有人认为DI 是IoC 的另一种说法。

没有IoC 的程序中,即传统的Java应用中,我们使用面向对象编程,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 **new Object() 的方式 **将后者的对象创建出来,然后才能实现属性或方法的调用。这样子,对象的创建于对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。

控制反转后将对象的创建转移给第三方(IoC容器),现在IoC容器掌握了主动权,可以自己选择需要创建的对象。个人认为所谓控制反转就是:获得依赖对象的方式反转了。

解耦图解: 图1 早年的情况 图2 应用IoC后的 图3 理想情况

IoC是Spring框架的核心内容,控制反转通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

使用多种方式完美的实现了IoC,采用XML方式配置Bean 的时候,Bean 的定义信息是和实现分离的;而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。(spring 2.5 以前使用XML文件配置,3.0开始可以使用注解配置)

在 Spring 应用中,大致步骤如下。

  1. 开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义。
  2. Spring 容器在初始化时先读取配置信息,自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
  3. 当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。

2.4 IOC思想的代码实现

2.4.1 HelloSpring

  1. 创建Hello类

  2. 新建spring配置文件(xml)(存放在resources文件夹下)(命名 beans.xml 即可[亦可随意],不用和官方一样长)

    配置信息的模板(推荐去 官网 取模板)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
        xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework/schema/beans
            https://www.springframework/schema/beans/spring-beans.xsd">
    	
        <bean id="..." class="...">  【1】【2】
            <!-- collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions go here -->
    
    </beans>
    

    【1】id 属性是一个标识单个 bean 定义的字符串。

    【2】class 属性定义 bean 的类型并使用完全限定班级名称。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
           xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework/schema/beans
            https://www.springframework/schema/beans/spring-beans.xsd">
    
        <!--  使用Spring 来创建对象,再Spring这些都成为Bean  
    	类型 变量名 = new 类型();
    	Hello hello = new Hello();
    	id = 变量名
    	class = new 的对象(全路径)
    	property 相当于给对象中的属性设置一个值。-->
        
        <bean id="hello" class="com.torch.pojo.Hello">
            <property name="str" value="Spring"/>
        </bean>
    </beans>
    

    填写好模板后根据提示完成文件上下文的配置

名称 就按照文件的名称即可

  1. 新建测试类,实例化容器,通过容器获取所需的类

    public class MyTest {
        public static void main(String[] args) {
            //获取Spring的上下文对象,用xml加载context,就必须用 ClassPathXmlApplicationContext,可以传入多个配置文件,这里就只需要传入一个
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            //我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以
            Hello hello = (Hello) context.getBean("hello");
            System.out.println(hello);
        }
    }
    

思考问题:

  • Hello 对象是谁创建的? Hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的? Hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转:

​ 控制:谁来控制对象的创建,传统该应用程序的对象是由程序本身控制创建的,使用Spring后,对象 由Spring来创建的。

​ 反转:程序本身不创建对象,而编程被动的接收对象。

​ 依赖注入:就是利用 pojo 类的 set 方法来进行注入的。(当实体类被Spring托管后会看到类旁边会 有"叶子"进行跳转)

​ IOC是一种编程思想,由主动的编程变成被动的接收。

​ 可以通过 new ClassPathXmlApplicationContext 去浏览底层源码。

因此到了现在, 我们彻底不用在程序中去改动了,要实现不同的操作,只需要xml配置文件中进行修改,所谓的IoC,一句话搞定:对象由Spring来创建,管理,装配!

ApplicationContext 的 getBean 方法
  • getBean (String s) :

    参数就是spring配置文件中bean的id,去IoC容器中找 唯一的id 的bean,找不到或者找到多个,则抛出异常。

    Hello hello = (Hello) context.getBean("hello");
    
  • getBean (String s,Class aClass) :

    传入bean 的 id 和 指定类的class,去IoC容器中找 对应类 的bean,找不到或者找到多个,则抛出异常;相较于第一项,可以不用进行强制转换。

    Hello hello = context.getBean("hello",Hello.class);
    
  • getBean (Class aClass) :

    传入指定类的class,去IoC容器中找 对应类 的bean,找不到或者找到多个,则抛出异常。

    Hello hello = context.getBean(Hello.class);
    

注入的类可以是注册bean的实现的接口 或者 继承的父类,通过接口或者父类获取bean。

SpringContextsUtil 工具类(拓展)

创建一个工具类SpringContextsUtil ,通过实现Spring中的ApplicationContextAware接口,在applicationContext.xml中注入bean后Spring会自动调用setApplicationContext方法。此时我们就可以获取到Spring context。

public class SpringContextsUtil implements ApplicationContextAware{
  //Spring应用上下文环境
  private static ApplicationContext applicationContext; 
  
  /**
  * 实现ApplicationContextAware接口的回调方法,设置上下文环境  
  * @param applicationContext
  * @throws BeansException
  */
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    SpringContextsUtil.applicationContext = applicationContext;
  }
 
  /**
  * @return ApplicationContext
  */
  public static ApplicationContext getApplicationContext() {
    return applicationContext;
  }
 
  /**
  * 获取注册bean名为name 的对象  
  * @param name
  * @return Object 一个以所给名字注册的bean的实例
  * @throws BeansException
  */
  public static Object getBean(String name) throws BeansException {
    return applicationContext.getBean(name);
  }
 
  /**
  * 获取类型为requiredType,注册bean名为name 的对象
  * 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
  * @param name       bean注册名
  * @param requiredType 返回对象类型
  * @return Object 返回requiredType类型对象
  * @throws BeansException
  */
  public static Object getBean(String name, Class requiredType) throws BeansException {
    return applicationContext.getBean(name, requiredType);
  }
    
  /**
  * 获取类型为requiredType的对象
  * 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
  * @param name       bean注册名
  * @param requiredType 返回对象类型
  * @return Object 返回requiredType类型对象
  * @throws BeansException
  */
  public static Object getBean(Class requiredType) throws BeansException {
    return applicationContext.getBean(requiredType);
  }
 
  /**
  * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
  * @param name
  * @return boolean
  */
  public static boolean containsBean(String name) {
    return applicationContext.containsBean(name);
  }
 
  /**
  * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
  * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)  
  * @param name
  * @return boolean
  * @throws NoSuchBeanDefinitionException
  */
  public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.isSingleton(name);
  }
 
  /**
  * @param name
  * @return Class 注册对象的类型
  * @throws NoSuchBeanDefinitionException
  */
  public static Class getType(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.getType(name);
  }
 
  /**
  * 如果给定的bean名字在bean定义中有别名,则返回这些别名  
  * @param name
  * @return
  * @throws NoSuchBeanDefinitionException
  */
  public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.getAliases(name);
  }
}

调用方法:

// 获取inspectionUtil bean
InspectionUtil inspectionUtil = (InspectionUtil) SpringContextUtil.getBean("inspectionUtil");

注:

  1. 使用时会出现无法获取applicationContext,并抛出NullPointerException。 原因:使用此方法必须在spring applicationContext.xml中注入bean。否则spring无法自动调用setApplicationContext。如下

    <bean id="springContextsUtil" class="com.sinosoft.sepmis.util.SpringContextsUtil" ></bean>
    
    
  2. 如果注入后仍然出现这个问题。 则修改中的default-lazy-init=“false”。 或者是修改bean注入属性

    <bean id="springContextsUtil" class="com.sinosoft.sepmis.util.SpringContextsUtil" lazy-init="false"></bean>
    
    

2.4.2 改造2.1的IOC原型代码

  1. 在resources下 添加 beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
           xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework/schema/beans
            https://www.springframework/schema/beans/spring-beans.xsd">
    
        <bean id="mysqlImpl" class="com.torch.dao.UserDaoMySQLImpl"/>
        <bean id="oracleImpl" class="com.torch.dao.UserDaoOracleImpl"/>
    
        <bean id="userServiceImpl" class="com.torch.service.UserServiceImpl">
            <!--
                ref: 引用Spring容器(现在就是xml配置文件)中创建好的对象
                value: 具体的值,基本数据类型!
            -->
            <property name="userDao" ref="mysqlImpl"/>
        </bean>
    </beans>
    
  2. 测试类

    public class MyTest {
        public static void main(String[] args) {
            //获取ApplicationContext
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    
            //容器在手,天下我有,需要什么,就直接get什么!
            //类似于 工厂的模式
            UserService userService = (UserService) context.getBean("userServiceImpl");
    
            userServiceImpl.getUser();
        }
    }
    

这样就可以通过修改配置文件,实现不同的调用(修改 ref )

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

3. IOC创建对象的方式

pojo类 User

public class User {
    private String name;

    public User(){
        System.out.println("User的无参构造!");
    }

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

    public User(TheOne theOne){
    }

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println("name="+name);
    }
}
  1. 默认方法是使用无参构造创建对象,如果没有无参构造方法,且配置文件中没有指明使用有参构造方法,则会报错。

  2. 有参构造有三种方法

    1. 有参构造方法:下标赋值

      <bean id="user" class="com.torch.pojo.User">
          <constructor-arg index="0" value=""/>
      </bean>
      
    2. 有参构造方法: 通过类型赋值

      <bean id="user" class="com.torch.pojo.User">
          <!--第二种参数类型-->
          <!--type的值:基本类型可以直接用,引用类型需要全限定名 -->
          <constructor-arg type="java.lang.String" value=""/>
      </bean>
      

      如果两个参数都是 String 的,就会出现问题,所以不推荐使用

    3. 有参构造方式: 直接通过参数名赋值

      <bean id="user" class="com.torch.pojo.User">
          <constructor-arg name="name" value=""/>
      </bean>
      
    4. 有参构造方式:通过引用赋值(有点类似 通过参数名赋值)

      构造方法的参数是 自定义的bean,ref就可以直接引用这个类 ,引用的bean一定是配置中有的

      <bean id="user" class="com.torch.pojo.User">
          <!--第四种 引用-->
          <constructor-arg ref="one"/>
      </bean>
      <bean id="one" class="com.torch.pojo.TheOne"/>
      

总结: 在配置文件加载时,spring容器中管理的所有对象都会被初始化;而对象的创建遵循单例模式。

单例 explain:

​ 从当前情景出发,单例表现在Spring初始化的时候就会创建,然后getBean时,都是调用同一个对象。可以根据下面的代码进行测试。

User user1 = (User)context.getBean("user");
User user2 = (User)context.getBean("user");
System.out.println(user1 == user2);

4. Spring 配置

配置中的名称都是区分大小写的,在getBean的时候不能大小写混用。

4.1 别名

当原来 bean 的 id 为了规范会比较长,别名就会起到便捷的作用;当然别名不会影响原来 id 的使用。

<!--可以使用别名获取到对象    --> 
<alias name="user" alias="people"/>

相较于它,中的 name 更好用.

4.2 Bean的配置

  • id

    bean 的唯一标识符,也就是相当于对象名

  • class

    bean对象所对应的全限定名:包名+类型

  • name

    也是别名,可以取多个别名(比alias好用很多),多个别名之间可以通过 空格、逗号、分号,做分割

  • scope

    作用域,singleton(单例)、prototype(原型)、request(请求)、session(会话)

<bean id="user" class="com.spring.pojo.User" name="user2,u2 u3;u4" scope="singleton">
</bean>
  • depends-on

    若一个bean是另一个的依赖,则通常意味着将一个bean设为另一个的属性。通常可使用XML形式配置元数据中的元素完成此操作。但有时bean之间的依赖关系不那么直接。一个示例是何时需要触发类中的静态初始化器,例如用于数据库驱动程序注册。depends-on属性可显式强制初始化一或多个使用该元素的bean之前的bean

    • 看如下案例,使用depends-on属性表示对单个bean的依赖关系:

      <bean id="beanOne" class="ExampleBean" depends-on="manager"/>
      <bean id="manager" class="ManagerBean"/>
      
      
    • 要表示对多个 bean 的依赖,请提供 bean 名称列表作为依赖属性的值(逗号、空格和分号都是有效的分隔符):

      <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
      	<property name="manager" ref="manager"/>
          <property name="accountDao" ref="accountDao"/>
      </bean>
      <!-- <property/>还是照常需要的,除非通过注解进行自动装配 -->
      <bean id="manager" class="ManagerBean"/>
      <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao"/>
      

    depends-on属性既可以指定一个 初始化期(initialization-time) 依赖项,也可指定一个对应的析构期(destruction-time)依赖项。在销毁给定bean之前,首先销毁定义与给定bean的依赖关系的依赖bean。因此,depends-on还可以用来控制关闭顺序。

    对应的注解@DependsOn(value = {“dog”,“cat”}),@DependsOn(“cat”)
    注解在需要bean依赖的Bean类上

  • lazy-init

    仅适用于单例bean

    默认的初始化过程中,ApplicationContext会及早地创建并配置所有的单例bean。一般来说,这种预实例化是有好处的,毕竟相比于若干天后的亡羊补牢,这样可立即发现配置或上下文环境的错误。
    当然了,如果你的业务决定了不想要这种默认行为,也可将bean定义标记为延迟初始化来防止对单例bean的预实例化。延迟初始化的bean告诉IoC容器在首次请求时而不是在应用启动阶段就创建一个bean实例

    <bean id="user" class="com.torch.pojo.User" lazy-init="true">
    	<property name="name" value="foo"/>
    </bean>
    

    当上述的配置被 ApplicationContext 使用时,在 ApplicationContext 启动时不会预实例化惰性bean,未使用该属性的非惰性bean才会被预实例化。

    不过需要注意的是,当lazy-init bean是未lazy-init的单例bean的依赖时,ApplicationContext在启动阶段还是会创建lazy-init bean,因为它必须要满足单例的依赖关系,lazy-init bean会被注入到其它未lazy-init 的单例bean中。

    另外如果需要,可通过标签内的 default-lazy-init 属性控制容器级别的延迟初始化,案例如下:

    <beans  default-lazy-init="true"> 
    	... ...
    </beans>
    

    对应注解@Lazy(),注解默认为true,注解在需要延迟实例化的Bean类上

4.3 import

一般用于团队开放,可以将多个配置文件导入合并为一个。 即使用时,只需要使用总的配置文件,在配置文件中导入其他配置文件。

假设,现在项目中有多个人开发,这三个人负责不同的类的开发,不同的类需要 注册在不同的bean中(如 beans1.xml,beans2.xml,beans3.xml … 多个beansXML文件),我们可以用 import 将所有人的 beans.xml合并为一个总的(ApplicationContext.xml)。这样在new ClassPathXmlApplicationContext 的时候就只需要导入 ApplicationContext.xml 即可。但是不同beans之间的bean的id,也需要都是不同,不能取同样的id。

(由创建多个beans.xml资源文件时,上下文环境选择之间创建好的即可)

导入格式:

<import resource="beans.xml"/>
<import resource="beans1.xml"/>

5. 依赖注入(DI)

5.1 构造器注入

3 . 中有参、无参的构造注入

5.2 set方式注入(重点)

set注入 是依赖注入的本质

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性,由容器来注入

注入类型:value 、bean、array、list、map、set、null、properties … …

【环境搭建】

  1. 复杂类型

    public class Address {
        private String address;
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + '\'' +
                    '}';
        }
    }
    
  2. 真实测试对象

    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbies;
        private Map<String,String> card;
        private Set<String> games;
        private String wife;//用来测试空指针
        private Properties info;//配置类
        
        //还有对应得getter&setter&toString方法,这里就省略不写了
    }
    
  3. beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
           xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework/schema/beans
            https://www.springframework/schema/beans/spring-beans.xsd">
    
        <bean id="address" class="com.torch.pojo.Address">
            <property name="address" value="earth"/>
        </bean>
    
        <bean id="student" class="com.torch.pojo.Student">
            <!--第一种,普通值注入,value-->
            <property name="name" value=""/>
    
            <!--第二种,Bean注入,ref;[基本类型是不能ref的]-->
            <property name="address" ref="address"/>
    
            <!--第三种,数组注入,array-->
            <property name="books">
                <array>
                    <value>《红楼梦》</value>
                    <value>《西游记》</value>
                    <value>《水浒传》</value>
                    <value>《三国演义》</value>
                </array>
            </property>
    
            <!--list集合,list-->
            <property name="hobbies">
                <list>
                    <value>听歌</value>
                    <value>敲代码</value>
                    <value>看定影</value>
                </list>
            </property>
    
            <!--map-->
            <property name="card">
                <map>
                    <entry key="身份证" value="12345612345612345678"/>
                    <entry key="银行卡" value="31213131231231324546"/>
                </map>
            </property>
    
            <!--set-->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>COC</value>
                    <value>BOB</value>
                </set>
            </property>
    
            <!--null-->
            <property name="wife">
                <null/>
            </property>
    
            <!--properties-->
            <property name="info">
                <props>
                    <prop key="studentId">20203112314</prop>
                    <prop key="sex"></prop>
                    <prop key="nickname">小明</prop>
                </props>
            </property>
        </bean>
    </beans>
    
  4. 测试类

    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student);
        }
    }
    
基于构造方法 还是 基于setter 的 DI?

两者可以混合使用,构造方法用于强制依赖项,setter方法或配置方法用于可选依赖项。注意,在setter方法上使用@Require 注释可用于使属性成为必需的依赖项,当然必需的依赖项最好还是用构造方法进行注入。

Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,构造函数的参数太多,是不行的,可能功能太过冗余,需要重构进行分离。

Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入

使用对特定类最有意义的 DI 样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 的唯一可用形式。

5.3 拓展方式

p命名空间 和 c命名空间

配置文件(需要添加约束):

xmlns:p="<http://www.springframework/schema/p>"
xmlns:c="<http://www.springframework/schema/c>"

Example:

  • 复杂类: Address 类 见4.2的复杂类型

  • 测试对象:

    public class User {
        private String name;
        private int age;
        private Address address;
        public User() {
        }
        public User(String name, int age, Address address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
        //getter、setter、toString 方法这里就省略不写了
    }
    
  • userbeans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
           xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework/schema/p"
           xmlns:c="http://www.springframework/schema/c"
           xsi:schemaLocation="http://www.springframework/schema/beans
            https://www.springframework/schema/beans/spring-beans.xsd">
    
        <!--p命名空间注入,可以直接注入属性的值:properties-->
        <bean id="user" class="com.torch.pojo.User" p:name="" p:age="18" p:address-ref="address"/>
    
        <bean id="address" class="com.torch.pojo.Address" p:address="asdasd"/>
    	
        <!-- c命名注入,通过构造器注入:construct-args -->
        <bean id="user2" class="com.torch.pojo.User" c:name="" c:_1="20" c:address-ref="address"/>
    
    </beans>
    

    p命名空间 对应了setter注入:

    • p:name [name为成员变量名,p:xxx]: 根据 成员变量名 注入 值
    • p:address-ref[address为成员变量名,p:xxx-ref]:根据 成员变量名 引用 配置(容器)中存在的bean

    c命名空间 对应了构造器注入:

    • c:name[name为成员变量名,c:xxx]:根据 构造函数 的参数名 注入 值
    • c:_1[ _1 指下标为1 的构造函数的参数,c:_x]:根据 构造函数 的参数下标 注入 值
    • c:address-ref[ address 为成员连梁,c:xxx-ref]:根据 构造函数 的参数名 引用 配置(容器)中存在的bean
  • 测试类

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        User user = context.getBean("user",User.class);
        System.out.println(user);
    
        User user2 = context.getBean("user2",User.class);
        System.out.println(user2);
    }
    

使用 @Test 注解需要在parent pom.xml 中导入junit

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

5.4 bean的作用域

  • singleton 单例

    (默认值)在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在。

    <bean id="beanId" class="..." scope="singleton">
    

    可以测试:getBean两次给不同的变量,输出两个变量的等式 或者 输出两个变量的哈希码,测试结果为 true & 相同哈希码

  • prototype 原型

    每次从容器中调用Bean时,都返回一个新的实例。

    <bean id="beanId" class="..." scope="prototype">
    

    同上的测试,结果为 false & 不同的哈希码

  • request、session、application、websocket

    仅当您使用 Web 感知 Spring ApplicationContext 实现(例如 XmlWebApplicationContext)时,请求、会话、应用程序和 websocket 范围才可用。如果将这些范围与常规 Spring IoC 容器(例如 ClassPathXmlApplicationContext)一起使用,则会引发抱怨未知 bean 范围的 IllegalStateException。

    • request 请求

      将单个 bean 定义限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。您可以根据需要更改创建的实例的内部状态,因为从同一 loginAction bean 定义创建的其他实例看不到这些状态更改。它们是针对个人要求的。当请求完成处理时,该请求范围内的 bean 将被丢弃。

      <bean id="loginAction" class="com.something.LoginAction" scope="request"/>
      

      在使用注解驱动的组件或 Java 配置时,@RequestScope 注解可用于将组件分配给请求范围。

      @RequestScope
      @Component
      public class LoginAction {
          // ...
      }
      
      
    • session 会话

      将单个 bean 定义限定为 HTTP 会话的生命周期。应用类似于request,只是范围更大。

      <bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
      

      在使用注解驱动的组件或 Java 配置时,您可以使用 @SessionScope 注解将组件分配给会话范围。

      @SessionScope
      @Component
      public class UserPreferences {
          // ...
      }
      
    • application 应用

      将单个 bean 定义限定为 ServletContext 的生命周期。这有点类似于 Spring 单例 bean,但在两个重要方面有所不同:它是每个 ServletContext 的单例,而不是每个 Spring ApplicationContext(在任何给定的 Web 应用程序中可能有多个),它实际上是公开的,因此可见ServletContext 属性。

      <bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
      

      在使用注解驱动的组件或 Java 配置时,您可以使用 @ApplicationScope 注解将组件分配给应用程序范围。

      @ApplicationScope
      @Component
      public class AppPreferences {
          // ...
      }
      
    • websocket web套接字

      将单个 bean 定义限定为 WebSocket 的生命周期。WebSocket 范围与 WebSocket 会话的生命周期相关联,适用于 STOMP over WebSocket 应用程序,请参阅 WebSocket 范围了解更多详细信息。

6. bean的自动装配

先前在配置文件里写的 都是手动装配,自动装配就是我不去设置,它也能自动的按照之前编写的机制运行。

  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中自动寻找,并自动给bean装配属性

Spring有三种装配方式:

  1. 在xml中显示的配置(之前一直介绍的方式)

  2. 在java中显示配置(Java代码)

  3. 隐式的自动装配bean 重要 现在正要讲的

6.1 场景搭建

cat

public class Cat {
    public void shout(){
        System.out.println("喵喵");
    }
}

dog

public class Dog {
    public void shout(){
        System.out.println("汪汪");
    }
}

people

public class People {
    private Cat cat;
    private Dog dog;
    private String name;
    //省略getter setter toString方法
}

beans.xml

<bean id="cat" class="com.torch.pojo.Cat"/>
<bean id="dog" class="com.torch.pojo.Dog"/>
<bean id="people" class="com.torch.pojo.People">
    <property name="name" value=""/>
    <property name="dog" ref="dog"/>
    <property name="cat" ref="cat"/>
</bean>

MyTest

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = context.getBean("people",People.class);
        people.getDog().shout();
        people.getCat().shout();
    }
}

6.2 byName自动装配

byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的 bean id

需要保证bean的id唯一,并且 容器中 bean id 与 set方法的名称 一致

[会将set方法名生成id进行匹配,如 set方法:setCat =》id:cat ;只要 id 匹配,并且参数为bean或者其父类,都能自动装配成功]

<bean id="cat" class="com.torch.pojo.Cat"/>
<bean id="dog" class="com.torch.pojo.Dog"/>
<bean id="people" class="com.torch.pojo.People" autowire="byName">
    <property name="name" value=""/>
</bean>

6.3 byType自动装配

byType:会自动在容器上下文中查找,和自己对象set属性类型(class)相同的bean

需要保证所有bean的class唯一,bean 和 set属性 的类型一致

好处:可以省略 id

[类似byName,根据 set方法的参数类,去匹配容器中的 bean的class ;set方法与id是否匹配就没有要求了]

<bean id="cat" class="com.torch.pojo.Cat"/>
<bean id="dog" class="com.torch.pojo.Dog"/>
<bean id="people" class="com.torch.pojo.People" autowire="byType">
    <property name="name" value=""/>
</bean> 

6.4 使用注解实现自动装配

ps:jdk1.5支持注解,Spring2.5开始支持注解。

注解相比于xml哪个方法更好,需要视情况而定。

使用注解须知

  1. 导入约束 xmlns:context="http://www.springframework/schema/context"

  2. 配置注解的支持 <context:annotation-config/>

    <!-- 其实代码很好改,把beans的部分复制后改成context就行了 -->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework/schema/beans"
        xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework/schema/context"
        xsi:schemaLocation="http://www.springframework/schema/beans
            http://www.springframework/schema/beans/spring-beans.xsd
            http://www.springframework/schema/context
            http://www.springframework/schema/context/spring-context.xsd">
        
        <context:annotation-config/>
    </beans>
    
  3. 指定要扫描的包,指定包下的注解才能生效(可以指定多个包,用 空格、逗号、分号,做分割),同时使用指定扫描的包 之后就不需要在写配置注解的支持了,因为"指定扫描的包"包含了"配置注解的支持"。

    <context:component-scan base-package="com.torch.pojo"/>
    
    
6.4.1 @Autowired
  1. 用于构造函数

    public class People {
        private Cat cat;
        @Autowired
        public People(Cat cat){this.cat = cat;}
        //... ...
    }
    

    从 Spring Framework 4.3 开始,如果目标 bean 仅定义一个构造函数开始,则不再需要在此类构造函数上使用 @Autowired 注释。但是,如果有多个构造函数可用并且没有 主/默认构造函数,则必须至少使用 @Autowired 注释其中一个构造函数,以指示容器使用哪一个。有关详细信息,请参阅 构造函数解析 的讨论。

  2. 用于传统的 setter 方法 更推荐

    public class People {
        private Cat cat;
        @Autowired
        public void setCat(Cat cat) {
            this.cat = cat;
        }
        // ...
    }
    
  3. 用于具有任意名称和多个参数的方法

    public class People {
        private Cat cat;
        private Dog dog;
        @Autowired
        public void prepare(Cat cat,Dog dog) {
            this.cat = cat;
            this.dog = dog;
        }
        // ...
    }
    
  4. 用于字段,甚至可以将其与构造函数混合使用

    在属性(字段)上使用时,set方法可以被省略,前提是自动装配的属性在IOC(Spring)容器中存在。

    public class People {
        private Cat cat;
        @Autowired
        private Dog dog;
        @Autowired
        public People(Dog dog) {
            this.dog = dog;
        }
        // ...
    }
    

    确保容器中的 bean(例如,Cat 或 Dog )与 @Autowired 注释的注入点的 类型始终一致。否则,注入可能会由于运行时出现“找不到类型匹配”错误而失败。

  5. 用于 需要该类型数组的 字段或方法

    来指示 Spring 从 ApplicationContext 提供特定类型的所有 bean

    public class People {
        @Autowired
        private Cat[] cats;
        //OR
        //@Autowired
        //public void setCats(Cat[] cats){this.cats = cats;}
        // ...
    }
    
  6. 用于类型化集合

    public class People {
        private Set<Cat> cats;
        @Autowired
        public void setCats(Set<Cat> cats) {
            this.cats = cats;
        }
        // ...
    }
    

    如果您希望数组或列表中的项目按特定顺序排序,您的目标 bean 可以实现 org.springframework.core.Ordered 接口或使用@Order 或标准@Priority 注释。否则,它们的顺序遵循容器中相应目标 bean 定义的注册顺序。

    注解@Order 或者 接口Ordered 的作用是定义Spring IoC容器 中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响

    • @Order的注解源码解读

      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
      @Documented
      public @interface Order {
      	/**
      	 * 默认是最低优先级,值越小优先级越高
      	 */
      	int value() default Ordered.LOWEST_PRECEDENCE;
      }
      
      • 注解可以作用在类(接口、枚举)、方法、字段声明(包括枚举常量);
      • 注解有一个int类型的参数,可以不传,默认是最低优先级
      • 通过常量类的值我们可以推测参数值越小优先级越高
    • Ordered接口类

      package org.springframework.core;
      
      public interface Ordered {
          int HIGHEST_PRECEDENCE = -2147483648;
          int LOWEST_PRECEDENCE = 2147483647;
          int getOrder();
      }
      
    • 代码示例

      实现CommandLineRunner接口的类会在Spring IOC容器加载完毕后执行,适合预加载类及其它资源;也可以使用ApplicationRunner,使用方法及效果是一样的

      @Component
      @Order(1)
      public class BlackPersion implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("----BlackPersion----");
          }
      }
      
      @Component
      @Order(0)
      public class YellowPersion implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("----YellowPersion----");
          }
      }
      

      result:

      ----YellowPersion----
      ----BlackPersion----    
      
  7. 用于类型化的 Map 实例

    只要预期的键类型是字符串映射值包含预期类型的所有 bean包含相应的 bean 名称

    public class People {
        private Map<String, Cat> cats;
        @Autowired
        public void setCats(Map<String, Cat> cats) {
            this.cats = cats;
        }
        // ...
    }
    

    默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配会失败。在声明的数组、集合或映射的情况下,至少需要一个匹配元素

  8. 拓展:

    • @Autowired(required = false) 的用

      我们知道 @Nullable 注解,只要字段标记了这个注解,说明这个字段可以为null(Spring5.0开始可以使用@Nullable)

      public People(@Nullable String name){...}
      

      Autowired 注解有个唯一的属性 required

      public @interface Autowired {
          boolean required() default true;
      }
      

      @Autowired(required = false) 和 @Nullable 的功能相同

      如果显示定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空

      public class People {
          @Autowired(required = false)
          private Cat cat;
          ... ...
      }
      
    • @Autowired 的自动装配方式

      Autowired 注解默认优先ByType进行自动装配,当发现装配类型于spring容器中存在两个及以上实例时,会采用ByName的方式继续寻找对应的实例进行装配。

      如果多个相同类型,可以搭配@Qualifier,按照 @Qualifier(value=“id”) 去辅助配置 @Autowired的使用。

      <bean id="cat1" class="com.torch.pojo.Cat"/>
      <bean id="cat2" class="com.torch.pojo.Cat"/>
      
      public class People{
          private Cat cat;
          @Autowired
          @Qualifier(value = "cat")
          public void setCat(Cat cat){this.cat = cat;}
          ... ...
      }
      
6.4.2 @Resource

(@Resource的作用相当于@Autowired,是Java的注解)

区别于@Autowire

@Resource默认使用ByName装配,如果找不到对应的id,则采用ByType的方式,如果找不到对应的类型或者对应的类型有两个及以上,则会报错。@Resource仅能用在:类、成员变量和方法上

@Resource有两个重要的属性

  • name:使用name属性,Spring将name属性解析为bean的名字;
  • type:使用type属性,Spring将type属性则解析为bean的类型,使用byType自动注入策略;当找多个时,再使用byName的自动注入策略

@Resource装配顺序

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

    @Resource(name = "cat",type = com.torch.pojo.Cat.class)
    private Cat cat;
    
    
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

    @Resource(name = "cat2")
    private Cat cat;
    
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到,抛出异常;找到多个,在从这些里面找到名称(id)匹配的bean进行装配,找不到则抛出异常

    @Resource(type = com.torch.pojo.Dog.class)
    private Dog dog;
    

    我看网上的帖子都是说”指定了type后,仅根据byType进行自动装配,找不到或者找到多个,都抛出异常“,但是我写了个样例,得出的是我上面的结论

    测试类:

    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            People people = context.getBean("people",People.class);
            people.getCat().shout();
        }
    }
    

    Cat:

    public class Cat {
        public void shout(){
            System.out.println("喵喵");
        }
    }
    

    People:

    public class People {
        @Resource(type = Cat.class)
        private Cat cat;
        //getter setter toString 方法省略
    }
    

    beans.xml

    <!--beans等标签省略-->
    <bean id="cat" class="Cat"/>
    <bean id="dog" class="Dog"/>
    <bean id="cat11" class="Cat"/>
    <bean id="dog111" class="Dog"/>
    <bean id="people" class="People"/>
    

    Result:

    喵喵
    

    分析:

    只指定了@Resource的type属性,找到Cat类的bean共有两个,“cat"和"cat11”,如果按照网上的说法应该是抛出异常,但是却没有且输出正常,再当我将id “cat” 改为 “cat0” 的时候在运行,就会抛出异常;所以我觉得,当只指定了@Resource的type属性时,找到多个类型的bean时,会再根据 id 匹配,如果找不到,则抛出异常。

  4. 如果既没有指定name,又没有指定type [默认方式],则自动按照byName方式进行装配;如果找不到,则采用ByType的方式,如果找不到或者找到多个,则抛出异常。

    @Resource
    private Cat cat;
    
    

Reference: 框架学习-Spring踩坑记录

spring的@Resource注解在JDK8以上版本兼容问题

原因:该注解被jdk占用
在使用Spring注解开发中,使用@Resource报空指针异常时有两个解决方案:
1.使用jdk8
2.在maven中的pom.xml从新导入一个javax.annotation的依赖
如下: 建议去maven库查询依赖 [见Main References:MVN Repository]

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

7. 使用注解开发

使用注解需要导入context约束,增加注解的支持。见6.4

在Spring4之后,要使用注解开发,必须要保证 aop 的包导入了

[现在spring-webmvc 包已经自动导入,以后还有没有包含就不知道了]

7.1 场景搭建

User:

public class User {
    private String name;
    //setter getter toString 方法省略
}

测试类:

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

7.2 如何使用注解开发

7.2.1 bean

@Component 组件 (Spring的注解)

注解放在pojo类上,说明这个类被Spring管理了,等价于bean

属性时自动生成的,id 是类名的开头变成小写的格式,class就是全路径类名

@Component
public class User {
    @Value(value = "王")
    private String name;
    //setter getter toString 方法省略
}

7.2.2 属性如何注入

@Value 值(Spring的注解)

唯一的值:value,用来注入属性的值,等价于

注解放在属性 和 set方法上,注入初始值一般属性 和 set方法选一个放就行了。

但是这仅仅适用于简单的属性注入,像map、list等复杂数据类型还是通过配置的形式注入,更加的方便和清楚。

public class User {
    @Value("王")
    private String name;
    
    //OR
    //@Value(value = "王")
    //public void setName(String name){this.name=name;}
    
    //setter getter toString 方法省略
}

7.2.3 衍生的注解

@Component 有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

  • dao 【@Repository

    @Repository
    public class UserDao {
    	... ...
    }
    
    
  • service【@Service

    @Service
    public class UserService {
    	... ...
    }
    
    
  • controller【@Controller

    @Controller
    public class UserController {
    	... ...
    }
    

三个注解和 @Component是一样的功能,都代表将某个类注册到Spring中,装配Bean(需要将扫描包改为"com.torch",覆盖所有注解)。

  1. 自动装配(见6.4)

  2. 作用域

    @Scope (Spring的注解)(默认还是单例模式)

    @Scope("prototype")
    @Component
    public class User {
        @Value(value = "王")
        private String name;
        //setter getter toString 方法省略
    }
    
    
  3. 小结

    注解和xml的对比*

    • xml:更加万能,适用于任何场合!维护简单方便
    • 注解:不是自己类使用不了,灵活性较差,维护相对复杂!

    最好的实践方式

    • xml用于bean的管理,注解只负责完成属性的注入,根据使用环境和实际需求决定。
    • 我们在使用的过程中,需要注意一个基本问题:必须让注解生效,就需要开启注解的约束和注解的支持(或者开启扫描包能够覆盖所有的注解)

8. 使用JavaConfig实现配置(零配置)

完全不适用Spring的xml配置,全权交给java来做。

JavaConfig 是Spring的一个子项目,在Spring 4之后,它成为了一个核心功能!

References for this section

spring @Bean的用法

Spring之@Bean注解详解

Spring的注解 @Bean用法

官网

Spring干货集|Bean依赖你又觉得行了?

8.1 AnnotationConfigApplicationContext

使用AnnotationConfigApplicationContext 实例化Spring 容器

Spring 3.0引入的,这种通用的 ApplicationContext 实现不仅能够接受 @Configuration 类作为输入,还能够接受普通的 @Component 类和使用 JSR-330 元数据注释的类。

8.1.1 以下我会例举一些场景,并作出我个人的测试小结:

  • 成功案例:@Bean用在普通的java类中,不过是通过@Bean注释在配置类的方法上;无论配置类是否注解@Configuration 或者 @Component,都是正确返回。

    public class MyConfig {
        @Bean()
        public User getUser(){return new User();}
    }
    
    public class User {
        @Value("王")
        private String name;
        //setter getter toString 方法省略
    }
    
    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
            User user = context.getBean("getUser",User.class);
            System.out.println(user);
        }
    }
    
  • 失败案例:在bean类中 用@Bean注解方法获取bean,在实例化Spring容器的时候,构造注入bean类。通过类获取bean时,会发现抛出异常:找到两个对应的类型的bean,“user” 和 “getUser”。

    当getBean确定 id 时,User 是否注解 @Component 或者 @Configuration,都是正常输出。

    public class User {
        @Value("王")
        private String name;
        //setter getter toString 方法省略
        @Bean
        public User getUser(){return new User();}
    }
    
    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(User.class);
            User user = context.getBean(User.class);
            System.out.println(user);
        }
    }
    
  • 成功案例:只是将简单的Java类作为输入 实例化Spring容器,Java类注册成了bean类。

    public class MyConfig {
        @Override
        public String toString() {return "MyConfig{}";}
    }
    
    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
            MyConfig myConfig = context.getBean("myConfig", MyConfig.class);
            System.out.println(myConfig);
        }
    }
    
  • 小结

    1. 只要类作为 AnnotationConfigApplicationContext 的输入实例化Spring容器,这些类就会注册为bean;
    2. 只有作为输入类中的@Bean注解的方法,才会生效;
    3. 是否注解@Component 或者 @Configuration 都能正常运行;

    重点:注解的作用就是说明和配置,虽说不进行注解,不影响简单用例的输出,但是在场景情况复杂的时候,会出现一些错误。所以最好在对应的bean类加上@Component注解,在配置类上加上@Configuration注解,并将获取bean类的方法,同一写在配置类中并注解@Bean。

8.2 @Bean

Spring的 @Bean 注解用于表示一个方法实例化、配置和初始化一个由 Spring IoC 容器管理的新对象。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

对于熟悉 Spring 的 XML 配置的人来说,@Bean 注解与 元素的作用相同。

您可以将 @Bean 注释方法与任何 Spring @Component 一起使用。但是,最常与 @Configuration bean 一起使用

8.2.1 完整的@Configuration:

用 @Configuration 注释一个类表明它的主要目的是作为 bean 定义的来源,相当于用 @Configuration 注释的类 管理 实例化、配置等bean对象的方法。此外,@Configuration 类允许通过调用同一类中的其他 @Bean 方法来定义 bean 间的依赖关系。这确保了bean之间的引用是强类型的和可导航的。

最简单的@Configuration 类如下所示:

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

这个配置就等同于之前在xml里的配置

<bean id="myService" class="com.acme.services.MyServiceImpl"/>

8.2.2 “精简”@Bean 模式(lite模式)

什么是lite模式:

当 @Bean 方法在未使用 @Configuration 注释的类中声明时,在 @Component,甚至在普通的旧类中声明的 Bean 方法。

public class MyConfig { //未使用 @Configuration 注释
    @Bean
    public User getUser(){return new User();}
}

public class User {
    @Value("王")
    private String name;
    //setter getter toString 方法省略
}
@Component
public class User { //在 @Component,甚至在普通的旧类(除去@Component) 中声明的 Bean 方法
    @Value("王")
    private String name;
    //setter getter toString 省略
    @Bean
    public User getUser(){
        return new User();
    }
}

lite模式下的Bean方法将被容器视为普通工厂方法(类似于XML中的工厂方法声明 factory-method属性)。

与注释 @Configuration 类中bean方法的语义不同,lite模式不支持“bean间引用”(即不能声明bean间的依赖关系)。相反,它们对其包含组件的内部状态进行操作,并且可以选择对它们可能声明的参数进行操作。因此,这样的@Bean 方法不应调用其他@Bean 方法。每个这样的方法实际上只是特定 bean 引用的工厂方法,没有任何特殊的运行时语义。

在常见情况下,@Bean 方法将在 @Configuration 类中声明,以确保始终使用“完整”模式,并因此将跨方法引用重定向到容器的生命周期管理。这可以防止通过常规 Java 调用意外调用相同的 @Bean 方法,这有助于减少在“精简”模式下操作时难以追踪的细微错误

8.2.3 自定义bean的命名

默认情况下bean的名称和方法名称相同,你也可以使用name属性 或者 value属性 来指定,也可以起许多的别名。

但是只要自定义了bean的名称,则不能再用注解@bean 的方法的名称 来 getBean()。

@Configuration
public class AppConfig {
    @Bean("myFoo") //@Bean(name = {"myFoo","ss"}) OR @Bean(value = "ss") 
    public Foo foo() {
        return new Foo();
    }
}

8.2.4 bean的依赖

@bean 也可以依赖其他任意数量的bean,如果TransferService 依赖 AccountRepository,我们可以通过方法参数实现这个依赖

@Configuration
public class AppConfig {
    @Bean  //带参的方法,注入依赖
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

8.2.5 接受生命周期的回调

任何使用@Bean定义的bean,也可以执行常规生命周期的回调,类似@PostConstruct and @PreDestroy注释。

也完全支持常规的Spring 生命周期回调。如果一个 bean 实现了 InitializingBean、DisposableBean 或 Lifecycle,则容器会调用它们各自的方法。

@Bean 注解支持指定任意初始化和销毁回调方法,很像 Spring XML 的 bean 元素上的 init-method 和 destroy-method 属性,如下例所示:

public class Foo {
    public void init() {
        // initialization logic
    }
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {
    @Bean(initMethod = "init",destroyMethod = "cleanup")
    public Foo foo() {
        return new Foo();
    }
}

默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果你不想执行该方法,则添加@Bean(destroyMethod=“”)来防止出发销毁方法。

对于上述示例中的 Foo,在构造期间直接调用 init() 方法同样有效,如以下示例所示:

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }
    // ...
}

当您直接在 Java 中工作时,您可以对您的对象做任何您喜欢的事情,而不必总是依赖容器生命周期。

8.2.6 拓展

  • @Bean注解的获得bean的方法,除了直接返回bean类,您还可以使用接口(或基类)返回类型声明 @Bean 方法

    如以下示例所示:

    @Configuration
    public class AppConfig {
        @Bean
        public TransferService transferService() {
            return new TransferServiceImpl();
        }
    }
    

    但是,这会将高级类型预测的可见性限制为指定的接口类型 (TransferService)。然后,只有在实例化 受影响的单例 bean 后,容器才知道完整类型 (TransferServiceImpl)。非惰性(Spring容器会及时创建并配置)单例 bean 会根据它们的声明顺序进行实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试通过未声明的类型进行匹配(例如 另一个组件用@Autowired 注解 TransferServiceImpl(成员变量),它仅在 transferService bean 具有已实例化)。【好吧,这一段官网的话,看不太懂(虽然稍微修改了下)】

    如果您始终通过声明的服务接口引用您的类型,则您的 @Bean 返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明最具体的返回类型可能更安全(至少与引用您的 bean 的注入点所要求的一样具体)。

  • @Description

    有时候提供bean的详细信息也是很有用的,bean的描述可以使用 @Description来提供

    @Configuration
    public class AppConfig {
        @Bean
        @Description("Provides a basic example of a bean")
        public Foo foo() {
            return new Foo();
        }
    }
    
  • @Scope

    默认范围是单例,但您可以使用 @Scope 注释覆盖它,如以下示例所示:

    @Configuration
    public class MyConfiguration {
        @Bean
        @Scope("prototype")
        public Encryptor encryptor() {
            // ...
        }
    }
    
    

8.3 @Configuration

@Configuration 是一个类级别的注解,表明一个对象是 bean 定义的来源。 注解 @Configuration 的类 通过 注释@Bean 的方法声明 bean。对@Configuration 类上的@Bean 方法的调用也可用于定义bean 间的依赖关系。

8.3.1 注入Bean间的依赖

当 bean 相互依赖时,表达这种依赖关系就像让一个 bean 方法调用另一个方法一样简单,如以下示例所示:

@Configuration
public class AppConfig {
    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());//⭐
    }
    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中**,beanOne 通过构造函数注入**接收对 beanTwo 的引用。

只有在 @Configuration 类中声明了 @Bean 方法时,这种声明 bean 间依赖关系的方法才有效。您不能使用普通的 @Component 类来声明 bean 间的依赖关系,就是前面提到的lite模式不能进行bean间依赖。

8.3.2 查找方法注入

如前所述,查找方法注入是您应该很少使用的高级功能。在单例范围的 bean 依赖于原型范围的 bean 的情况下,它很有用。使用 Java 进行这种类型的配置为实现这种模式提供了一种自然的方式。下面的例子展示了如何使用查找方法注入:

public abstract class CommandManager {
    public Object process(Object commandState) {
        // 获取相应Command接口的新实例
        Command command = createCommand();
        // 在Command实例(希望是全新的)上设置状态
        command.setState(commandState);
        return command.execute();
    }

    // 好吧……但是这个方法的实现在哪里呢?
    protected abstract Command createCommand();
}

通过使用 Java 配置,您可以创建 CommandManager 的子类,其中抽象 createCommand() 方法被覆盖,从而查找新的(原型)命令对象。以下示例显示了如何执行此操作:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // 根据需要在这里注入依赖项
    return command;
}

@Bean
public CommandManager commandManager() {
    // 使用createCommand()返回CommandManager的新的匿名实现
    // 返回一个新的原型Command对象
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

8.3.3 使用@Import 注解

就像在 Spring XML 文件中使用 元素来帮助模块化配置一样,@Import 注释允许从另一个配置类加载 @Bean 定义,如以下示例所示:

@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}

现在,在实例化上下文时不需要同时指定 ConfigA.class 和 ConfigB.class,只需要显式提供 ConfigB,如以下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

从 Spring Framework 4.2 开始,@Import 还支持对常规组件类的引用,类似于 AnnotationConfigApplicationContext.register 方法。如果您想通过使用一些配置类作为入口点来显式定义所有组件来避免组件扫描,这将特别有用。

8.3.4 在导入的@Bean 定义上注入依赖项

前面的示例(8.3.1)有效,但过于简单。在大多数实际场景中,bean 跨配置类相互依赖。使用 XML 时,这不是问题,因为不涉及编译器,您可以声明 ref=“someBean” 并信任 Spring 在容器初始化期间解决它。当使用@Configuration 类时,Java 编译器会对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法

幸运的是,解决这个问题很简单。正如我们已经讨论过的,@Bean 方法可以有任意数量的参数来描述 bean 依赖关系。考虑以下更真实的场景,其中包含多个 @Configuration 类,每个类都依赖于其他类中声明的 bean:

@Configuration
public class ServiceConfig {
    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {
    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // 所有的东西都跨配置类连接起来…
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有另一种方法可以达到相同的结果。请记住,@Configuration 类最终只是容器中的另一个 bean:这意味着它们可以利用 @Autowired 和 @Value 注入以及与任何其他 bean 相同的其他功能。【这里就不深究了, 官网连接 放这】

8.4 @ComponentScan

8.4.1 组件扫描 @ComponentScan

启用组件扫描,您可以注释您的 @Configuration 类,如下所示:

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    // ...
}

一般@ComponentScan都是注解在配置类上。

有经验的 Spring 用户可能熟悉 Spring 的 context: namespace 中等效的 XML 声明,如下例所示:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在前面的示例中,扫描 com.acme 包以查找任何带有 @Component 注释的类,并且这些类在容器中注册为 Spring bean 定义

8.4.2 scan() - **AnnotationConfigApplicationContext **

AnnotationConfigApplicationContext 公开了 scan(String…) 方法以允许相同的组件扫描功能,如以下示例所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住,@Configuration 类使用@Component 进行元注释(@Configuration 包含 @Component),因此它们是组件扫描的候选对象

在前面的示例中,假设 AppConfig 在 com.acme 包(或下面的任何包)中声明,它会在调用 scan() 期间被拾取。在 refresh() 时,它的所有 @Bean 方法都被处理并注册为容器中的 bean 定义

8.5 实现方式-简单场景演示:

8.5.1 @Configuration + @Bean

在配置类(注释@Configuration)中定义一个方法,并使用@Bean注解声明。

简单示例:

MyConfig:

  • 可以理解成 配置文件 变成 配置类,通过 @Bean + getXxx()方法 实现Bean的注册。
@Configuration
public class MyConfig {
    @Bean
    public User getUser(){
        return new User();
    }
}

User:

@Component//虽说@Component在这样方式下可以不用加,但是为了保证好的规范,最好还是加上@Component和其衍生注解
public class User {
    @Value("王")
    private String name;
    //setter getter toString 方法省略
}

MyTest:

  • 通过配置类获取Spring上下文,通过 new AnnotationConfigApplicationContext 对象,参数为配置类(有多个配置类,可以不断传参)
  • getBean,传入的String,则是注解 @Bean 的方法的名称(默认情况),其他使用方法和用xml的方式相同
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = context.getBean("getUser",User.class);
        System.out.println(user);
    }
}

8.5.2 @Component + @Configuration + @ComponentScan

在Developer类上使用@Component注解,并在配置类上先声明@Configuration,然后再声明@ComponentScan(“User类的路径”),这样会自动扫描@Component并生成Bean。

//@Component的目的是将该类和属性注入
@Component
public class Developer {
    @Value("java")
    private String language;
	// setter getter toString省略
}
//这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component,@Configuration代表这是一个配置类,就和我们之前看到的beans.xml是一样的
@Configuration
@ComponentScan("com.torch.pojo")
public class DeveloperConfig {

}
public void test2(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DeveloperConfig.class);
        Developer bean = (Developer) context.getBean("developer");
        System.out.println(bean.getLanguage());
}

如果两种方法都使用,会建两个对象,@Component建立的对象用getBean(“user”)获取,配置类中@Bean声明的用getBean(“getUser”)获取,这两个对象是不同的。

Debug

  1. 开始创建maven项目的时候会遇到Unresolved plugin

    • problem

      同标题

    • solution

      之前没有配置过maven,下载、安装和配置maven,修改maven中setting的中央仓库镜像(改为阿里云的[需要用最新的镜像]),配置了maven本地仓库的位置

      详细见: csdn贴

本文标签: 学习笔记框架Spring站狂神