Bean装配,从Spring到Spring Boot

自从用上Spring Boot,真的是一直用一直爽,已经完全无法直视之前Spring的代码了。约定大于配置的设计理念,使得其不需要太多的配置就能开箱即用。

1、在Spring当中一切皆为Bean:

Bean是组成一个Spring应用的基本单位。Spring(这里是狭义的概念,指Spring Core)中最核心部分就是对Bean的管理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                         http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:annotation-config/>

    <context:component-scan base-package="com.zjut.ssm.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="20971500"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>
    </bean>
</beans>

让我们再次看一下Spring MVC的配置文件,除了一些参数外,还有两个bean节点,注入InternalResourceViewResolver来处理视图,注入CommonsMultipartResolver来处理文件上传。这个时候,如果需要集成Mybatis一起工作,类似的,注入相关的Bean就可以了。Mybatis最核心的Bean就是SqlSessionFactory,通过创建Session来进行数据库的操作。在不使用Spring时,可以通过加载XML,读入数据库信息,进行创建。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

显然,上面的代码是不符合Spring的思想的。为了达到松耦合,高内聚,尽可能不直接去new一个实例,而是通过DI的方式,来注入bean,由Spring IoC容器进行管理。Mybatis官方给到一个MyBatis-Spring包,只需添加下面的Bean就可以组织Mybatis进行工作了(创建sqlSessionFactory,打开Session等工作),关于Mybatis的内容,这里不展开了。

<beans>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value=""/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value=""/>
    </bean>
</beans>

现在我们就知道了,通过XML配置Spring,集成各种框架的实质,就是Bean装配的。每一个框架都是由N多个Bean构成的,如果需要使用它,就必须根据框架的要求装配相应的Bean。装配成功的Bean由Spring IoC容器统一管理,就能够正常进行工作。

 

2、Bean的装配:

具体Bean的装配方式,发展到现在也已经有很多种了,从过去的XML到Java Config,再到现在Spring Boot的Auto Configuration,是一种不断简化,不断清晰的过程。Bean装配一般分为三步:注册、扫描、注入

2.1)由XML到Java Config的注册:

XML配置Bean早已成为明日黄花了,目前更常见的是使用Java Config和注解来进行Bean的装配。当然,偶尔也能看到它的身影,例如用于ssm框架的集成的spring-*.xml。

Java Config的优势如下:

  1. Java是类型安全的,如果装配过程中如果出现问题,编译器会直接反馈。
  2. XML配置文件会随着配置的增加而越来越大,不易维护管理。
  3. Java Config更易于重构和搜索Bean。

因此下面主要讲都是基于Java的配置方法。基本流程如下:

// 注册
@Configuration
public class BeanConfiguration {
    @Bean
    public AtomicInteger count() {
        return new AtomicInteger();
    }
}
//或者 
@Componment
public class Foo{}

// 扫描
@ComponentScan(basePackages={})
@Configuration
public class BeanConfiguration {}

// 注入
@Autowired
private AtomicInteger c;

Java Config注册Bean,主要分为两类,注册非源码的Bean和注册源码的Bean。 

1)非源码的Bean:

非源码的Bean,指的是我们无法去编辑的代码,主要是引入外部框架或依赖,或者使用Spring的一些Bean。这些Bean的配置一般采用Java文件的形式进行声明。
新建一个使用@Configuration修饰的配置类,然后使用@Bean修饰需要创建Bean的方法,具体的可指定value和name值(两者等同)。示例如下:

@Configuration
public class BeanConfiguration {
    @Scope("prototype")
    @Bean(value = "uploadThreadPool")
    public ExecutorService downloadThreadPool() {
        return Executors.newFixedThreadPool(10);
    }
}

其中需要注意的是:

  1. Bean默认是以单例的形式创建的,整个应用中,只创建一个实例。如果需要,每次注入都创建一个新的实例,可添加注解@Scope("prototype")
  2. 注入多个相同类型的Bean时,手动指定name或value值加以区分。或通过@Primary,标出首选的Bean。

2)注册源码的Bean:

源码的Bean,指的是我们自己写的代码,一般不会以@Bean的形式装配,而是使用另外一系列具有语义的注解。(@Component、@Controller、@Service、@Repository)添加这些注解后,该类就成为Spring管理的组件类了,列出的后三个注解用的几率最高,基本已经成为样板注解了,Controller类添加@Controller,Service层实现添加@Service。看一个例子:

