在Spring中允许我们通过XML或者Java配置文件装配Bean,但是由于Spring Boot是基于注解开发,因此下面是通过注解来介绍Spring的用法,以满足Spring Boot开发者的需要。
如果一个个的Bean使用@Bean注解注入Spring IoC容器中,那么将是一件非常麻烦的事情。好在Spring还允许我们进行扫描装配Bean到IoC容器中,对于扫描装配而言使用的注解是@Component和@ComponentScan,@Component标注那个类被扫描进Spring Ioc容器,而@ComponnetScan则是表明采用何种策略去扫描装配Bean
这里我们把代码清单中的User移动到包package cn.hctech2006.boot.bootall.config中,然后对其进行修改。代码如下:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("user")
public class User {
@Value("1")
private Long id;
@Value("USER_NAME_1")
private String userName;
@Value("NOTE_1")
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
这里的注解@Component表明这个类将被Spring IoC容器扫描装配,其中配置的user则会作为Bean的名称,当然你也可以不配置这个字符串,那么IoC容器就会把类名第一个字母作为小写,其他不变作为Bean名称放入IoC容器中;注解@Value则是指定具体的值,使的Spring IOC给予指定的属性注入对应的值。为了让Spring IoC容器装配这个类,需要改造AppConfig,代码如下:
package cn.hctech2006.boot.bootall.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {
}
这里加入@ComponentScan,意味着他会进行扫描,但是它只会扫描AppConfig所在的当前包和其子包,之前把User.java转移到package cn.hctech2006.boot.bootall.config,就是这个原因。这样就可以删掉之前使用的@Bean标注的创建对象方法,然后进行测试,测试清单如下:
package cn.hctech2006.boot.bootall.context;import cn.hctech2006.boot.bootall.config.User;
import cn.hctech2006.boot.bootall.config.AppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.logging.Logger;
public class IoCTest {
private static Logger log = Logger.getLogger(String.valueOf(IoCTest.class));
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
log.info(user.getUserName());
}
}
这样就可以运行了,但是为了使的USer类能够被扫描,上面我们把它迁移到了不该放置他的配置包,这就显得太不合理了。为了更家合理,@ComponentScan还允许我们自定义扫描的包。下面探讨他的配置项。
首先探讨@ComponentScan的源码
@Target({ElementType.TYPE})
@Documented
//在一个类中可以重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
//定义扫描的包
@AliasFor("basePackages")
String[] value() default {};
//定义扫描的包
@AliasFor("value")
String[] basePackages() default {};
//定义扫描的类
Class<?>[] basePackageClasses() default {};
//Bean name生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//作用域解析器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
//作用域代理解析器
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
//资源匹配模式
String resourcePattern() default "**/*.class";
//是否启用默认的过滤器
boolean useDefaultFilters() default true;
//当满足过滤条件时扫描
ComponentScan.Filter[] includeFilters() default {};
//当不满足过滤条件时扫描
ComponentScan.Filter[] excludeFilters() default {};
//是否延迟初始化
boolean lazyInit() default false;
//定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
//过滤器类型,可以按照注解类型或者正则表达式等过滤
FilterType type() default FilterType.ANNOTATION;
//定义过滤的类
@AliasFor("classes")
Class<?>[] value() default {};
//定义过滤的类
@AliasFor("value")
Class<?>[] classes() default {};
//匹配方式
String[] pattern() default {};
}
}
首先通过配置项basePackages定义扫描的包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径:还可以通过basePackageClasses定义扫描的类:其中还有includeFilters和excludeFilters,includeFilters是定义满足过滤器条件的Bean去扫描,excludeFilter是排除过滤器条件的Bean,他们都需要通过注解@Filter去定义,他又一个type类型,这里可以定义为注解或者正则式等类型。classes定义注解类,pattern定义正则式类。
此时我们再把User类放到package cn.hctech2006.boot.bootall.bean;中,这样User和AppConfig就不再同包,那么我们将AppConfig的注解修改为:
@ComponentScan("cn.hctech2006.boot.bootall.*")
public class AppConfig {
}
包名可以采用正则表达式去匹配。但是有时候,有时候我们的需求是想扫描一些包,将一些bean装配到Spring IoC容器,而不是加载这个包里面的某些bean。比方说,现在我们有一个UserService类,为了标注他是服务类,将类标注为@Service(该标准注入了@Component,所以在默认的情况下他会被Spring扫描装配到IoC容器中),这里再假设我们采用了策略:
@ComponentScan("cn.hctech2006.boot.bootall.*")这样cn.hctech2006.boot.bootall.bean和cn.hctech2006.boot.bootall.service这两个包都会被扫描,此时我们定义的UserService类如下:
@Servicepublic class UserService {
public void printUser(User user){
System.out.println("编号:"+user.getId());
System.out.println("用户名称:"+user.getUserName());
}
}
按照以上装配策略,他将会被扫描到Spring IoC容器中。为了不被装配,需要把扫描的策略修改为:
@ComponentScan(value = "cn.hctech2006.boot.bootall.*",excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})这样由于加入了excludeFilters的配置,使得标注了@Service的类将不能被IoC容器扫描注入。事实上,之前zaiSpring Boot上述实例中看到的注解@SpringBootApplication也注入了@ComponentScan,这里不妨探究其源代码
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
//自定义排除的扫描类
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
//通过类型排除自动配置类
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
//通过名称排除自动配置类
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
//自定义扫描包
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
//自定义扫描的类
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
显然它能够定义扫描那些包,但是要注意,它提供的exclude和excludeName两个方法是对于其内部的自动装配类菜生效的。为了排除其他类,可以再加入@ComponentScan以达到我们的目的。例如扫描User而不扫描UserService,可以吧启动配置文件写成:
(excludeFilters = {@ComponentScan.Filter(classes = Service.class)})public class BootAllApplication {
public static void main(String[] args) {
SpringApplication.run(BootAllApplication.class, args);
}
}
这样就能够扫描指定的包并且排除对应的类了。
确实没有生成service
2. 自定义第三方Bean
现实的Java应用往往需要引入许多来自第三方的包,并且很有可能希望把第三方的类对象也放入到Spring IoC容器中,这时@Bean注解就可以发挥作用了。
例如,要引入一个DBCP数据源,我们先在pom.xml上加入项目所需的DBCP包和数据库Mysql的驱动依赖。代码如下
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
这里DBCP和数据库驱动就被加入到项目中,接着使用他提供的机制来生成数据源。这时候可以吧代码清单的代码放入Appconfig.java中。
package cn.hctech2006.boot.bootall.config;import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@ComponentScan(value = "cn.hctech2006.boot.bootall.*",excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})
public class AppConfig {
@Bean(name = "dataSource")
public DataSource getDataSource(){
Properties props = new Properties();
props.setProperty("driver", "com.mysql.cj.jdbc.Driver");
props.setProperty("url",
"jdbc:mysql://172.17.0.1:3306/hc_official_website_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try{
dataSource = BasicDataSourceFactory.createDataSource(props);
}catch (Exception e){
e.printStackTrace();
}
return dataSource;
}
}这里通过@Bean定义了其配置项name为“dataSource”,那么Spring就会把他返回的对象用名称“dataSource”保存在IoC容器中。当然你也可以不填写这个名称,那么他就会用你的方法名称作为Bean的名称保存到IoC容器中。通过这样,就可以将第三方类装配到Spring IoC容器中。