Spring整合Junit 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {配置类.class})

1 @Bean和xml里面的bean标签

两种方法可以配置的东西类似,只是写法不同而已 

配置类 

@Configuration
//引入一个xml配置文件
@ImportResource("classpath:/beans.xml")
public class MainConfig {
    //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
    //@Bean
    @Bean("configPerson")
    public Person person01() {
        return new Person("lisi", 20);
    }
}

 beans.xml

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

    <bean id="xmlPerson" class="com.bug.bean.Person">
        <property name="name" value="蔡徐坤"/>
        <property name="age" value="80"/>
        <property name="nickName" value="唱、跳、rap、篮球"/>
    </bean>

</beans>

 测试

@Autowired
ApplicationContext applicationContext;
@Test
public void test() {
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }
}

要导入容器中的 bean

package com.bug.bean;
public class Person {
    private String name;
    private Integer age;
    private String nickName;
    public Person(String name, Integer age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person() {
        super();
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", nickName=" + nickName + "]";
    }
//...
}

2 @Import 手动导入指定的类

public @interface Import {
	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();
}

bean的id是全类名

2.1 导入指定的类

配置类 

@Configuration
//bean的id是全类名 com.bug.bean.Person
//同一个类只会导入一个
@Import({Person.class})
public class MainConfig02 {
}

2.2 实现 ImportSelector 接口的类

 返回要导入类的全类名,这个类就会被导入容器中

配置类

@Configuration
@Import({MyImportSelector.class})
public class MainConfig06 { }

 返回一个String数组,里面是要导入的容器bean的全类名

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
	//返回值,就是到导入到容器中的组件全类名
	//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		//importingClassMetadata
		//方法不要返回null值
		return new String[]{"com.bug.bean.Son","com.bug.bean.Person"};
	}
}

2.3 实现 ImportBeanDefinitionRegistrar 接口的类

@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class MainConfig07 { }

如果容器中没有Person的定义信息,那么我就注册一个 

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
 * AnnotationMetadata:当前类的注解信息
 * BeanDefinitionRegistry:BeanDefinition注册类;
 * 		把所有需要添加到容器中的bean;调用
 * 		BeanDefinitionRegistry.registerBeanDefinition手工注册进来
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    boolean definition = registry.containsBeanDefinition("com.bug.bean.Person");
    if(!definition){
        //指定Bean定义信息;(Bean的类型,Bean。。。)
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Person.class);
        //注册一个Bean,指定bean名
        registry.registerBeanDefinition("Person", beanDefinition);
    }
}

}

3 @ComponentScan 导入

@ComponentScan 就是扫描,我们扫描的时候可以指定扫描范围,扫描那些,不扫描那些等

/**
 * Enumeration of the type filters that may be used in conjunction with
 * {@link ComponentScan @ComponentScan}.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 2.5
 * @see ComponentScan
 * @see ComponentScan#includeFilters()
 * @see ComponentScan#excludeFilters()
 * @see org.springframework.core.type.filter.TypeFilter
 */
public enum FilterType {
	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,
	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,
	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,
	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,
	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM
}
//@ComponentScan  value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则

3.1 扫描指定的注解

package com.bug.bean;
import org.springframework.stereotype.Controller;
@Controller
public class Father {
}
package com.bug.bean;
public class Son extends Father{
}

 useDefaultFilters = false 的意思是禁用掉默认的过滤规则,如果有其他注解(比如@Repository)是不会被扫描的

@Configuration
@ComponentScans(
        value = {
                @ComponentScan(value = {"com.bug.bean"}, includeFilters = {
                        @Filter(type=FilterType.ANNOTATION,classes={Controller.class}), //包含指定注解
                }, useDefaultFilters = false)
        }
)
public class MainConfig03 {}

这个和我们SSM整合的时候,对SpringMVC的配置写法类似

<!--SpringMVC的配置文件,包含网站跳转逻辑的控制,配置  use-default-filters="false" 禁用掉默认的过滤规则 Spring 里面的所有注解这个时候都不会被扫描到-->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
    <!--只扫描控制器。  -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

 输出:

