Spring基础专题——第十一章(高级注解编程完结)

前言:去年到现在一直没有很好的时间完成这个spring基础+源码的博客目标,去年一年比较懒吧,所以今年我希望我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从一个互联网职场小白到一个沪漂湿人,一路让我知道分享是一件多么重要的事情,总之不对的地方,多多指出,我们一起徜徉代码的海洋!
 
我这里做每个章节去说的前提,不是一定很标准的套用一些官方名词,目的是为了让大家可以理解更加清楚,如果形容的不恰当,可以留言出来,万分感激

1、配置Bean

在上一节,我们讲了一些基本的Spring注解,在3.x开始得到广泛的使用,而此时,高级的注解,意味着要和很多框架在一起整合,从而衍生出来,基本注解是为了解决Spring本身的一些配置文件繁琐,本章带你看下Spring注解的高级部分!通过这些注解的学

习,今后就可以基于纯注解来完成。

 

配置Bean,是Spring3.x中提供的新的注解,用于替换xml文件,同时也是后续SpringBoot开发的重要核心。

回忆下我们之前讲解的内容,我们想实现UserService的register方法,注册用户,是不是要在applicationContext.xml配置文件里做这些事情?

<bean id="user" class="com.chenxin.spring5.bean.User" lazy-init="true"></bean>

<bean id="userDao" class="com.chenxin.spring5.injection.UserDaoImpl"></bean>

<bean id="userService" class="com.chenxin.spring5.tx.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
</bean>

<context:component-scan base-package="com.chenxin.spring5" >
</context:component-scan>

等等这些工作都是由配置文件完成

  • 配置形式发生了变化:由xml转成java代码

那么引入了配置Bean后,这些工作自然就要交给配置Bean来完成,来看一个配置Bean的整体是什么样子的,假设我有一个类,AppConfig,加上一个注解@Configuration,这个类就等同于一个applicationContext.xml这个配置文件,所以壳子我们先换下。

@Configuration
public class AppConfig {
}

除了这个变化之外

  • 工厂的转变

以前我们会先读配置文件applicationContext.xml后,Spring通过ApplicationContext ctx = new ClassPathXmlApplicationContext读取applicationContext.xml文件所在的路径,现在不是这样咯,因为配置文件已经不存在了!!取而代之的,转成了配置Bean

所以我们创建工厂的方式也随时替换成

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

或者

ApplicationContext ctx = new AnnotationConfigApplicationContext("配置Bean所在的包路径");
  • 日志门面的替换

之前我们用配置文件创建Bean的同时,用的是log4j日志门面,而此时当使用配置Bean的方式,log4j就被淘汰了,取而代之的是logback日志门面

所以要引入相关的logback坐标依赖,而这个logback日志门面,也是后续通过全注解的方式进行编程中,大力推荐的logback日志门面,也是Springboot默认使用的日志门面!

<!--        配置Bean的日志门面-->
        <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的日志文件,在resource下新建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>

所以这个整体我们就搭建好了,继续往下看

既然@Configuration可以替代xml的方式成为配置bean,其本质是什么呢?

本质是@Component注解的衍生注解,看下源码,是不是有个@Component注解,既然是衍生注解了,是不是也可以通过<context: component-scan进行扫描?但是这xml都被配置bean取代了,这个标签也自然就没有了,那你扫啥?

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

所以我们正常用注解的方式,就不会这么做了,那怎么实现扫描呢,我们后面说;

 

2、@Bean注解

配置bean已经完成了替换xml的功能,那么xml的创建Bean对象,应该怎么通过配置bean,去帮我们创建对象呢,实际上是通过@Bean注解来帮我们完成的。

@Bean注解在配置bean中进行使用,等同于xml配置中的<bean>标签。

 

2.1、对象的创建

简单回顾下之前的知识

  • 简单对象:直接能够通过new的方式创建的对象
  • 复杂对象:不能直接通过new的方式创建的,比如Connection,SqlSessionFactory。

所以我们创建简单对象,用配置bean要这么做,注意,修饰符一定是public,返回参数一定是你要创建的对象,方法体中写你要创建的对象的代码,方法名等同于bean标签的id值!!!

@Configuration
public class AppConfig {
    
    @Bean
    public User user(){
        return new User();
    }
    等同于
    <bean id="user" class="com.chenxin.spring5"/>
}


public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        User user = (User) ctx.getBean("user");
        System.out.println(user);
}

结果是可以创建对象成功的!

