在上节Spring(9)中记录了Spring2.X开始使用的几个基础注解,但是其功能仅是简化XML配置,需要和XML配置文件配合使才行,并且基础注解只能创建程序员自己开发的对象,并不能满足全注解开发的需要,所以从Spring3.X开始,引入了相关注解,使注解完全代替了XML配置文件并且可以创建任意对象并注入。

接下来就看看Spring3.X出现的高级注解:

一、配置Bean

@Configuration注解又叫配置Bean,在类上加入该注解的作用是声明该类成为XML配置文件(和XML配置文件具有相同作用),等效↓:

26.PNG

这样就代替了XML配置文件,那么在创建工厂加载配置文件时只需要找到这个类或者扫描这个类所在包即可,这点在后续多配置文件整合时会细致展开:

1.创建工厂代码
ApplicationContext ctx = new AnnotationConfigApplicationContext();
2.指定配置文件
  (1)指定配置Bean的对应class
     ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig4.class);
  (2)指定配置Bean所在的路径
     ApplicationContext ctx = new AnnotationConfigApplicationContext("com.jin.config");
  (3)在(1)的基础上,扫描多个class

@Configuration注解的本质也是@Component注解的衍生注解,可以应用<context:component-scan进行扫描。

一个问题,基于注解开发集成日志的话,不能集成log4j,集成logback,相关依赖

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.logback-extensions</groupId>
            <artifactId>logback-ext-spring</artifactId>
            <version>0.1.4</version>
        </dependency>

logback.xml写法:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 控制台输出 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,
            %-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}
                [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
二、@Bean注解

可以说是重中之重了,前面都不看这块也要会,当然要前面都不看这块理解的肯定也不透。

贴两个文章,一个是讲@Bean和@Componet的区别,还一个是@Bean是否要跟@Configuration配合使用:

1、@Bean和@Componet区别

2、@Bean是否要跟@Configuration配合使用

1的答案研究完接下来的内容就比较清晰了,@Bean更加灵活(Bean比Component的自定义性更强。可以实现一些Component实现不了的自定义加载类。) 2的话答案是否,但是不好(@Bean能不能单独在方法上注解?可以,但是并不会注入到Spring的IOC容器中,相当于有没有注解都一样)

接下来就细致研究@Bean注解:

1、@Bean注解的基本使用

创建对象,如图,很清楚:

27.PNG

上图是使用@Bean注解进行简单对象的创建,他灵活强大的地方就在复杂对象也可以这么创建,因为方法体里的代码是自定义的,只需要写完创建对象实例的逻辑后,返回该复杂对象即可,用Connetion为例:

    //创建复杂对象
    //Connection 不能直接通过new 创建
    @Bean
    public Connection conn() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring_mybatis", "root", "1");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

用FactoryBean优化(一般整合遗留系统时用):

package com.jin.bean;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * @author jinyunlong
 * @date 2021/12/27 15:38
 * @profession ICBC锅炉房保安
 */
public class ConnectionFactoryBean implements FactoryBean<Connection> {
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring_mybatis", "root", "8");
        return conn;
    }

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

    @Override
    public boolean isSingleton() {
        return false;
    }
}
    @Bean
    public Connection conn1(){
        ConnectionFactoryBean factoryBean = new ConnectionFactoryBean();
        Connection conn = null;
        try {
            conn = factoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

然后只需要在获取对象时直接用工厂getBean这个方法名就行:

    //用于测试:@Bean注解
    @Test
    public void test2(){
//        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.jin");
        User user = (User) ctx.getBean("ssss");
        System.out.println("user = " + user);

//        Connection conn = (Connection) ctx.getBean("conn");
//        System.out.println("conn = " + conn);
        Connection conn1 = (Connection) ctx.getBean("conn1");
        System.out.println("conn1 = " + conn1);
    }

自定义id值: @Bean("id")

控制对象创建次数: @Bean下面加入注解 @Scope("singleton|prototype") 默认值 singleton

2、@Bean注解的注入

用户自定义类型注入,以UserDAO和UserService为例,只需要在@Bean注解方法中调用setDAO方法,并返回UserService对象即可:

    @Bean
    public UserDAO userDAO(){
        return new UserDAOImpl();
    }

    @Bean
    public UserService userService(UserDAO userDAO){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }

简化写法,形参不要了,直接在set方法里使用userDAO():

    @Bean
    public UserService userService(){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO());
        return userService;
    }

JDK类型的注入:

    @Bean
    public Customer customer(){
        Customer customer = new Customer();
        customer.setId(1);
        customer.setName("name");
        return customer;
    }

如上无论是自定义类型的注入和JDK类型的注入都无法解耦合,先看对JDK类型注入的解耦合咋做,和上节基础注解一样,使用@PropertySource("")注解加载配置文件,然后使用@Value进行值的注入即可:

    @Configuration
    @PropertySource("classpath:/init.properties")

    @Value("${id}")
    private Integer id;
    @Value("${name}")
    private String name;


    @Bean
    public Customer customer(){
        Customer customer = new Customer();
        customer.setId(id);
        customer.setName(name);
        return customer;
    }

自定义类型注入解耦合也差不多,用其他注解引入XML配置文件覆盖原对象就完事了,下面说。

3、@ComponentScan注解

概念和基本使用

扫描包中的类到Spring容器去
@ComponentScan注解在配置bean中进行使用,等同于XML配置文件中的
<context:component-scan>标签
目的:进行相关注解的扫描 (@Component @Value ...@Autowire
@Configuration
//@ComponentScan(basePackages = "com.jin.scan",
//               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class}),
//                                 @ComponentScan.Filter(type = FilterType.ASPECTJ,pattern = "*..User1")})
@ComponentScan(basePackages = "com.jin.scan",useDefaultFilters = false,
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})})
public class AppConfig2 {
}

