注意:改变Test代码不算侵入。改变dao和Service才算破坏开闭原则
修改配置文件XML就更不算侵入了

IOC创建bean的2种方式

  • 如果bean的类里面无参和有参构造都有,那么spring是使用你的『无参构造器』来创建对象,(此时对象的各个属性还没有值),然后再通过『反射』对各个属性赋值。
    • 无参构造在配置加载前就已经构造好了,此时相当于使用默认配置
  • 如果你的类的构造器『只有有参构造器』,而没有无参的构造器,那么,Spring 会调用你有参的构造器去创建这个对象,并同时完成对其属性的赋值。此后,Spring 不再另外对你的属性赋值。
    • 有参构造在配置加载完才能开始进行创建bean(因为需要读取配置的参数给有参构造传参)
  • Spring 官方推荐使用有参构造器创建并初始化对象。有参构造可能会出现循环依赖的问题,见:循环依赖
  • 一切带@Component的注解就相当于<bean></bean>

    xml有参构造的配置

  • 通过xml配置有参构造创建bean,有三种方式

    1. <!-- 第一种根据index参数下标设置 -->
    2. <bean id="userT" class="com.kuang.pojo.UserT">
    3. <!-- index指构造方法 , 下标从0开始 -->
    4. <constructor-arg index="0" value="kuangshen"/>
    5. </bean>
    6. <!-- 第二种根据参数名字设置 -->
    7. <bean id="userT" class="com.kuang.pojo.UserT">
    8. <!-- name指参数名 -->
    9. <constructor-arg name="name" value="kuangshen"/>
    10. </bean>
    11. <!-- 第三种根据参数类型设置 -->
    12. <bean id="userT" class="com.kuang.pojo.UserT">
    13. <constructor-arg type="java.lang.String" value="kuangshen"/>
    14. </bean>

    spring创建bean的三种方式

  • 这三种方式本质都是通过构造函数创建,这是基石。其实这三种基本没什么区别,2,3其实就相当于个简化的工具类而已

  1. 通过构造方法直接创建
  2. 通过工厂类的工厂方法
  3. 通过工厂对象的工厂方法

    @Bean
    //DataSource是一个接口,DruidDataSource是它的一个实现类
    public DataSource dataSource() throws Exception {
     DruidDataSource dataSource = new DruidDataSource();
     dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/scott?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false");
     dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    // ...
    
     return dataSource;
    }
    
    @Bean
     public DataSource dataSource() throws Exception {
         Properties properties = new Properties();
         properties.setProperty("driver", "com.mysql.cj.jdbc.Driver");
      //...
    //该DruidDataSourceFactory其实就是根据传入的配置调用DruidDataSource()构造方法罢了
         DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
         return dataSource;
     }
    

    注入方式

    有三种注入方式

  • 基于字段的依赖注入(有很多缺点)
  • 基于构造函数的依赖注入
    • 对于必须的依赖项,建议使用构造函数注入,使得它们成为不可变的对象且防止为空
  • 基于setter的依赖注入

    • 对于可选的依赖项建议采用setter方式注入
  • 现在普遍采用java配置的方式进行注入,你将@Autowired或者@Resource用于setter方法上或者构造,或者字段注入,这就可以实现三种不同的注入。

    Bean详解

  • pojo即具有getter/setter 方法的类

  • bean:即具有getter/setter 方法,无参构造方法,可序列化的类

    bean的作用域

    重点掌握单例和原型即可,其他作用域只能用于web开发

  • 中文名叫做单例,原型,会话,请求

Spring详解 - 图1
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
Bean作用域都是在bean那里增加一个scope属性即可

  • 单例bean并非线程安全的,要保证bean的线程安全最简单的方法是设置该bean为原型

    配置作用域

    ```java xml配置 添加scope属性即可

注解版 @Component 后添加 @Scope(“…”)

单例和原型还有一种xml配置法和一种注解配置法,这两种只适用于单例和原型

true即单例 false即原型

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON/SCOPE_PROTOTYPE)

**Singleton(单例)**

- **单例作用域在spring里也是bean默认的作用域**
- 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
- Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域(即默认)。

测试:
```java
public void test03(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     User user2 = (User) context.getBean("user");
     System.out.println(user==user2);
}

结果为true。说明无论User创建几个对象,这些对象实际上都是一个单例对象
另外上面的例子也说明bean-user在spring容器中是一个bean对象,但是在类里他可以分化为class属性类不同对象
比如上面的一个bean分化为user1和user2两个对象
Prototype(原型)
即一个bean分化为多个对象,这些对象不==。(即每次getBean时都会创建一个新对象)见单例作用域
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

bean的注入/装配

自动装配

自动装配是使用spring满足bean依赖的一种方法
spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。

即IOC的三种实现方式
这一小节重点看第三种,第二种以后会说

https://www.cnblogs.com/shinubi/p/4184943.html

spring-study/spring-auto-zhuangpei

byName(按名称自动化装配)

有三个类,猫狗人。猫狗是人的宠物,即人拥有猫狗。猫狗各有一个成员方法shout,分别输出喵~汪~
Person类
privat``Cat``cat``;
private Dog dog;
private String str;
...一系列set方法和get方法
Test类
创建People类对象people
people调用猫和狗的shout方法 输出喵~和汪~
XML
按之前的方法依赖是这样创建的
<bean id="dog" class="com.rao.autozhuanpei.Dog"/>
<bean id="cat22" class="com.rao.autozhuanpei.Cat"/>
<bean id="people" class="com.rao.autozhuanpei.People">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="name" value="狂神"/>
</bean>

现在我们把people的bean对象的2个ref所在的property标签删除,增加一个autowire属性,得到
<bean id="people" class="com.rao.autozhuanpei.People" ``**autowire="byName"**``>
<property name="name" value="狂神"/>
</bean>

可以发现没有2个ref属性Test类依然可以调用猫狗的shout方法,输出喵~汪~

因为autowire属性为自动装配,byName属性值会使程序自动寻找set方法名set后面同名的bean对象
bean的第一个弊端:bean的Id必须与set方法的方法名值相同(首字母小写),而bean id又=对应私有成员属性的名字。即private Cat 8cat8,则id和set也得是8cat8
如假设people类里Cat cat的set方法叫做setCatMiao,那么会自动catMiao对象(对象首字母必须小写,后面字母大小写得给set的大小写一样,如catmiao,catMIAO都不可以)(一般也不会乱设置set方法名)

byName还有一个弊端:同一id的bean必须唯一,否则识别不出是哪个bean
比如有2个同名set方法(参数/类型有差异),就识别不出来。
byName不成功报空指针异常(找不到)

byType(按类型自动装配)

byType弊端则是同一类型的bean必须唯一,否则失败。同时要保证bean类型与自动注入(set)的类型一致
byType不成功报不唯一异常(NoUniqueBeanDefinitionException)(识别不了)
实践测试:
将byName项目的autowire值改为byType。即可
如果增加一个同类型的bean,则报错

注解实现自动装配

  • (比xml配置更方便)实际上绝大多数时候都使用注解开发

java1.5以上,spring2.5以上才支持注解。不过话说现在也没人用这么老的版本继续开发吧,也就些老项目还用

在spring4之后,想要使用注解形式,必须得要引入aop的包,还要导入约束(xml中)
约束:xmlns:context``=``"http://www.springframework.org/schema/context" 放xml的其他xmlns旁边
配置注解支持:<context:annotation-config/>

spring-study/spring-zhujie 基于spring-autozhuangpei

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
             <context:annotation-config/>


  <bean  id="dog" class="com.rao.zhujie.Dog"/>
    <bean  id="cat" class="com.rao.zhujie.Cat"/>
    <bean id="people" class="com.rao.zhujie.People"/>
</beans>
  1. 在成员变量/set方法前加上@Autowired,bean对象配置按最简单的形式配置。即可实现注解自动装配
  2. 自动装配的对象得在IOC容器中存在,一个对象时是byType装配(名字可随意),多个同类型对象时是先byType再byName装配(只选择同类型中符合名字的,一个都不符合报错)
  3. 用autowired实现自动装配可以省略不写set方法
  4. 注解实现自动装配时,String类型的变量必须单独设置bean,不能像以往一样用子标签设置.比如这里的People的name可以这样设置

