对象加载到ioc容器中的方法
1.直接指定配置类路径加载
new AnnotationConfigApplicationContext(MainConfig.class);
2.使用注解
2.1使用类注解@Component(必须在spring扫描的包路径下才可以)
使用@Component注解告诉spring这是一个需要加载待容器中的类,@Component只是其中的基础注解,以他为基础注解的组合注解也可以,包括但不限于(@Controller@Configuration@Service@Repository)等注解
2.2使用方法注解@Bean
在配置类中创建一个返回对象的方法,在方法上加上@Bean注解就会把返回的对象添加到ioc容器中
public class MainConfig {
/**
* 给容器中注册一个Bean;类型为返回值的类型,优先使用注解里传入的参数为id,如果传入的为空则默认是用方法名作为id
* @return
*/
@Bean("person")
public Person person01() {
return new Person("测试", 20);
}
}
3.在配置类上使用@ComponentScan扫描某个包下的
3.1ComponentScan中的参数
@ComponentScans(
value = {
@ComponentScan(value = "com.yx",
includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Service.class}),
@Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
,@Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})},
excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Service.class})},
useDefaultFilters = false)}
)
//@ComponentScan value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则
//useDefaultFilters默认为true,指示是否自动检测用{@code @Component}标注的类,如果设置为false则所有的扫面都无效
//includeFilters与excludeFilters可以同时存在,如果同时存在则排除的优先级较高,但是两个不同的ComponentScan中的扫面互不影响
public class MainConfig {}
自定义的MyTypeFilter
public class MyTypeFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// TODO Auto-generated method stub
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
//当对象的名称包含ser的时候这个对象需要按照规则过滤,否则不过滤
if (className.contains("ser")) {
return true;
}
return false;
}
}
注意:必须在拥有@Component注解或是直接制定的类上的注解才有效
因为有@Service所以扫描有效果
@Service
@ComponentScan(value = "com")
public class TestService {
}
因为直接指定(new AnnotationConfigApplicationContext(MainConfig.class);)为配置类所以扫描有效果
@ComponentScan(value = "com.yx")
public class MainConfig {
@Bean("person")
public Person person01() {
return new Person("lisi", 20);
}
}
因为没有@Component注解不是配置类所以扫描无效
@Data
@ComponentScan(value = "com.yx")
public class Person {
private String name;
private Integer age;
}
4.在配置类上使用@ComponentScans进行扫描
@ComponentScans其实可以理解为@ComponentScan的升级版本,可以同时扫描多个路径,参考以下源码,@ComponentScans就是可以在一个注解中同时定义多个扫描的排除规则
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
ComponentScan[] value();
}
5.使用@Import注解指定需要导入的类
源码:
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
class的值可以为自己创建的class类,也可以是实现了ImportSelector接口的类,也可以是实现了ImportBeanDefinitionRegistrar接口的类
*/
Class<?>[] value();
}
用法:
/**
*导入的对象有Color.class
MyImportSelector.class, MyImportBeanDefinitionRegistrar.class实现中返回的对象,但是这两个接口的实现不会注入到容器中
*/
@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainImportConfig {
}
ImportSelector用法示例:如果引入了这个类就会把(com.yx.bean.Blue,com.yx.bean.Yellow)这两个对象注入到ioc容器中,但是id为全类名
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// TODO Auto-generated method stub
//importingClassMetadata
//方法不要返回null值
return new String[]{"com.yx.bean.Blue","com.yx.bean.Yellow"};
}
}
ImportBeanDefinitionRegistrar用法示例:调用registry.registerBeanDefinition(“user”, beanDefinition);方法注入对象,这种方式比较灵活,可以用循环方式动态添加参数的值与对象注入的数量
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类;
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//这里面可以写一些自己的判断逻辑,也可以通过registry获取当前容器中的信息
BeanDefinition beanDefinition1 = registry.getBeanDefinition("com.yx.bean.Yellow");
String beanClassName = beanDefinition1.getBeanClassName();
System.out.println(beanClassName);
System.out.println(beanDefinition1);
//获取容器中是否有某个id
boolean person = registry.containsBeanDefinition("person");
/**
* 手动注册一个对象
* @see ConfigurableListableBeanFactory#getBeanDefinition
* * @see org.springframework.beans.factory.support.RootBeanDefinition
* * @see org.springframework.beans.factory.support.ChildBeanDefinition
* public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {}
* void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
* throws BeanDefinitionStoreException;
* 必须创建一个BeanDefinition类型对象把需要的参数封装进去,接口BeanDefinition的实现类有很多
* 例如:RootBeanDefinition,genericBeanDefinition,childBeanDefinition等,
* 然后通过他提供的方法给其中添加属性值等最后调用registerBeanDefinition方法注入
*/
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition("user");
RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addGenericArgumentValue(100);
constructorArgumentValues.addGenericArgumentValue("name");
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("user", beanDefinition);
}
}
6.使用Spring提供的 FactoryBean(工厂Bean)
用法示例:
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
// TODO Auto-generated method stub
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Color.class;
}
//是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}
说明:
注意,需要把这个类添加到容器中才行,添加了这个实现类就会注册getObject返回的对象
//工厂Bean获取的是调用getObject创建的对象
用id获取的对象是getObject对象,如果需要获取FactoryBeand对象需要在对象id钱添加&符号
Object bean1 = applicationContext.getBean(“colorFactoryBean”);
class com.yx.bean.Color
Object bean2 = applicationContext.getBean("&colorFactoryBean");
class com.yx.bean.ColorFactoryBean
这里有个疑问,当添加了这个FactoryBeanspring容器中从id看只多了一个FactoryBean对象,没有color对象,可以通过对象类型访问:applicationContext.getBean(Color.class);求原理是从ioc容器中去获取,如果获取步到就获取FactoryBean然后从FactoryBean中去获取,
是可以从获取到对象的
源码如下:
通过applicationContext.getBean("&colorFactoryBean")获取对象流程
/**
* Return the actual bean name, stripping out the factory dereference
* prefix (if any, also stripping repeated factory prefixes if found).
* @param name the name of the bean
* @return the transformed name
* @see BeanFactory#FACTORY_BEAN_PREFIX
判断是获取FactoryBean对象还是获取源对象,如果beanName以&开头则把这个&截取掉
String FACTORY_BEAN_PREFIX = "&";
*/
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
String beanName = name;
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
return beanName;
}
/**
* Get the object for the given bean instance, either the bean
* instance itself or its created object in case of a FactoryBean.
* @param beanInstance the shared bean instance
* @param name name that may include factory dereference prefix
* @param beanName the canonical bean name
* @param mbd the merged bean definition
* @return the object to expose for the bean
*/
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
//如果beanInstance对象的类型不是FactoryBean或者对象名不是空或者说是&开头的则不做处理,直接返回对象。否则执行下面的getObjectFromFactoryBean方法
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
// BeanFactoryUtils.isFactoryDereference(name)== return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanInstance;
}
Object object = null;
if (mbd == null) {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//如果没有直接返回则会把获取到的对象转换成FactoryBean进入getObjectFromFactoryBean方法去获取对象,源码见下方
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
getObjectFromFactoryBean:重点见postProcessObjectFromFactoryBean方法
/**
* Obtain an object to expose from the given FactoryBean.
* @param factory the FactoryBean instance
* @param beanName the name of the bean
* @param shouldPostProcess whether the bean is subject to post-processing
* @return the object obtained from the FactoryBean
* @throws BeanCreationException if FactoryBean object creation failed
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (e.g. because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (object != null && shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
}
this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
}
}
return (object != NULL_OBJECT ? object : null);
}
}
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (object != null && shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
postProcessObjectFromFactoryBean:
/**
* Obtain an object to expose from the given FactoryBean.
* @param factory the FactoryBean instance
* @param beanName the name of the bean
* @return the object obtained from the FactoryBean
* @throws BeanCreationException if FactoryBean object creation failed
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
return factory.getObject();
}
}, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//重点在这里会直接从FactoryBean中获取对象
object = factory.getObject();
}
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}
// Do not accept a null value for a FactoryBean that's not fully
// initialized yet: Many FactoryBeans just return null then.
if (object == null && isSingletonCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(
beanName, "FactoryBean which is currently in creation returned null from getObject");
}
return object;
}
7.各种方法的总结对比
方法 | 说明 | 应用 |
加载启动配置类 | 只是一个普通的java类,不需要做任何处理,当指定他为配置类的时候就会加载到容器中,一般只有在启动的时候使用会使用到 | ioc容器加载的时候会指定加载入口,配置类,处理框架自动加载的指挥加载指定的配置文件类 |
@Component | 通过注解指定为配置类,只要在@ComponentScan扫描路径下的所有包含@Component注解的类都会添加到容器中,常用的如@Controller,@Service等 | 常见的创建不需要参数的类,比如方法类,但是限制在于必须在扫面的包路经下 |
@Import() | 在包含配置类上添加@Import注解里的值为class类型,都会注册到ioc容器中 | spring容器中大量使用,加载的类无法添加参数,与@Component区别在于需要加载的类不需要添加注解,也不需要在扫描的路径下 |
Selector | 实现Selector接口,方法返回的数组中为类的具体路径,都会加载到容器中 | 是@Import()的一个参数,返回的是类的全路径,于@Import()作用差不多,但是可以定义自己的逻辑判断根据容器中已有信息判断该类是否需要注入 |
@Bean | 在配置类中某个方法上添加@Bean注解,会把方法返回的对象注入ioc容器 | 日常开发中经常使用,通常在已经确认创建类的参数时使用,直接把创建号好的对象注入容器 |
ImportBeanDefinitionRegistrar | 实现ImportBeanDefinitionRegistrar接口的类中重写方法可以手动注册自己创建的想要的类,比较灵活,可以根据自己的需求注入不确定数量和值的类型 | 比较灵活,是spring底层原理,用语实现@Bean的,可以根据容器中已有信息做逻辑判断,也是把创建好的对象直接手动注入,但是更加灵活,可以动态判断获取对象,比如根据配置文件中配置的数量去做循环注入不同对象,有兴趣的可以考虑根据配置文件做多数据源的自动装配,抛弃手动创建配置类对象用@bean注入 |
容器中对象的加载类型
1.@Scope(“prototype”)调整作用域
/** @Scope:调整作用域 prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。
* 每次获取的时候才会调用方法创建对象;
* singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。
* 以后每次获取就是直接从容器(map.get())中拿,
* request:同一次请求创建一个实例
* session:同一个session创建一个实例
需要注意的是如果是单实例是在容器创建的时候就会创建对象添加到容器中,如果是多实例就会在获取对象的时候才会创建对象,而且多实例是不会交给ioc容器管理的,只会创建对象
/*
2.@Lazy懒加载
/**懒加载:
* 单实例bean:默认在容器启动的时候创建对象;
* 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion
*/
3.@Conditional选择注入(可以用于类也可以用于方法)
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
传入的class对象必须实现了Condition接口
*/
Class<? extends Condition>[] value();
}
例如:
/**
*判断是否windows系统,这里获取的是系统变量中的操作系统名称,当然也可以自定义其他方式
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
应用:
/**
*如果加载了这个类呢么这个类如果不满足条件就不会添加到ioc容器中,
加在方法上也是如此,如果有两个不同的实现类判断会优先满足不符合条件的
*/
@Conditional({WindowsCondition.class})
public class MainImportConfig {
}