上面注解中的参数还带着扫描包含和排除的使用,其实就是把上节标签中的使用方式挪到注解里用了。

4、Spring工厂创建对象的多种配置方式

28.PNG

这里就比较灵活了,3、4用的很少,1、2根据需求灵活搭配使用即可。

配置优先级的问题:

@Component及其衍生注解 < @Bean < 配置文件bean标签
优先级高的配置 覆盖优先级低配置
@Component
public class User{

}
@Bean
public User user(){
  return new User();
}
<bean id="user" class="xxx.User"/>
配置覆盖:id值 保持一致

优先级高的配置可以覆盖优先级低的配置,那么就能用XML配置文件对@Bean中自定义类型对象解耦合了,加入@ImportResource("applicationContext.xml")注解,和@PropertySource("classpath:/init.properties")一样,如下,这样对id=userDAO注入的就是UserDAOImplNew对象,一定要注意bean id保持一致才能覆盖:

@Configuration
@ImportResource("applicationContext.xml")
public class AppConfig4 {

    @Bean
    public UserDAO userDAO(){
        return new UserDAOImpl();
    }
}

    <bean id="userDAO" class="com.jin.injection.UserDAOImplNew" />
5、整合多个配置信息

在用XML配置文件时有看到两种方式用多配置文件进行整合,这样做的好处是拆分多个配置bean的开发,是一种模块化开发的形式,也体现了面向对象各司其职的设计思想,(比如DAO一个,Service一个,Controller一个),同样的,使用注解也可以,文章开头写了@Configuration又叫配置Bean,其作用就是代替XML,那么我们整合只需要多写几个@Configuration然后放一起就可以了。

多配置信息整合的方式
   多个配置Bean的整合
   配置Bean与@Component相关注解的整合
   配置Bean与SpringXML配置文件的整合
整合多种配置需要关注那些要点
   如何使多配置的信息 汇总成一个整体
   如何实现跨配置的注入

首先看多个配置Bean的整合,三种方式:

1、base-package进行多个配置Bean的整合:

29.PNG

相当于XML配置中的applicationContext-*.xml

2、使用@Import注解

30.PNG

相当于XML配置中的import标签。

3、在工厂创建时,指定多个配置Bean的Class对象 【了解】

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig4.class, AppConfig5.class);