如果我要创建复杂对象呢?比如连接Connection对象?配置bean中这么写就可以了。

    @Bean
    public Connection conn(){
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/users?useSSL=false","root","123456");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    getBean的时候:

    Connection conn = (Connection) ctx.getBean("conn");
    System.out.println(conn);

这样复杂对象我们也创建好了,但是你有没有发现一个问题,前面我们创建工厂说过,创建复杂对象,用FactoryBean来帮我们创建复杂对象,那你现在要搞到@Bean中,如果我有一些遗留系统用的是FactoryBean的方式创建复杂对象,而这些代码又非

常多,那我能不能和@Bean结合起来?

首先我还是先定义一个ConnectionFactoryBean,用于连接对象的创建,这个忘记记得翻下前面讲工厂对象的创建的章节!

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    public Connection getObject() throws Exception {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/users?useSSL=false","root","123456");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

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

    public boolean isSingleton() {
        return false;
    }
}

那我想通过配置Bean整合这个FactoryBean创建的对象,怎么做呢?很简单我们只需要在配置bean中这么写

    @Bean
    public Connection conn1() throws Exception {
        return new ConnectionFactoryBean().getObject();
    }

进而我们通过getBean("conn1")就可以整合遗留系统创建对象了,自己写,很少这么无聊的!

那么有人会不会问,刚刚说这个方法名user,conn,conn1都是默认这个对象的id值,那我现在不想默认,我想自己指定,怎么做?很简单

    @Bean("u")
    public User user(){
        return new User();
    }

这样就可以了,不用多说哈

2.2、控制对象的创建次数

用@Bean中,我们怎么像xml中控制对象创建次数呢?

以前我们这么干的

<bean id="user" class="com.chenxin.spring5" scope="singleton|prototype"/>

这里有个隐含的面试题,我前几个章节我讲过,这里想到了顺带提下:

当Spring创建对象的形式是默认形式,也就是单例的前提下,在工厂创建的时候,对象会同时被创建出来,如果你想在getBean的时候帮你创建这个单例对象,你只需要配置懒加载就可以,也就是lazy-init为true

当Spring创建对象的形式是prototype形式,一定只是在你getBean的时候工厂才帮你创建对象,而此时你无论配置什么懒加载,都不会生效。

其实原因很简单,你创建单例对象,整个工厂这个对象就一份,出于Spring的角度考虑,我初始化创建好了你拿来用,或者你想用的时候我给你造一个,就一个而已吗,不费事,你可以自己控制

但是你想让我初始化帮你整这么多对象,我哪知道你要几个对象?所以我干脆让你自己getBean的时候我给你创建一个,不香吗让我考虑你们怎么想的!!!

好了这个扯多余了,看怎么实现@Bean控制的

    @Bean("u")
    @Scope("singleton|prototype")
    public User user(){
        return new User();
    }

和之前讲的其实一毛一样的。不加@Scope注解,默认就是单例嘛。

2.3、注入

@Bean怎么完成相应的注入呢?注入两种

  • 用户自定义类型的注入

比如有个UserService,UserDao对象等,我们就通过下面的写法来替换了配置文件的写法

<bean id="userService" class="com.chenxin.spring5.config.UserServiceImpl">
      <property name="userDao" ref="userDao"></property>
</bean>

<bean id="userDao" class="com.chenxin.spring5.config.UserDaoImpl"></bean>


@Bean
public UserDao userDao() {
    return new UserDaoImpl();
}

@Bean
public UserService userService(UserDao userDao){
    UserServiceImpl userService = new UserServiceImpl();
    userService.setUserDao(userDao);
    return userService;
}

其实还有简化写法,这么干

 

@Bean
public UserDao userDao() {
    return new UserDaoImpl();
}

@Bean
public UserService userService(){
    UserServiceImpl userService = new UserServiceImpl();
    userService.setUserDao(userDao());
    return userService;
}
  • JDK类型的注入

我们对User的基本的成员变量进行赋值

    @Bean
    public User user() {
        User user = new User();
        user.setName("chenxin");
        user.setPassword("123456");
        return user;
    }

但是你发现这个其实就已经是耦合了? 所以这个问题我们怎么解决呢?当然我们是要通过转移配置文件的方式去解耦,把这些信息转移到相关的配置文件中,进而这个耦合就没了

所以我们定义一个配置文件application.properties,后写两个key,value,,结合上节的@PropertySource,于是配置Bean就要这么写

@Configuration
@PropertySource("application.properties")
public class AppConfig {

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

    @Value("${password}")
    private String password;

    @Bean
    public User user() {
        User user = new User();
        user.setName(name);
        user.setPassword(password);
        return user;
    }
}

这样就实现了我们通过配置Bean进行解耦,注入的效果。

3、@ComponentScan注解

其实之前讲的这个标签<context: component-scan/>,读者应该就已经联想到的这个@ComponentScan这个注解了吧

这个注解的意思,应用在配置Bean中,等同于Xml中的<context: component-scan>标签

引入的目的是为了扫描基础注解(如@Component,@Service,@Controller,@Autowired等等这些衍生的注解)

所以这个注解这么使用:

<context:component-scan base-package="扫描注解所在的包路径">


@Configuration
@ComponentScan(basePackages="扫描注解所在的包路径")
public class AppConfig {
   
}

既然我们可以扫描相关注解,那我们怎么排除一些不想要的包呢?

结合我们上节学习排除包的xml配置方式,一共五种

那现在用注解的话,其实也是一样的,注解有个属性是excludeFilters,里面是个数组,因为排除策略是可以叠加的,所以用数组来存

如果我们想排除不扫注解@Service的情况下,我们这么写,对比看下

<context:component-scan base-package="com.chenxin.spring5">   
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>

@Configuration
@ComponentScan(basePackages = "com.chenxin.spring5",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class AppConfig2 {
}

如果我们想排除某些包下的类,我们这么写,如果是多个就数组写法,逗号隔开

<context:component-scan base-package="com.chenxin.spring5">
        <context:exclude-filter type="assignable" expression="com.chenxin.spring5.jdk.User"/>
    </context:component-scan>


@Configuration
@ComponentScan(basePackages = "com.chenxin.spring5",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {User.class})})
public class AppConfig2 {
}

