小言_互联网的博客

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

311人阅读  评论(0)

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

1、配置Bean

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

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

 

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

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


   
  1. <bean id="user" class="com.chenxin.spring5.bean.User" lazy-init="true"> </bean>
  2. <bean id="userDao" class="com.chenxin.spring5.injection.UserDaoImpl"> </bean>
  3. <bean id="userService" class="com.chenxin.spring5.tx.service.UserServiceImpl">
  4. <property name="userDao" ref="userDao"> </property>
  5. </bean>
  6. <context:component-scan base-package="com.chenxin.spring5" >
  7. </context:component-scan>

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

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

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


   
  1. @Configuration
  2. public class AppConfig {
  3. }

除了这个变化之外

  • 工厂的转变

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

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


   
  1. ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
  2. 或者
  3. ApplicationContext ctx = new AnnotationConfigApplicationContext( "配置Bean所在的包路径");
  • 日志门面的替换

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

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


   
  1. <!-- 配置Bean的日志门面-->
  2. <dependency>
  3. <groupId>org.slf4j </groupId>
  4. <artifactId>slf4j-api </artifactId>
  5. <version>1.7.25 </version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.slf4j </groupId>
  9. <artifactId>jcl-over-slf4j </artifactId>
  10. <version>1.7.25 </version>
  11. </dependency>
  12. <dependency>
  13. <groupId>ch.qos.logback </groupId>
  14. <artifactId>logback-classic </artifactId>
  15. <version>1.2.3 </version>
  16. </dependency>
  17. <dependency>
  18. <groupId>ch.qos.logback </groupId>
  19. <artifactId>logback-core </artifactId>
  20. <version>1.2.3 </version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.logback-extensions </groupId>
  24. <artifactId>logback-ext-spring </artifactId>
  25. <version>0.1.4 </version>
  26. </dependency>

光引入坐标还不够,需要logback的日志文件,在resource下新建logback.xml


   
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3. <!-- 控制台输出 -->
  4. <appender name="STDOUT"
  5. class= "ch.qos.logback.core.ConsoleAppender">
  6. <encoder>
  7. <!--格式化输出:%d表示⽇期,%thread表示线程名,
  8. %-5level:级别从左显示5个字符宽度%msg:⽇志消息,%n是换⾏符-->
  9. <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}
  10. [%thread] %-5level %logger{50} - %msg%n </pattern>
  11. </encoder>
  12. </appender>
  13. <root level="DEBUG">
  14. <appender-ref ref="STDOUT" />
  15. </root>
  16. </configuration>

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

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

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


   
  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface Configuration {
  6. @AliasFor(
  7. annotation = Component.class
  8. )
  9. String value() default "";
  10. }

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

 

2、@Bean注解

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

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

 

2.1、对象的创建

简单回顾下之前的知识

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

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


   
  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public User user(){
  5. return new User();
  6. }
  7. 等同于
  8. <bean id= "user" class= "com.chenxin.spring5"/>
  9. }
  10. public static void main(String[] args) {
  11. ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
  12. User user = (User) ctx.getBean("user");
  13. System.out.println(user);
  14. }

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

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


   
  1. @Bean
  2. public Connection conn(){
  3. Connection conn = null;
  4. try {
  5. Class.forName( "com.mysql.jdbc.Driver");
  6. conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/users?useSSL=false", "root", "123456");
  7. } catch (ClassNotFoundException e) {
  8. e.printStackTrace();
  9. } catch (SQLException e) {
  10. e.printStackTrace();
  11. }
  12. return conn;
  13. }
  14. getBean的时候:
  15. Connection conn = (Connection) ctx.getBean( "conn");
  16. System.out.println(conn);

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

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

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


   
  1. public class ConnectionFactoryBean implements FactoryBean<Connection> {
  2. public Connection getObject() throws Exception {
  3. Connection conn = null;
  4. try {
  5. Class.forName( "com.mysql.jdbc.Driver");
  6. conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/users?useSSL=false", "root", "123456");
  7. } catch (ClassNotFoundException e) {
  8. e.printStackTrace();
  9. } catch (SQLException e) {
  10. e.printStackTrace();
  11. }
  12. return conn;
  13. }
  14. public Class<?> getObjectType() {
  15. return Connection.class;
  16. }
  17. public boolean isSingleton() {
  18. return false;
  19. }
  20. }

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


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

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

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


   
  1. @Bean("u")
  2. public User user(){
  3. return new User();
  4. }

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

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控制的


   
  1. @Bean("u")
  2. @Scope("singleton|prototype")
  3. public User user(){
  4. return new User();
  5. }

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

