14

把对象交给spring管理的3种方法及经典应用

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ%3D%3D&%3Bmid=2247485613&%3Bidx=1&%3Bsn=a13f157059b2d6a2e00e5cec4152b315
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

背景

先说一说什么叫把对象交给spring管理。它区别于把类交给spring管理。在spring里采用注解方式@Service、@Component这些,实际上管理的是类,把这些类交给spring来负责实例化。

而对象交给spring管理,举个例子,最常见的在配置文件里定义一个bean,或者JavaConfig的方式就是在@Configure标签标注的类里的@Bean对象。这些Bean已经new出来了。是以对象实例的方式交给spring管理的。这些对象往往是与业务无关的基础组件。比如datasource的bean、redis连接池的bean。个数是有限的。

特别是面试的时候千万要注意他们的区别。有时候有的人觉得自己回答没问题,最后面试没通过,很可能是因为没有弄清楚面试的考察点。面试官的逻辑是如果你没弄清楚问题就回答,面试官很可能会怀疑工作中接到任务也会没有弄清楚就直接开干,做出些负产出来。建议面试的时候不但回答问题,同时也可以说对象交给spring管理和类交给spring管理有点类似,但是……这样清楚的表达自己的思考。

方法一:XML配置Bean

这个方法非常常见,举个例子:

<bean id="car" class="com.lm.spring.bean.Car">

这种定义方法相当于Car car = new Car() 然后 注册car到spring容器。用的时候直接用。

既然bean是被实例化后的对象,就涉及到对象实例化的时候要传参数的问题。

<bean id="car" class="com.lm.spring.bean.Car">
<constructor-arg value="Baoma"></constructor-arg>
<constructor-arg value="Red"></constructor-arg>
<constructor-arg value="400000"></constructor-arg>
</bean>

这是构造器传参方式,相当于

Car car = new Car("Baoma","Red","400000")。当然构造器传参还支持指定参数的index或者name啥的。其中value代表是基本数据类型,也可以传对象,对象用ref=,代表传的是另外一个bean.

经典的配置场景是数据库配置:先配置dataSource,再配置需要引用dataSource的sessionFactory。

<!-- 数据源配置 -->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="#{props.url}" />
<property name="username" value="#{props.user}" />
<property name="password" value="#{props.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="#{props.initialPoolSize}" />
<property name="minIdle" value="#{props.minPoolSize}" />
<property name="maxActive" value="#{props.maxPoolSize}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="#{props.maxIdleTime}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="#{props.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="#{props.minEvictableIdleTimeMillis}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>


<!-- sessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="ds"></property>
<!-- <property name="hibernateProperties"></property> -->
<property name="configLocation" value="classpath:com/chinasofti/etc/conf/hibernate.cfg.xml"></property>
</bean>

这些都是常规用法。前段时间见到了另外一种用法,虽然没啥技术难度。确实之前没有这么用过,也觉得很有意思:

<bean id="car" class="java.util.ArrayList">
<property name="cars">
<list>
<ref bean="car1"></ref>
<ref bean="car2"></ref>
<ref bean="car3"></ref>
</list>
</property>
</bean>

这里用property方法传参相当于调用set方法传参。当然,这里除了可以定义ArrayList对象,还可以定义HashMap、HashSet、Array。

我看到这个恍然大悟,原来自己写的一些方法都不地道。比如机房信息,一个公司可能有七八个机房,什么大兴机房、永丰机房啥的。这个内容很少,放在数据库里没啥必要,之前项目就直接在程序里用枚举定义出来,然后放到map里的。如果加了个机房,就改改代码上线呗。

其实更合适的方法,应该是在配置文件里定义一个class="java.util.map"的bean。然后把内容都放到配置文件里。这些每次添加机房,只改配置文件,不改代码,更合理一些。

虽然实际来讲哈,现在都走devops工具发布了,成本工作量是一样的。但是这样写,能明确修改的是配置,不是代码逻辑。影响范围会更明确些。

方法二:@Bean