如果我们想写切入点表达式,我们这么写

    <context:component-scan base-package="com.chenxin.spring5">
       
        <context:exclude-filter type="aspectj" expression="com.chenxin.spring5.jdk..*"/>
    </context:component-scan>

@Configuration
@ComponentScan(basePackages = "com.chenxin.spring5",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.chenxin.spring5..*"})})
public class AppConfig2 {
}

你也可以叠加排除策略

<context:component-scan base-package="com.chenxin.spring5">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:exclude-filter type="aspectj" expression="com.chenxin.spring5.jdk..*"/>
    </context:component-scan>


@Configuration
@ComponentScan(basePackages = "com.chenxin.spring5",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.chenxin.spring5.jdk..*"})
                , @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class AppConfig2 {
}

所以我把这些示例写出来,读者看看其实没什么区别,只是使用方式稍微变化了下!!

这个type我也标注下

type = FilterType.ANNOTATION 对应 value
                 .ASSIGNABLE_TYPE 对应 value
                 .ASPECTJ 对应 pattern
                 .REGEX 对应 pattern
                 .CUSTOM 对应 value

排除方式我们讲了,那么还有最后一个包含方式

这里我就举一个例子就可以了,其实就把排除取反,还是那句话,既然排除,我们就应该先置某个属性,使用默认的过滤use-default-filters为false,因为你不能按照Spring默认扫当前包和其子包这种默认策略,要实现的你需求——包含某些包,而这个值默认是true

你要改成false,这个上一章也详细讲过!

<context:component-scan base-package="com.baizhiedu" use-default-filters="false">
 <context:include-filter type="" expression=""/>
</context:component-scan>

@ComponentScan(basePackages = "com.baizhiedu.scan",
 useDefaultFilters = false,
 includeFilters = {@ComponentScan.Filter(type=
FilterType.ANNOTATION,value={Service.class})})

同样exclude换成include,其他不变,这里就不多做举例了,在实际开发中,整合一些老的系统,以及遗留的代码会经常用到这些。

 

4、多种创建对象方式的应用场景

我们讲了很多关于Spring创建对象的方式,比如@Component,@Bean,<bean>标签等

那么这些创建方式,我们日常开发中会怎么选择呢?

对于@Component注解及其衍生的注解@Controller,@Service几乎用在的都是Controller层和Service层,而对于那些实体对象的创建我们不会通过@Component去做,比如用户实体User,Product,这些则是交给ORM框架——Mybatis帮我们创建

所以这@Component注解用于程序员自己开发的类型上。

对于@Bean注解,用于框架提供的类型,以及别的程序员开发的类型,因为这样的情况是没有源码的,更多的是以jar形式存在,别人已经做好过了,你拿来整合和使用,你是没有机会拿到他的java源码去加@Component这样类似的注解的。

比如SqlSessionFactoryBean,这个显然是Spring为我们提供的,你没有源码,你就没法加上@Component相关注解,这个时候用@Bean可以帮你创建对象就可以了,还有MapperScannerConfigurer等等。

对于<bean>标签,我们在纯注解开发中基本是不用的,所以配置文件也不再写,只有一个场景会遇到,就是整合遗留系统的时候,这个会使用到!

其实还有一个注解,我们不常用,是我们之前提到过的

@Import(User.class)这个注解,一旦在配置Bean上加这个,就会一起把Import标注的类也由Spring帮忙创建出来

只是这个注解几乎不用,这里了解下就好~~Spring框架底层会用到这个,以及多配置Bean整合

 

既然有这么多的配置,那么优先级的话

@Component注解及其衍生    <   @Bean   <     配置文件bean标签

优先级高的配置,覆盖优先级低的配置,并且id值要保持一致,不然就会重复创建bean了,很容易报错bean冲突!!

尤其是在整合一些遗留系统的时候,这个我后面抽时间再补充一个章节专门讲遗留系统的整合用到Spring的场景!!

 

 

 

所以Spring注解的高级部分,这一节也讲述完了,后面有机会我会补充一些真实的业务场景,截此,Spring的基础部分我们就暂时告一段落,过两天我会开始源码部分的入手,因为我这仅仅只是讲了Spring,实际上Spring和很多框架整合包括Mybatis我并没有详细去讲,因为这个属于整合框架部分,等我哪天抽空了写个系统再来分析Spring整合框架的特点!!欢迎提出宝贵意见

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风清扬逍遥子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值