跨配置进行注入

在应用配置Bean的过程中,不管使用哪种方式进行配置信息的汇总,其操作方式都是通过成员变量加入@Autowired注解完成。

这块开始看可能会比较乱,但是更能凸显他的灵活。

@Configuration
@Import(AppConfig2.class)
public class AppConfig1 {
   @Autowired
   private UserDAO userDAO;
   @Bean
   public UserService userService() {
      UserServiceImpl userService = new UserServiceImpl();
      userService.setUserDAO(userDAO);
      return userService;
   }
}
@Configuration
public class AppConfig2 {
   @Bean
   public UserDAO userDAO() {
     return new UserDAOImpl();
   }
}

用扫描包的方式好像userDAO对象会找不到,没有关系,IDEA智能识别的事。

配置Bean与@Component相关注解的整合、配置Bean与配置文件整合和多个配置Bean的整合方式是一样的,通过添加不同的注解(@Import(AppConfig2.class)、@ComponentScan("")、@ImportResource("applicationContext.xml"))来整合,并且跨配置进行注入的方式都是通过@Autowired进行注入。

模拟一个简单场景是开发完DAO、Service、Controller层的配置Bean,并把他们整合到一个最大的配置Bean中去,用@Import实现,然后这个主配置Bean还负责实现包扫描和配置覆盖,整合遗留系统的功能,所以将以上注解全部加入,结构如下:

package com.jin;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;

/**
 * @author jinyunlong
 * @date 2021/12/29 11:12
 * @profession ICBC锅炉房保安
 */
@Import({AppConfig1.class,AppConfig2.class,AppConfig3.class})
@ComponentScan(basePackages = "com.jin.scan")
@ImportResource("applicationContext.xml")
public class AppConfigBase {
}

不出意外的好多对象:

31.PNG

6、配置Bean底层实现原理

33.PNG

Cglib相关代码参照Spring(5)

7、四维一体的开发思想

了解即可,就是Spring开发一个功能的4种形式,虽然开发方式不同,但是最终效果是一样的。

1. 基于schema
2. 基于特定功能注解
3. 基于原始<bean
4. 基于@Bean注解

开发场景案例:

1. <context:property-placehoder
2. @PropertySource 【推荐】
3. <bean id="" class="PropertySourcePlaceholderConfigure"/>
4. @Bean 【推荐】

因为用注解开发,所以1、3用的就很少了,Spring集成注解可以直接使用2,如果看2不顺眼想自己用编码方式实现,或者Spring中没有集成自己想要的,使用4。

三、总结

至此,Spring2.X和Spring3.X的一些基础注解就梳理完了,综合这些注解的功能,先前简单、复杂对象的创建,自定义类型的注入、JDK类型的注入,包扫描,解耦合,整合配置信息等就都可以用注解实现了。

到了SpringBoot,有几个重要注解直接被整合到启动类注解中去了,参考文章:

spring boot 启动类

34.PNG

可见有@ComponentScan注解,那么就可以去扫描扫描启动类同包以及子包下的注解,@Configuration(本质:也是@Component注解的衍生注解,可以应用@ComponentScan进行扫描)、@Component等等等等:SpringBoot启动配置类(二)【@Configuration注解的配置类如何被加载到?】

当然,@EnableAutoConfiguration也是个很牛的注解,他是整合了spring.factories这个配置文件,如下图SpringBoot的spring.factories:

35.PNG

@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样,借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

可以理解为,在spring.factories中的配置,我们只需要引入对应的jar包,便可以将该配置Bean交由SpringBoot启动类的IOC容器(工厂)管理。

简言之,@EnableAutoConfiguration是加载第三方Bean用的。

@Configuration:SpringBoot启动类自己的配置Bean。

参考文章:

1、Spring boot 启动类源码分析以及@EnableAutoConfiguration和@SpringBootApplication讲解

2、Spring Factories

Spring的注解编程到这记得就差不多了。


标题:Spring(10)
作者:jyl
地址:http://www.jinyunlong.xyz/articles/2021/12/29/1640748143344.html