spring基础之常用组件
一、基于xml注入bean
先看看我们在没有使用注解之前,最早使用xml进行bean的注入是怎么操作的呢?
首先我们需要在项目中创建一个.xml文件然后使用bean标签注册一些组件。现在我们就以注册person这个bean进行举例。
先创建一个需要注册的bean实例
@Data @AllArgsConstructor @NoArgsConstructor public class Person { /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; }
然后再resource下面创建一个xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="com.spring.assembly.bean.Person"> <property name="name" value="让你三行代码"/> <property name="age" value="18"/> </bean> </beans>
新建一个测试类来测试一下
@Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Person person = (Person) context.getBean("person"); //打印结果 System.out.println(person); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
通过测试打印的结果我们可以看到已经打印了我们再xml文件中配置bean的数据。我们先来解读一下,ClassPathXmlApplicationContext,这个是什么意思了?其实就是通过ClassPathXmlApplicationContext把我们类路径下的spring的配置文件加载到容器中,在spring容器中,给我提供了很多方法,其中有个getBean()的方法,这个方法就是通过name去获取容器中已经注册的bean对象,这个name是什么呢?其实就是我们在xml中的那个bean id。如果这两个不一致就在容器中找不到这个bean.
二、基于注解注入bean
上面我们讲完了基于xml的方式注入bean,下面我们接着通过注解的方式注入bean,为什么有了基于xml的方式还要有基于注解的方式呢?主要是基于xml的方式过于繁琐,配置文件多,而基于注解的方式我们只需要一个注解就搞定了,既方便又轻松。
我们基于注解的方式很显然是不需要写xml文件的,下面我们就来实现一个基于注解的方式。
首先我们先创建一个AnnotateConfi类,给这个类上加上@Configuration这个注解,这个注解是什么意思呢?这个注解就是代表是配置类等价于我们的配置文件。 @Bean这个注解是什么意思呢?就是说我们现在给容器中注册一个bean,类型为返回值的类型。
@Configuration public class AnnotateConfig { @Bean public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); } }
下面我们测试一下,我们的容器中有没有这个bean
@Test public void test02(){ ApplicationContext app = new AnnotationConfigApplicationContext(AnnotateConfig.class); //在容器中获取对象 Person person = (Person) app.getBean("person"); //打印结果 System.out.println(person); //获取person这个对象的bean id String[] names = app.getBeanNamesForType(Person.class); for (String name : names) { System.out.println(name); //peron 这里我们没有命名,但是自动给我们取了一个id叫person,对于@Bean这个注解来说就是用的这个方法的名字作为id来使用的, /** * IOC容器会初始化很多javaBean实例,他们是怎么存放的呢?其实就是一个Map对象,map.put("key","value") * 这个key就是我们的id value就是我们javabean的实例,就是和我们配置相关的这个id * 我们要修改这个bean实例的id,直接在@Bean注解中加入就可以了*/ } }
结果很显然我们也是容容器中拿到了这个bean,具体是怎么注册的呢?其实就是通过AnnotationConfigApplicationContext这个容器类把我们的配置文件加载到容器中。然后在通过容器中提供的getBean的方法获取bean对象。
AnnotationConfigApplicationContext这个容器类是一个非常重要的容器类,所有的bean的创建,bean的实例化,bean的前置后置都是在这里类中进行的,这是一个核心类。
对于xml文件配置我们给需要注册的bean设置了id,那么我们通过注解方式注册的bean的id是什么呢?其实容器也给我们提供了一个方法,这个方法getBeanNamesForType就可以获取出来我们通过注解注册的bean的id.
@Test public void test02(){ ApplicationContext app = new AnnotationConfigApplicationContext(AnnotateConfig.class); //在容器中获取对象 Person person = (Person) app.getBean("person"); //打印结果 System.out.println(person); String[] names = app.getBeanNamesForType(Person.class); for (String name : names) { System.out.println(name); } }
通过运行上面的代码我们可以发现我们没有命名但是自动给我们取了一个id叫person,其实对于注解@bean来说,它就是我们的这个方法的名字 (public Person person(){})作为id来使用的。那通过注解的方式来注册bean的id都是这个方法的方法名,那是不是这个id就不能修改呢?这个显然是不对,想要修改bean其实很简单只需要在我们注解@bean后面加上我们自己要修改的bean的id就可以了。
@Configuration public class AnnotateConfig { @Bean("person2") public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); }
容器会初始化很多javaBean,他们到底是怎么存放的呢?其实就是一个很大的map对象,这个map的key就是我们注册bean的id,value就是我们bean的实例。
三、ComponentScan扫描规则
自定义包扫描
ComponentScan这个注解的作用其实就是可以扫描指定路径下的所有的组件
下面我们就来测试一下,先创建一个配置文件给它加上注解指定让它去扫描com.spring.assembly.componentScan这个包下的组件
@Configuration @ComponentScan(value = "com.spring.assembly.componentScan") public class ScanConfig { @Bean public Person person(){ return new Person("让你三行代码",20); } }
然后我们在这个com.spring.assembly.componentScan包下面去创建几个类,然后我们去获取到容器中的bean,看我们能不能获取到
@Controller public class PersonController { }
@Service public class PersonService { }
@Repository public class PersonDao { }
@Test public void test03(){ ApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class); System.out.println("ioc容器创建完成"); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String name : beanDefinitionNames) { System.out.println(name); } }
通过结果我们可以看出@ComponentScan这个注解根据我们的配置的包路径已经扫描到了相对应的组件。
自定义包扫描规则
@ComponentScan这个注解不仅仅是可以自定义包路径扫描,它还可以做到自定义包扫描规则,什么叫自定义包扫描规则呢?通俗一点讲就是我们自定这个包路径下那些组件扫描,那些不扫描。
那现在我们去定义@ComponentScan这个注解扫描com.spring.assembly.componentScan这个包下面的@controller这个注解,@service和@Repository这两个注解不进行扫描。我们只需要在@ComponentScan这个注解里面进行配置就行了
@Configuration @ComponentScan(value = "com.spring.assembly.componentScan", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes ={Controller.class})}, useDefaultFilters = false) public class ScanConfig { @Bean public Person person(){ return new Person("让你三行代码",20); } }
从结果可以看出,我加上了 includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes ={Controller.class})}, useDefaultFilters = false这么一段之后我们的@service和@Repository这两个注解在我们的容器中是没有注册的。为什么加上这段话就不会进行注册呢?includeFilters 的意思就是指定那些组件应该被包含进来(excludeFilters指定按照什么规则排除组件),这个注解是一个数组,每一个元素就是一个Filter对象,这个Filter**就是这些组件扫描过滤的类,从源码不难看出,扫描的规则有下面下面5中,ANNOTATION是按照注解,ASSIGNABLE_TYPE是按照给定的类型,ASPECTJ是使用ASPECTJ表达式,REGEX是使用正则表达式,CUSTOM使用自定义规则,自己写类实现TypeFilter接口。
**useDefaultFilters表示是否启用默认的组件扫描规则,设置false表示不启用默认的过滤规则,用户需要自定义过滤规则,如果设置为true的话,spring会默认扫描符合标准的类。这里有一个坑,就是如果使用了自定义的过滤器的话,我们设置成true会怎么样呢?**下面我们就来测试一下
@Configuration @ComponentScan(value = "com.spring.assembly.componentScan", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes ={Controller.class})}, useDefaultFilters = true) public class ScanConfig { @Bean public Person person(){ return new Person("让你三行代码",20); } }
我们从结果可以看出,如果我们使用自定义过滤规则,useDefaultFilters设置为true,我们自定义的过滤规则就会失效。为什么设置成true就不会生效了?跟进去源码当我们设置useDefaultFilters为true时就进入registerDefaultFilters这个方法,这个方法里面就会执行this.includeFilters.add(new AnnotationTypeFilter(Component.class));这个语句,执行这个语句就是会把所有以注解@Component开头的都会扫描进来。所以当我们设置为true的时候personService和sersonDao这两个bean为什么也会注册进来了,其实@service和@repository顶层也是@component,相当于实现了@component的接口。
四、scope扫描规则
@scope的作用是用于管理spring容器中bean的作用域范围的。
@Scope的取值一般常用的就是singleton和prototype这两种,singleton(默认值)表示spring容器中会为这个bean创建单个bean,在整个容器中共享。prototype表示每次从容器中获取bean的时候都会重新创建一个新的bean。
下面是将bean设置为singleton
@Configuration public class AnnotateConfig { @Scope("singleton") @Bean public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); }
@Test public void test03(){ ApplicationContext context = new AnnotationConfigApplicationContext(AnnotateConfig.class); System.out.println("ioc容器创建完成"); Person person =(Person) context.getBean("person"); Person person1 =(Person) context.getBean("person"); System.out.println(person==person1); }
当我设置为singleton的时候从容器中获取两次bean进行比较他们都是同一个对象。
下面是将bean设置为prototype
@Configuration public class AnnotateConfig { @Scope("prototype") @Bean public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); }
通过结果我们可以看出bean设置为prototype的时候是获取的不同的对象。
五、懒加载@Lazy
什么是懒加载呢?其实就是在我们需要的时候才会去注册我们需要的bean实例,懒加载主要针对的时候单实例bean。下面我们就用代码演示一下。
@Configuration public class AnnotateConfig { @Lazy @Bean public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); }
@Test public void test03(){ ApplicationContext context = new AnnotationConfigApplicationContext(AnnotateConfig.class); System.out.println("容器创建完成"); }
通过结果可以看出来我们的person是没有注册到容器中的。下面我们在演示一下容器创建完之后我们去获取我们的bean.
@Test public void test03(){ ApplicationContext context = new AnnotationConfigApplicationContext(AnnotateConfig.class); System.out.println("容器创建完成"); context.getBean(Person.class); }
可以看出我们的bean已经注册到了容器中,也就是说当我们使用了@Lazy这个注解的时候,容器初始化的时候并不会给我们注册bean,只有我们在第一次时间的时候才会加载到IOC容器中去。
六、@Conditional条件注册bean
@Conditional其实就是根据条件来决定时候去创建这个bean
下面我们定义两个对象person和person2,判断我们当前环境如果是windows我们就不注册person2这个bean.
首先我们定义一个WinCondition这个类,这个类主要是去作为条件去判断,这个类需要去实现spring的Confition接口,重写它的matches方法,这个方法中ConditionContext这个参数就是判断条件可以使用的上下文环境,AnnotatedTypeMetadata这个参数可以获取注解的信息。
public class WinCondition implements Condition { /** * * @param context 判断条件可以使用的上下文环境 * @param metadata 注解的信息 * @return */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //获取到IOC容器中正在使用的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //获取当前环境变量 Environment environment = context.getEnvironment(); String property = environment.getProperty("os.name"); System.out.println(property); if(property.contains("Windows")){ return false; } return true; } }
@Data @AllArgsConstructor @NoArgsConstructor public class Person { /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; }
@Data @AllArgsConstructor @NoArgsConstructor public class Person2 { private String name; private Integer age; }
@Configuration public class AnnotateConfig5 { @Bean public Person person(){ System.out.println("给容器中注册person"); return new Person("让你三行代码",20); } @Conditional(WinCondition.class) @Bean public Person person2(){ System.out.println("给容器中注册person2"); return new Person("让你三行代码",30); } }
@Test public void test04(){ ApplicationContext context = new AnnotationConfigApplicationContext(AnnotateConfig5.class); System.out.println("ioc容器创建完成"); }
从结果不难看出我们并没有打印我们的person2,因为我们限制了,如果判断我们的系统是windows的话person2就不会注册。