<bean id="name" class="java.lang.String">
<constructor-arg name="original" value="kuang"/> </bean>
其他基本类型还不清楚,arg name也不清楚设置什么,先按original设置

  • **@Qualifier**用于搭配着autowired使用。假如**@autowired**有多个对象时,按byType+Name装配。又想选择不符合的name的对象,就在@autowired后再加一个**@Qualifier(name="?")**
    • 比如注解自动装配name,可以设置多个name的bean对象。想用哪个name值修改**@Qualifier的value就好了(感觉有点鸡肋,既然还是得修改直接修改namebean的value不更方便吗)**
    • @Qualifier用于注入方法的参数时可以单独使用,相当于隐藏了@autowired,如X(@Qualifier("dynamicDb") DataSource ds){}
  • @Resource(name,type)自动装配,type为bean的class
    • resource注解是j2ee的注解,而@Autowired是spring的注解。使用autowired需要有@Controller、@Service、@Component、@Repository等注解
    • resource一个就相当于autowired或者@Autowired+@Qualifier的功能结合

有一个value属性,未设置时相当于@Autowired。设置时相当于@Autowired+@Qualifier
Resource有一个参数时根据参数找name/type,无参时则是先byName再byType。2个参数时则全都要符合

  • @Value 仅仅读取配置文件注入基本类和string等
  • @AutoWired
  • @Resource

    属性值设置为空

    **@Autowired**有一个默认属性为required值为布尔值,默认为真
    注解设置属性的方法 @?(属性=...)
    如果设置为false,则允许xml里不创建该bean对象比如上面的例子private Cat cat的注解属性值默认为true,则xml里cat的bean对象必须创建。否则报错
    *(测试时先把各个类中的cat调用成员变量/方法的语句删除了,不然无论false还是true,只要没cat这个对象,就报错,这违背了最基本的原则)

还有一个构造器属性注解 **@Nullable** 使用时写在构造器参数列表最前面即可,用于基本类型的构造器
比如public People(@Nullable String name){...}
设置该注解允许一个基本类型值为空(即允许xml里不设置bean对象)

基本类型值设置可以为空是2种方法都可以,其他类型第一种。反正都用第一种就好了

可选注入

  • 默认情况下,当我们标记了一个@Autowired后,Spring如果没有找到对应类型的Bean,它会抛出NoSuchBeanDefinitionException异常。可以给@Autowired增加一个required = false的参数。

    • 找到就注入,找不到就算了,忽略掉不报错
      @Component
      public class MailService {
      @Autowired(required = false)
      ZoneId zoneId = ZoneId.systemDefault();
      ...
      }
      

      注入顺序

  • 在允许的情况下,java会自动将所有能注入的bean都进行注入,这时可以手动指定bean的注入顺序

  • 使用@Order(x) x为>=1的整数 数字越小优先级越大

    public interface Validator {
      void validate(String email, String password, String name);
    }
    
  • 下面三个实现类我觉得可以写为一个实现类,应该是为了展示一次多个注入的情况吧 ```java @Component public class EmailValidator implements Validator { public void validate(String email, String password, String name) {

      if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
          throw new IllegalArgumentException("invalid email: " + email);
      }
    

    } }

@Component public class PasswordValidator implements Validator { public void validate(String email, String password, String name) { if (!password.matches(“^.{6,20}$”)) { throw new IllegalArgumentException(“invalid password”); } } }

@Component public class NameValidator implements Validator { public void validate(String email, String password, String name) { if (name == null || name.isBlank() || name.length() > 20) { throw new IllegalArgumentException(“invalid name: “ + name); } } }

```java
@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}
@Component
@Order(1)
public class EmailValidator implements Validator {
    ...
}

@Component
@Order(2)
public class PasswordValidator implements Validator {
    ...
}

@Component
@Order(3)
public class NameValidator implements Validator {
    ...
}

