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());
}
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>
- 参数只有一个的时候,constructor-arg 可以不写name,直接写value
- 不写 index 默认从0开始,如果不写 index 那么我们写 constructor-arg 的顺序需要和构造函数的顺序一致。不一致的时候可以用index来指定,没有指定的 constructor-arg index值还是从上到下的顺序。
- 当构造函数存在重载的时候,我们需要指定 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
- class:静态工厂类的class
- 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
- factory-bean:工厂Bean的id
- 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