2.3、注入

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

  • 用户自定义类型的注入

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


   
  1. <bean id="userService" class="com.chenxin.spring5.config.UserServiceImpl">
  2. <property name="userDao" ref="userDao"> </property>
  3. </bean>
  4. <bean id="userDao" class="com.chenxin.spring5.config.UserDaoImpl"> </bean>
  5. @Bean
  6. public UserDao userDao() {
  7. return new UserDaoImpl();
  8. }
  9. @Bean
  10. public UserService userService(UserDao userDao){
  11. UserServiceImpl userService = new UserServiceImpl();
  12. userService.setUserDao(userDao);
  13. return userService;
  14. }

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

 


   
  1. @Bean
  2. public UserDao userDao() {
  3. return new UserDaoImpl();
  4. }
  5. @Bean
  6. public UserService userService(){
  7. UserServiceImpl userService = new UserServiceImpl();
  8. userService.setUserDao(userDao());
  9. return userService;
  10. }
  • JDK类型的注入

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


   
  1. @Bean
  2. public User user() {
  3. User user = new User();
  4. user.setName( "chenxin");
  5. user.setPassword( "123456");
  6. return user;
  7. }

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

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


   
  1. @Configuration
  2. @PropertySource("application.properties")
  3. public class AppConfig {
  4. @Value("${name}")
  5. private String name;
  6. @Value("${password}")
  7. private String password;
  8. @Bean
  9. public User user() {
  10. User user = new User();
  11. user.setName(name);
  12. user.setPassword(password);
  13. return user;
  14. }
  15. }

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

3、@ComponentScan注解

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

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

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

所以这个注解这么使用:


   
  1. <context:component-scan base- package= "扫描注解所在的包路径">
  2. @Configuration
  3. @ComponentScan(basePackages="扫描注解所在的包路径")
  4. public class AppConfig {
  5. }

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

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

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

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


   
  1. <context:component-scan base-package="com.chenxin.spring5">
  2. <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
  3. </context:component-scan>
  4. @Configuration
  5. @ComponentScan(basePackages = "com.chenxin.spring5",
  6. excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
  7. public class AppConfig2 {
  8. }

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


   
  1. <context:component-scan base-package="com.chenxin.spring5">
  2. <context:exclude-filter type="assignable" expression="com.chenxin.spring5.jdk.User"/>
  3. </context:component-scan>
  4. @Configuration
  5. @ComponentScan(basePackages = "com.chenxin.spring5",
  6. excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {User.class})})
  7. public class AppConfig2 {
  8. }

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


   
  1. <context:component-scan base-package="com.chenxin.spring5">
  2. <context:exclude-filter type="aspectj" expression="com.chenxin.spring5.jdk..*"/>
  3. </context:component-scan>
  4. @Configuration
  5. @ComponentScan(basePackages = "com.chenxin.spring5",
  6. excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.chenxin.spring5..*"})})
  7. public class AppConfig2 {
  8. }

你也可以叠加排除策略


   
  1. <context:component-scan base-package="com.chenxin.spring5">
  2. <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
  3. <context:exclude-filter type="aspectj" expression="com.chenxin.spring5.jdk..*"/>
  4. </context:component-scan>
  5. @Configuration
  6. @ComponentScan(basePackages = "com.chenxin.spring5",
  7. excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.chenxin.spring5.jdk..*"})
  8. , @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
  9. public class AppConfig2 {
  10. }

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

这个type我也标注下


    
  1. type = FilterType.ANNOTATION 对应 value
  2. .ASSIGNABLE_TYPE 对应 value
  3. .ASPECTJ 对应 pattern
  4. .REGEX 对应 pattern
  5. .CUSTOM 对应 value

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

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

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


    
  1. <context:component-scan base-package="com.baizhiedu" use-default-filters="false">
  2. <context:include-filter type="" expression=""/>
  3. </context:component-scan>
  4. @ComponentScan(basePackages = "com.baizhiedu.scan",
  5. useDefaultFilters = false,
  6. includeFilters = {@ComponentScan.Filter(type=
  7. 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整合框架的特点!!欢迎提出宝贵意见

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


转载:https://blog.csdn.net/qq_31821733/article/details/116086889
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场