条件注入

  • 条件注入提供了很多注解,都是@Conditional?的形式,这里只列举4个个人觉得有用的
  • 以下配置文件都是yaml型,properties是否可以不清楚

    • @ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
    • @ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean) 如一个类被maven引入了依赖,即被spring创建并存在
      • 参数为?.class多个类时{?.class,?.class}
    • @ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
    • @ConditionalOnProperty
      • prefix为配置前缀 name为配置值 havingValue为期望值,如果读取到的值与期望值不符则false matchIfMissing为当不存在这个配置参数时,结果为true还是false
    • @ConditionalOnMissingBean:当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的bean只有一个。如一些默认配置类会有该注解,表示允许用户自定义 不清楚和@scope区别,而且spring默认就是单例,这个不知道和单例区别

      @ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
      表示当这3个类存在时,才会创建bean    这里用于判断相关依赖是否被引入
      
      @Configuration
      public class WebConfig {
      
      @Bean
      @ConditionalOnProperty(prefix = "rest", name = "auth-open", havingValue = "true", matchIfMissing = true)   //rest.auth-open
      public AuthFilter jwtAuthenticationTokenFilter() {
         return new AuthFilter();
      }
      }
      

      先注入后续创建

  • 一般用于配置类

    @ConfigurationProperties    //这里读取配置文件注入到属性中,但是没有@Component等注解,所以没有bean
    public class UploadConfig {...}
    //------------------------------------------
    @EnableConfigurationProperties(MultipartProperties.class)  //这里是对类进行bean创建
    public class UploadConfig {
      private final MultipartProperties multipartProperties;
    
      @Autowired
      public UploadConfig(MultipartProperties multipartProperties) {   //注入bean
          this.multipartProperties = multipartProperties;
      }
    }
    

    创建第三方bean

  • 如果一个Bean不在我们自己的package管理之内,例如jdk中某个类的对象,如何创建它?

    • 在配置类中创建一个方法用于创建第三方bean,然后给该方法标记@Bean
    • **@Bean和方法**即创建一个容器对象,就等同于bean标签。bean id为方法名,bean class为返回值类型
    • 自定义类直接返回一个new即可,但是这并不是叫你直接调方法来创建个对象! 而是应该让spring帮我们调
  • 注意使用了@bean就不要再使用@Component,即便设置了别名也会出现NoUniqueBeanDefinitionException之类型重复。 后面的情景复现不知道为什么没有出现该错误。。。
  • @Bean比@Component更加灵活些,如下,还可以初始化bean并通过set进行值设置,也可以通过构造方法设置

    @Configuration
    @ComponentScan
    public class AppConfig {
      // 创建一个Bean:
      @Bean
      UserInfo createUserInfo() {
          UserInfo userinfo=new UserInfo("18");
          userinfo.setID("001");
          userinfo.setName("rao");
          return userinfo;
      }
    }
    

    初始化与销毁bean

  • 有些时候,一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。

    • 我们通常会定义一个init()方法进行初始化,定义一个shutdown()方法进行清理,然后,引入JSR-250定义的Annotation

Spring容器会对MailService的Bean做如下初始化流程:

  • 调用构造方法创建MailService实例;
  • 根据@Autowired进行注入;
  • 调用标记有@PostConstruct的init()方法进行初始化。

而销毁时,容器会首先调用标记有@PreDestroy的shutdown()方法。
Spring只根据Annotation查找无参数方法,对方法名不作要求。

导入依赖
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>




@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Shutdown mail service");
    }
}

别名

注解配置别名

  • 有的时候我们需要创建不同的对象,如建立多个数据库连接。这时如果直接使用@Bean创建多个方法,会报bean重复。可以使用别名来创建不同的bean。
    • bean同名或者同类型都会报错,所以都要指定别名

  • 使用@Bean("name")或者@Bean+@Qualifier("name")来指定别名
  • @bean后添加@Primary来指定主要bean。如果出现重复bean就默认使用主要bean,如果要使用非主要bean,就用别名来指定使用从bean

    @Configuration
    @ComponentScan
    public class AppConfig {
      @Bean
      @Primary // 指定为主要Bean
      @Qualifier("z")
      ZoneId createZoneOfZ() {
          return ZoneId.of("Z");
      }
    
      @Bean 
      @Qualifier("utc8")  使用从bean
      ZoneId createZoneOfUTC8() {
          return ZoneId.of("UTC+08:00");
      }
    }
    

    xml配置别名

    alias 设置别名 , 为bean设置别名 , 可以设置多个别名
    <!--设置别名:在获取Bean的时候可以使用别名获取-->
    <alias name="userT" alias="userNew"/>
    alias没什么用,被name别名完爆


id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,逗号,分号,空格都可以隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名

<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
这里就设置了4个别名


导入其他人的配置文件。这样我们就能使用其他配置的bean对象
<import resource="{path}/beans.xml"/>
团队合作常常用到,比如多个人负责不同的类,每个人创建了不同的配置文件和bean对象。最后模块整合为一个项目,就创建一个xml,把所有其他的xml导入。


如果不同xml有重名bean,就需要多设置些别名。导入时就会自动选择不重名的别名使用

FactoryBean

  • 使用bena工厂创建bean需要实现bean工厂接口。
  • 当一个Bean实现了FactoryBean接口后,Spring会先实例化这个工厂,然后调用getObject()创建真正的Bean。getObjectType()可以指定创建的Bean的类型,因为指定类型不一定与实际类型一致,可以是接口或抽象类。