mainConfig03
father

 什么都不配置,这个时候也只会扫描有注解的,Person和Son没有注解是不会被扫描进来的

@Configuration
@ComponentScans(
        value = {@ComponentScan(value = {"com.bug.bean"})}
)
public class MainConfig03 {}

3.2 当前类及其子类

 把Father和Father的子类,添加到容器中。这个时候和注解没有关系(useDefaultFilters = false),只要是当前类和子类都会被扫描进容器。

@Configuration
@ComponentScans(
        value = {
                @ComponentScan(value = {"com.bug.bean"}, includeFilters = {
                        @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={Father.class}), //Father 及其子类
                }, useDefaultFilters = false)
        }
)
public class MainConfig03 {}

 输出:

mainConfig03
father
son

如果我把 classes 换成Object,则 com.bug.bean 包小所有的类都会被扫描到容器中

@Configuration
@ComponentScans(
        value = {
                @ComponentScan(value = {"com.bug.bean"}, includeFilters = {
                        @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={Object.class}), //Object 及其子类
                }, useDefaultFilters = false)
        }
)
public class MainConfig03 {}

3.3 自定义过滤规则

只要写一个类继承 TypeFilter  即可

public class MyTypeFilter implements TypeFilter {
    /**
     * metadataReader:读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到其他任何类信息的
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        System.out.println("--->" + className);
        if (className.contains("Son")) {
            return true;
        }
        return false;
    }
}
@Configuration
@ComponentScans(
        value = {
                @ComponentScan(value = {"com.bug.bean"}, includeFilters = {
                        @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
                }, useDefaultFilters = false)
        }
)
public class MainConfig03 {}

输出:

--->com.bug.bean.Father
--->com.bug.bean.Person
--->com.bug.bean.Son
mainConfig03
son


4 @Conditional

@Conditional 修饰整个类,如果是Windows系统才导入这个配置类配置的bean,包括这个配置类自己

//类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;
@Conditional({WindowsCondition.class})
@Import(Person.class)
@Configuration
public class MainConfig04 {}
//判断是否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;
    }
}

  @Conditional 还可以修饰方法,用于单独判断某个方法

//单独设置
@Configuration
public class MainConfig5 {
    
    @Bean("person")
    public Person person() {
        System.out.println("给容器中添加Person....");
        return new Person("张三", 25);
    }

    /**
     * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
     * <p>
     * 如果系统是windows,给容器中注册("bill")
     * 如果是linux系统,给容器中注册("linus")
     */
    @Bean("bill")
    @Conditional(WindowsCondition.class)
    public Person person01() {
        return new Person("Bill Gates", 62);
    }

    @Conditional(LinuxCondition.class)
    @Bean("linus")
    public Person person02() {
        return new Person("linus", 48);
    }

}
//判断是否linux系统
public class LinuxCondition implements Condition {

    /**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:注释信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // TODO是否linux系统
        //1、能获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        //2、获取类加载器
        ClassLoader classLoader = context.getClassLoader();

        //3、获取当前环境信息 运行时的信息 环境变量 虚拟机变量
        Environment environment = context.getEnvironment();

        //4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        //String property = System.getProperty("os.name");
        String property = environment.getProperty("os.name");

        //可以判断容器中的bean注册情况,也可以给容器中注册bean
        boolean definition = registry.containsBeanDefinition("person");//判断容器中是否包含 person  如果不包含我们可以
        //registry.registerBeanDefinition();

        if(property.contains("linux")){
            return true;
        }
        return false;
    }

}

5 工厂 bean 实现 FactoryBean 接口

5.1 注解实现

@Configuration
public class MainConfig08 {

    @Bean("cai")
    public PersonFactoryBean personFactoryBean(){
        return new PersonFactoryBean();
    }

}
//创建一个Spring定义的FactoryBean
public class PersonFactoryBean implements FactoryBean<Person> {
    //返回一个Person对象,这个对象会添加到容器中
    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        person.setName("蔡徐坤");
        return person;
    }

    //返回对象的类型
    @Override
    public Class<?> getObjectType() {
        return Person.class ;
    }
    //是单例?
    //true:这个bean是单实例,在容器中保存一份
    //false:多实例,每次获取都会创建一个新的bean;
    @Override
    public boolean isSingleton() {
        return true;
    }
}
@Autowired
ApplicationContext applicationContext;

/**
 * PersonFactoryBean 没有在容器中
 */
@Test
public void test() {
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }
}


