上一篇 Spring Boot IoC(二)IoC简介和BeanFactory
1.首先定义一个简单的java对象(Plain Ordinary Java Object) User
package com.lay.ioc.pojo;
public class User {
private Long id;
private String userName;
private String message;
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 getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2.然后定义一个java配置文件AppConfig
package com.lay.ioc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.lay.ioc.pojo.User;
@Configuration
public class AppConfig {
@Bean(name = "user")
public User initUser() {
User user = new User();
user.setId(1L);
user.setUserName("lay");
user.setMessage("hello world");
return user;
}
}
需要注意两个注解,@Configuration和@Bean
@Configuration:代表这是一个Java配置文件,Spring会根据它来生成IoC容器去装配Bean
@Bean:代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有配置它,则将方法名initUser
作为Bean的名称保存到IoC容器中。
3.构建Ioc容器测试
这里我注释掉了主类的@SpringBootApplication
和SpringApplication
的run()
方法,取消了SpringBoot默认的自动加载。通过手动装配Bean的方式加载到 AnnotationConfigApplicationContext
这个基于注解的IoC容器中。
package com.lay.ioc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lay.ioc.config.AppConfig;
import com.lay.ioc.pojo.User;
//@SpringBootApplication
public class IocApplication {
private static Logger log=LoggerFactory.getLogger(IocApplication.class);
public static void main(String[] args) {
// SpringApplication.run(IocApplication.class, args);
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user=ctx.getBean(User.class);
log.info(user.getMessage());
}
}
输出
-------省略-------
16:09:57.868 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'initUser'
16:09:57.868 [main] INFO com.lay.ioc.IocApplication - hello world
user的message信息hello world
已经输出,说明名称为user的Bean已经被加载到IoC容器中。
如果在Bean很多的情况下,一个个的使用@Bean来装配会很麻烦,Spring允许我们扫描进行Bean的装配。
两个注解,@Component
和@ComponentScan
@Component:标注哪个类被扫描进入Spring IoC容器。
@ComponentScan:标注采用何种策略去扫描装配Bean。
将User.java
移动到包com.lay.ioc.config
配置包下,
package com.lay.ioc.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Component 表明会被Ioc容器扫描
* user为Bean的名称,如果不配置默认会把类名第一个小写作为Bean名称
* @author Lay
*/
@Component("user")
public class User {
/**
* @Value 指定具体的值,然后IoC注入属性
*/
@Value("1")
private Long id;
@Value("lay")
private String userName;
@Value("hello scan world")
private String message;
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 getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
配置文件AppConfig.java
package com.lay.ioc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @ComponentScan 标明会进行扫描
* @author Lay
*/
@ComponentScan
@Configuration
public class AppConfig {
}
这里需要注意的是, @ComponentScan不加任何参数的话只会扫描类AppConfig所在的当前包和其子包,第一步把User.java
移动到com.lay.ioc.config
包下就是这个原因。配置类中去掉了之前的使用@Bean方法来配置的内容。
测试
package com.lay.ioc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lay.ioc.config.AppConfig;
import com.lay.ioc.config.User;
public class IocApplication {
private static Logger log=LoggerFactory.getLogger(IocApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user=ctx.getBean(User.class);
log.info(user.getMessage());
}
}
打印结果
17:08:58.890 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
17:08:58.893 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'user'
17:08:58.893 [main] INFO com.lay.ioc.IocApplication - hello scan world
可以看到成功输出了hello scan world ,采用扫描装配成功。
移动java文件的方式显得不太合理,所以我们可以采用自定义配置扫描。
查看@ComponentScan源码
@Retention(RetentionPolicy.RUNTIME)
@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 ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
// 是否启用默认的过滤模式
boolean useDefaultFilters() default true;
// 当满足过滤器的条件时扫描
Filter[] includeFilters() default {};
// 当不满足过滤器条件时扫描
Filter[] excludeFilters() default {};
// 是否延迟初始化
boolean lazyInit() default false;
// 定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
// 过滤器类型,可以按注解类型或者正则表达式等过滤
FilterType type() default FilterType.ANNOTATION;
// 定义过滤器的类
@AliasFor("classes")
Class<?>[] value() default {};
// 定义过滤器的类
@AliasFor("value")
Class<?>[] classes() default {};
// 匹配方式
String[] pattern() default {};
}
}
其中,下面这些是常用的配置项
// 定义扫描的包
@AliasFor("basePackages")
String[] value() default {};
// 定义扫描的包
@AliasFor("value")
String[] basePackages() default {};
// 定义扫描的类
Class<?>[] basePackageClasses() default {};
// 是否启用默认的过滤模式
boolean useDefaultFilters() default true;
// 当满足过滤器的条件时扫描
Filter[] includeFilters() default {};
// 当不满足过滤器条件时扫描
Filter[] excludeFilters() default {};
我们把User.java
在放回com.lay.ioc.pojo
包下,这样User和AppConfi就在不同的包下。修改AppConfig
package com.lay.ioc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.lay.ioc.pojo.User;
/**
* @ComponentScan 标明会进行扫描
* @author Lay
*/
@ComponentScan("com.lay.ioc.*")
// 或 @ComponentScan(basePackages= {"com.lay.ioc.pojo"})
// 或 @ComponentScan(basePackageClasses= {User.class})
@Configuration
public class AppConfig {
}
有时候我们的需求是扫描一些包,将一些Bean装配到Spring IoC容器中,而不是想加载这个包里的其他Bean。例如有一个UserService类,为了标注它为服务类,给它添加了@Service注解,如果再次使用扫描策略
@ComponentScan("com.lay.ioc.*")
package com.lay.ioc.service;
import org.springframework.stereotype.Service;
import com.lay.ioc.pojo.User;
@Service
public class UserService {
public void showUser(User user) {
System.out.println(user.getId());
System.out.println(user.getUserName());
System.out.println(user.getMessage());
}
}
测试类
package com.lay.ioc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lay.ioc.config.AppConfig;
import com.lay.ioc.pojo.User;
import com.lay.ioc.service.UserService;
public class IocApplication {
private static Logger log=LoggerFactory.getLogger(IocApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user=ctx.getBean(User.class);
log.info(user.getMessage());
UserService userService=ctx.getBean(UserService.class);
userService.showUser(user);
}
}
输出
18:10:37.537 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'user'
18:10:37.538 [main] INFO com.lay.ioc.IocApplication - hello scan world
18:10:37.538 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'userService'
1
lay
hello scan world
这样UserService
包也会被扫到,为了不被装配,需要把AppConfig.java
扫描的策略修改为
@ComponentScan(basePackages="com.lay.ioc.*",excludeFilters= {@Filter(classes= {Service.class})})
由于加了excludeFilters
的配置 ,使标注了@Service
的 类都将不会配IoC容器扫描注入,这样就可以把UserService排除到Spring IoC容器中了。
@SpringBootApplication源码
@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 {};
}
通过它就能够定义扫描哪些包,按时这里需要特别注意的是,@SpringBootApplication
提供的exclude
和excludeName
两个方法是对于其内部的自动配置类才会生效的。为了能够排除其他类,还可以再加入@ComponengScan
以达到我们的目的。例如,扫描User和不扫描UserService。可以把启动配置文件写成:
@SpringBootApplication(scanBasePackages="com.lay.ioc.*")
@ComponentScan(excludeFilters= {@Filter(classes= {Service.class})})
@Configuration
public class AppConfig {
}
下一篇Spring Boot IoC(四)依赖注入DI