因此,如果定义了一个FactoryBean,要注意Spring创建的Bean实际上是这个FactoryBean的getObject()方法返回的Bean。为了和普通Bean区分,我们通常都以XxxFactoryBean命名

@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {

    String zone = "Z";

    @Override
    public ZoneId getObject() throws Exception {
        return ZoneId.of(zone);
    }

    @Override
    public Class<?> getObjectType() {
        return ZoneId.class;
    }
}

依赖注入(DI)

依赖 : 指Bean对象的创建依赖于容器 .
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配
有三种方式注入:

  1. 构造器注入
  2. set方法注入。set方法注入就是依赖注入的核心
  3. 扩展方式注入(即命名空间注入)

    Set方法注入

    DI即怎么把对象注入到容器内(即怎么设置bean对象)这一节就是教你怎么给各种类型的对象创建bean
    如基本类型以外的类型,如集合对象,类对象怎么创建bean

    spring-study/spring-DI

@Address类    私有String类型变量address以及set,get
@Student类    
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String>card;
private Set<String>games;
private String wife;
private Properties info;//配置文件
.......一系列get与set方法
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
还有一个toString方法输出所有的成员属性

一个bean对象可以有很多个标签。也有独属于它自己的子标签,如array,map

<bean id="address" class="com.rao.pojo.Address"></bean>
<bean id="student" class="com.rao.pojo.Student">
基本类型值注入<property name="name" value="饶泽东"></property>
              value可以不跟name写一起,单独做一个标签,这时不需要双引号        <value>饶泽东</value>

Bean对象注入<property name="address" ref="address"></property>

数组注入<property name="books">
            <array>
                <value>红楼梦</value>
                <value>水浒传</value>
                <value>死国年纪</value>
            </array>
        </property>

List注入<property name="hobbys">
            <list>
                <value>听歌</value>
                <value>看电影</value>
                <value>爬山</value>
            </list>
        </property>

Map注入<property name="card">
            <map>
                  <entry key="中国邮政" value="456456456465456"/>
                <entry key="建设" value="1456682255511"/>
            </map>
        </property>

Set注入,这个是Set类型的注入。DI里的set注入是实现方式,是通过set方法注入。此Set非彼set
  <property name="games">
            <set>
                <value>LOL</value>
                <value>BOB</value>
                <value>COC</value>
            </set>
        </property>

NULL注入    <property name="wife"><null/></property>  <null/>相当于一对完整的无值的<null>标签

Properties注入   Properties配置文件的写法类似Map的key-value
  <property name="info">
            <props>
                <prop key="学号">20190604</prop>
                <prop key="性别">男</prop>
                <prop key="姓名">小明</prop>
            </props>
        </property>

最常用的就基本类型注入和Bean注入

扩展方式注入(命名空间)

好处是可以直接在bean id那里注入属性的值,不需要标签
但是p和c命名空间注入需要先导入xml约束才能使用

p命名注入(可以看成:无有参构造器注入)

1、P命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p"


<``bean`` ``id``=``"user"`` ``class``=``"com.kuang.pojo.User"`` ``p:name``=``"狂神"`` ``p:age``=``"18"``/>

Test
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
``User`` ``user`` ``=``context``.``getBean``(``"user",User.class``);
``System``.``out``.``println``(``**user**``);
结果:User{“name=狂神”,”age=18”}
这种方法会自动调用get-set-toString方法

c命名注入(可以看成有参构造器注入)

导入约束 : xmlns:c="http://www.springframework.org/schema/c"

``<``bean`` ``id``=``"user"`` ``class``=``"com.kuang.pojo.User"`` ``c:name``=``"狂神"`` ``c:age``=``"18"``/>
需要增加一个有参构造器
Test:和p命名注入一样
输出结果:和p命名注入一样

注解运用补充

注解环境之前说过,约束加依赖。见https://www.yuque.com/rain_/gl7kf7/wowarm#GQhlc

注解设置属性是@?(属性=…)
注解设置参数是@?(…)
注解参数只能有一个,也不能重复注释(>8高版本注解依赖允许重复注解)所以别想着用注解创建多个对象,注入多个值什么的
小提醒:spring里只有创建私有成员属性的bean才要求创建set,get方法。公有成员不需要set,get方法也可以直接创建bean对象