@Test
public void test02() {
    //工厂Bean获取的是调用getObject创建的对象
    Object object = applicationContext.getBean( "cai");
    System.out.println("bean的类型:"+object.getClass());

    Object object2 = applicationContext.getBean( "cai");
    System.out.println("是否是单实例:"+(object==object2));


    Person person = applicationContext.getBean(Person.class, "cai");
    System.out.println(person);
}

/**
 * 获取工厂bean本身
 */
@Test
public void test03() {
    Object factoryBean2 = applicationContext.getBean("&cai");
    System.out.println(factoryBean2.getClass());
    
    PersonFactoryBean factoryBean = applicationContext.getBean(PersonFactoryBean.class, "&");
    System.out.println(factoryBean.getClass());
}

 

spring导入logback spring导入bean方式_spring导入logback

5.2 xml 实现

 a) 使用构造器创建 Bean 实例

如果不采用构造注入,Spring底层会调用Bean类的无参构造器来创建实例,因此要求该Bean类提供无参数的构造器。在这种情况下,class元素是必须的(除非采用继承),class属性的值就是Bean类的实现类。

beans02.xml 

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

    <!--采用set方法-->
    <bean id="xmlPerson" class="com.bug.bean.Person">
        <property name="name" value="蔡徐坤"/>
        <property name="age" value="80"/>
        <property name="nickName" value="唱、跳、rap、篮球"/>
    </bean>

    <!--采用构造函数-->
    <bean id="constructorPerson" class="com.bug.bean.Person">
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="name" value="小明"/>
    </bean>

    
</beans>
  1.  参数只有一个的时候,constructor-arg 可以不写name,直接写value
  2. 不写 index 默认从0开始,如果不写 index 那么我们写 constructor-arg 的顺序需要和构造函数的顺序一致。不一致的时候可以用index来指定,没有指定的 constructor-arg index值还是从上到下的顺序。
  3. 当构造函数存在重载的时候,我们需要指定  constructor-arg 参数的类型,不然调用哪个构造函数是不确定的。比如type="java.lang.Integer"

public Person(String name, Integer age)  //type="java.lang.Integer" 调用这个
public Person(String name, String nickName) //type="java.lang.String" 调用这个

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans02.xml")
public class MainConfigTest09 {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void test() {
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

}

b)使用静态工程方法创建Bean

  1. class:静态工厂类的class
  2. factory-method:静态工厂静态方法
public class PersonStaticFactoryBean {

    //静态方法
    public static Person getPerson(String args) {
        System.out.println(args);
        return new Person();
    }

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


    <bean id="constructorStaticPerson" class="com.bug.config.PersonStaticFactoryBean" factory-method="getPerson">
        <!--配置静态工厂方法的参数-->
        <constructor-arg name="args" value="args"/>

        <!--驱动Spring以“蔡徐坤”为参数来执行Person的setName()方法-->
        <property name="name" value="蔡徐坤"/>
        <property name="age" value="18"/>
    </bean>


</beans>
@Test
public void test() {
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }

    Object obj = applicationContext.getBean("constructorStaticPerson");
    System.out.println(obj);
}

 c)调用实例工厂方法创建Bean

  1. factory-bean:工厂Bean的id
  2. factory-method:实例工厂的工厂方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--需要配置实例工厂-->
    <bean id="personFactoryBean02" class="com.bug.config.PersonFactoryBean02"/>

    <bean id="constructorPerson"
          factory-bean="personFactoryBean02"
          factory-method="getPerson">
        <!--配置实例工厂方法的参数-->
        <constructor-arg name="name" value="蔡徐坤"/>
        <constructor-arg name="age" value="18"/>
    </bean>


