前言:去年到现在一直没有很好的时间完成这个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整合框架的特点!!欢迎提出宝贵意见