@Component三个衍生注解

SpringMVC中,给对应的架构层类添加以下注解即表示该类属于哪个架构层。即表示将该类交由spring容器托管
用法为:写在类前

  • @Controller:web层(表示层)
  • @Service:service层(业务逻辑层)
  • @Repository:dao层(数据访问层)
    • 该注解作用类似于@Mapper,即自动创建bean 没有则会报找不到mapper接口…
      • 但是@Repository只有创建bean的功能,而@Mapper是既创建bean并且使bean被spring扫描到。所以@Repository要配合@MapperScannerConfigurer使用才有效
      • mapperscanmappermybatis的注解,mapperscan作用等同于mapper,在mapper接口很多时,一个mapperscan就可以扫描全部mapper类,不需要每一个类都写mapper。

很多时候写Component和写他的延伸注解都一样的。但是注释的作用不一样,衍生可以表示是哪个层(还有注释作用)
让指定包下的注解生效,交由IOC统一管理

注解与XML联系

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

所以推荐按照以下的方式搭配使用

  1. xml管理Bean
  2. 注解完成属性注入
  3. 使用过程中, 可以不用扫描,扫描是为了类上的注解

当然还是看公司要求和你自己,我偏要全用注解,写的时候爽歪歪,如果要维护就哭唧唧
小项目也可以全用注解

用java完全替代XML配置

Test类:
测试类把通过xml寻找上下文改为通过java配置类寻找上下文
ApplicationContext context=``new ``AnnotationConfigApplicationContext(Appconfig.``class``)``;
//通过xml寻找上下文参数是**?.xml** 通过java配置类寻找上下文参数是**?.class** 不是.java
User user=context.getBean(``"getuser"``,``User.``class``)``;
getBean参数为bean id,而@Bean的方法名即bean id

结果:输出sout(user.toString) -> rao

补充

  • SpringBoot在没配置@ComponentScan的情况下,默认只扫描和主类处于同包下的Class。要使用其他包的类就得配置,哪里要用其他包,就在要用的类上加个@scan
    • xml中的<context:component-scan base-package="com.kuang.pojo"/>
    • 在注解中为扫描包时@ComponentScan("com.kuang.pojo") 扫描类时则为@ComponentScan(basePackages ={"com.xkcoding.task.job"} )
      • 多个类用逗号分隔

用法:因为指定注解扫描包是一个配置,所以自然写在java配置类中
写在@config后

xml中的<import/>在注解中等于@import
如果要导入其他配置类,就在@config后写上@import("config2.class")

循环依赖(循环引用)

  • 如果spring容器中2个对象相互引用,就可能会遇到循环引用的问题
    • 解决方法是对其中一个bean所需的对方对象的参数添加@Lazy注解,该注解会给该类生成一个代理对象传入而非真实对象,因此既完成了传参构造又避免了循环依赖
  • 循环依赖只会发生在使用有参构造进行创建bean的时候 ```java public class Husband { private Wife wife; … }

public class Wife { private Husband husband; … } //对应的配置bean //给Wife参数前面添加@Lazy,就会给Husband的构造传个Wife的代理对象 @Bean public Husband husband([@Lazy]Wife wife) { … } @Bean public Wife wife(Husband husband) { … }

<a name="T9Usm"></a>
# @Import

- 用于多模块的时候,多模块时bean可能分散在多个子模块中。貌似其他模块的bean不能被自动识别到。这时需要手动注入获取
```java
一个独立的配置类:ConfigA.java

@ComponentScan("com.example.commandpattern.config.a")
public class ConfigA {

    @Bean
    public String demo() {
        return "hello world";
    }
}
另一个独立的配置类:ConfigB.java

@ComponentScan("com.example.commandpattern.config.b")
public class ConfigB {

    @Bean
    public LocalDate localDate() {
        return LocalDate.now();
    }
}
主配置类:MainConfig.java

// 主配置类引入各个独立配置类
@Import({ConfigA.class, ConfigB.class})
public class MainConfig {
}
引入,使用:

// 只需要将主配之类交给 Spring IoC 容器即可。
public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    System.out.println(context.getBean(String.class));
    System.out.println(context.getBean(LocalDate.class));
    System.out.println(context.getBean(StudentDao.class));
    System.out.println(context.getBean(StudentService.class));
}

单例bean中注入多例bean

  • 即要在单例bean中获取非单例的bean,详见链接