</beans>

注: constructorPerson 可以指定返回值类型,class="com.bug.bean.Person"

public class PersonFactoryBean02 {

    //实例方法方法
    public Person getPerson(String name, Integer age) {
        return new Person(name,age);
    }
}
@Test
public void test() {
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println(name);
    }

    Object obj = applicationContext.getBean("constructorPerson");
    System.out.println(obj);
}

 d)工厂Bean

这里和前面注解实现是一样的,只需要把工厂Bean配置在Xml中即可

Spring容器会自动检测容器中所有的Bean,如果发现某个Bean实现类实现了FactoryBean接口,Spring容器就会在实例化该Bean,根据<property...>执行setter方法之后,额外的调用该Bean的getObject()方法,并将该方法的返回值作为容器中的Bean

演示自由的获取任意类的、任意静态Field的值:

Spring框架本身提供的 org.springframework.beans.factory.config.FieldRetrievingFactoryBean (基本相同的功能)

beans.xml

<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 下面配置相当于如下代码:
    FactoryBean factory = new org.crazyit.app.factory.GetFieldFactoryBean();
    factory.setTargetClass("java.awt.BorderLayout");
    factory.setTargetField("NORTH");
    north = factory.getObject(); -->
    <bean id="north" class="org.crazyit.app.factory.GetFieldFactoryBean">
        <property name="targetClass" value="java.awt.BorderLayout"/>
        <property name="targetField" value="NORTH"/>
    </bean>

    <!-- 下面配置相当于如下代码:
    FactoryBean factory = new org.crazyit.app.factory.GetFieldFactoryBean();
    factory.setTargetClass("java.sql.ResultSet");
    factory.setTargetField("TYPE_SCROLL_SENSITIVE");
    theValue = factory.getObject(); -->
    <bean id="theValue" class="org.crazyit.app.factory.GetFieldFactoryBean">
        <property name="targetClass" value="java.sql.ResultSet"/>
        <property name="targetField" value="TYPE_SCROLL_SENSITIVE"/>
    </bean>

</beans>
public class GetFieldFactoryBean implements FactoryBean<Object> {
    private String targetClass;
    private String targetField;

    // targetClass的setter方法
    public void setTargetClass(String targetClass) {
        this.targetClass = targetClass;
    }

    // targetField的setter方法
    public void setTargetField(String targetField) {
        this.targetField = targetField;
    }

    // 返回工厂Bean所生产的产品
    @Override
    public Object getObject() throws Exception {
        Class<?> clazz = Class.forName(targetClass);
        Field field = clazz.getField(targetField);
        return field.get(null);
    }

    // 获取工厂Bean所生产的产品的类型
    @Override
    public Class<? extends Object> getObjectType() {
        return Object.class;
    }

    // 返回该工厂Bean所生成的产品是否为单例
    @Override
    public boolean isSingleton() {
        return false;
    }

}

 测试

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

// 下面2行代码获取的FactoryBean的产品
System.out.println(ctx.getBean("north"));

System.out.println(ctx.getBean("theValue"));
// ResultSet.TYPE_SCROLL_SENSITIVE = 1005;

// 下面代码可获取的FactoryBean本身
System.out.println(ctx.getBean("&theValue"));

North
1005
org.crazyit.app.factory.GetFieldFactoryBean@cb644e 


6 总结

* 给容器中注册组件;
 * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
 * 2)、@Bean[导入的第三方包里面的组件]
 * 3)、@Import[快速给容器中导入一个组件]
 *      1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
 *      2)、ImportSelector:返回需要导入的组件的全类名数组;
 *      3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
 * 4)、使用Spring提供的 FactoryBean(工厂Bean);
 *      1)、默认获取到的是工厂bean调用getObject创建的对象
 *      2)、要获取工厂Bean本身,我们需要给id前面加一个&  &cai