@Scope("prototype")
@Component(value = "uploadThread")
public class UploadTask implements Runnable {
    private List<ByteArrayInputStream> files;
    private List<String> fileNameList;
    private PropertiesConfig prop = SpringUtil.getBean(PropertiesConfig.class);

    // 如果直接传入MutiPartFile,文件会无法存入,因为对象传递后spring会将tmp文件缓存清楚
    public UploadThread(List<ByteArrayInputStream> files, List<String> fileNameList) {
        this.files = files;
        this.fileNameList = fileNameList;
    }

    @Override
    public void run() {
        for (int i = 0; i < files.size(); ++i) {
            String fileName = fileNameList.get(i);
            String filePath = FileUtils.generatePath(prop.getImageSavePath(),fileName);
            FileUtils.save(new File(filePath), files.get(i));
        }
    }
}

接着上面的线程池讲,这里我们实现了一个task,用来处理异步上传任务。在传统JUC中,我们一般会这么写代码:

private ExecutorService uploadThreadPool = Executors.newFixedThreadPool(10);
uploadThreadPool.submit(new UploadTask(fileCopyList, fileNameList));

在Spring中,我觉得就应该把代码写的更Spring化一些,因此添加@Component使之成为Spring Bean,并且线程非单例,添加@Scope注解。重构后的代码如下:

@Resource(name = "uploadThreadPool")
private ExecutorService uploadThreadPool;

@PostMapping("/upload")
public RestResult upload(HttpServletRequest request) {
	  uploadThreadPool.submit((Runnable) SpringUtils.getBean("uploadThread", fileCopyList, fileNameList));
}

Bean的注入在下节会仔细讲。其实这样写还有一个原因,非Spring管理的Bean一般是无法直接注入Spring Bean的。如果我们需要在UploadTask中实现一些业务逻辑,可能需要注入一些Services,最好的做法就是讲UploadTask本身也注册成Spring Bean,那么在类中就能够使用@Autowired进行自动注入了。

额外需要注意的是:**由于线程安全的一些原因,线程类是无法直接使用@Autowired注入Bean的。**一般会采用SpringUtils.getBean()手动注入。

 

2.2)自动扫描:

在配置类上添加@ComponentScan注解。该注解默认会扫描该类所在的包下所有的配置类,特殊的包可以配置basePackages属性。Spring扫描到所有Bean,待注入就可以使用了。

2.3)Bean的注入:

对于一些不直接使用的Bean,注册到Spring IoC容器后,我们是不需要手动去注入的。例如前面提到Mybatis的三个Bean。我们只需要根据文档进行使用,创建Mapper接口,并使用@Mapper修饰,在调用具体的查询方法时,Mybatis内部会进行Bean的注入,Open一个Session进行数据库的操作。

对于我们需要使用到的Bean,就需要注入到变量中进行使用。常用Bean注入的方式有两种。

1)注解注入(@AutoWired @Resource):

@Autowired是Bean注入最常用的注解,默认是通过byType的方式注入的。也就是说如果包含多个相同类型的Bean,是无法直接通过@Autowired注入的。这个时候需要通过@Qualifier限定注入的Bean。或者使用@Resource。@Resource是通过byName的方式注入,直接在注解上标明Bean的name即可。

public class Foo{
  // 正常字段注入
  @Autowired
	private AtomicInteger c;
  
  // 正常构造器注入
	private final AtomicInteger c;
  @Autowired
  public Foo(AtomicInteger c){this.c = c;}
  
  // 歧义加上@Qualifier
  @Autowired
  @Qualifier("count")
	private AtomicInteger c;  
  
  // 歧义直接使用@Resource(与前一种等同)
  @Resource("count")
	private AtomicInteger c;  
}

2)getBean方法;

推荐使用注解注入,但是在默写特殊情况下,需要使用getBean()方法来注入Bean。例如之前讲到的多线程环境。具体实现可见附录,实现ApplicationContextAware,可以封装成一个工具类。 

 

3、附录:

1)SpringUtils.java

@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    public static <T> T getBean(String name, Object... args) {
        return (T) getApplicationContext().getBean(name, args);
    }
}

 

2)非Spring Bean中获取Spring Bean的方法:

在非Spring Bean中获取Spring Bean,需要改造SpringUtils.java,去掉@Component,并且不需要实现接口了,手动注入Spring Context。

 

public class SpringUtils {
    private static ApplicationContext applicationContext;
    public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }
    // 余下代码如上
}
public class SkeletonApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SkeletonApplication.class, args);
        SpringUtils.setApplicationContext(context);
    }
}

 

参考:https://juejin.im/post/5d5a48615188257c7d1648ae

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值