这个方法是spring boot流行后的常用方法。本质和XML配置方法相同。所有用XML配置文件的方法都可以用这个方法改写。

@Configuration //此处为配置项
public class ServiceConfig {
@Bean //此处返回的是一个Spring的配置Bean,与xml的<bean>等价
public IMessageService getMessageService() {//方法名称随便写
return new MessgeServiceImpl();
}
}

很多基础组件的使用文档提供的是XML配置形式的,看到过一些刚毕业的同学在抓耳挠腮。因为项目用的spring boot。如果了解他们的本质就会知道可以直接自己这样写。

方法三:BeanFacoty registerSingleton

先上代码,定义一个普通Bean。

@Data
public class User {
    private Integer id;
    private String name;
    private String password;
    private Integer age;
}

定义一个被spring可以扫描的类,这个类要实现

BeanFactoryPostProcessor。里面调用registerSingleton注册一个对象。

@Component
public class MyBeanFacoty implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        User user1 = new User();
        user1.setId(1);
        user1.setName("贾元春");
        user1.setAge(27);
        configurableListableBeanFactory.registerSingleton("user", user1);
    }

}

之后就可以随时进行依赖了。

@RestController
public class JacksonController {
    @Resource
    private User user;
    @GetMapping("/writeStringAsString")
    public String writeStringAsString(String toWrite) throws Exception {
        System.out.println(user.getAge());
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(toWrite);
    }

这时候大家是否会有个疑问,XML配置Bean是传统的spring mvc里常用的将对象交给spring来管理的方法,@Bean是spring boot里将对象交给spring来管理的方法。那为什么还要有这个先实现BeanFactoryPostProcessor的方法呢?

因为这种方法可以用来做这件事情,但是不仅仅可以做这件事情。它神通广大,不仅可以将一个对象交给spring管理,还可以将已经交给spring管理的对象拿出来进行修改,还有其他各种的spring初始化的干预都可以做。所以用它来仅仅注册一个Bean有点杀鸡用牛刀的味道。

总结思考

之前也写过一些spring的文章,如:

SpringBoot启动原理

学习Spring的思考框架

专治不会看源码的毛病--spring源码解析AOP篇(2017版)

SpringBoot优雅退出

你看不懂的spring原理是因为不知道这几个概念

Spring参数的自解析--还在自己转换?你out了!

这些文章主要围绕的核心就是spring framework的原理和spring看似基础的应用技巧。

这是我的一个理念。学spring和学厨师很像。基础就是刀工、材料和火候。掌握了基础,通过自己的悟性就可以千变万化了。悟性不是靠教的,有用的还是基础。

spring有很多的扩展内容,spring boot、spring cloud、spring batch。之所以有这些就是因为它做了两件事:第一是控制反转,使用Bean的时候随时取用;第二是aop,把那些可以重用的代码写一份想用的地方都可以生效。

刚毕业的时候,有大师教我们说控制反转和依赖注入是一回事,后来我想想其实不是。控制反转是把对象和类交给spring来管理,由spring来控制器生命周期的过程。依赖注入是在使用对象的时候,可以通过set、构造器和注解方式获取对象的过程。一个是存钱的过程,一个是取钱的过程。联系是存钱是为取钱服务的。

而spring的一些高级框架就是将更多的东西交给spring来管理的过程,比如spring-jdbc,就是通过spring容器就可以调用jdbc了。我个人认为原理了解了,学的价值不大。

spring作者是学音乐出身的。spring牛的地方是它的理念,它没有做什么事情,只是把能替程序员做的事情都做了,给程序员开发带来了春天。把开发变成了艺术。

答疑时间

有个朋友问我:

AneAvq3.png!mobile

这里回答一下:

FzE77b.png!mobile

得出是哪五个,建议随便找个springboot程序,在SpringApplication类的run方法里createApplicationContext之后打个断点。

NrYRVre.png!mobile

context的beanFactory里的beanDefinitionMap里的5个对象就是spring开天辟地的五个bean。

mm2MJrr.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK