SPRING-BOOT系列之Spring4快速入门
上节 : spring boot简介
接着上章节的spring boot简介,我们会发现boot是基于spring的,其中最重要的就是spring容器了。那么本章着重介绍spring容器装配自定义bean的几种形式。
并在装配的时候能够学会做一些处理。
1. 新建一个maven项目
2. 引入spring依赖
可以在search.mavem.org这个网站中查找maven的依赖
3. 更改项目对jdk的依赖,这里我们使用jdk1.8
在pom文件中修改为:
<!-- 2. 把版本改成类似java的1.8版本的 --> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target>
(我会在最后粘贴出整个pom.xml文件的)
4. 简单使用注入bean的方式来输出下 :
首先,我们在App.java当中创建一个注解的容器,然后在此构造函数当中注入配置类MyConfig.class,在配置类当中注入bean为MyBean.java;总体结构图如下 :
然后在App.java文件当中获取当前容器,把刚才注入进来的MyBean输出下看看 :
5. 我们也可以根据在MyConfig当中的方法的名称来获取,默认的就是方法名 :
MyConfig代码如下 :
@Bean public MyBean createMyBean(){ return new MyBean(); }
6. 那么,我们也可以在这个方法上的Bean后面声明名称,这样我们通过名称取的时候就是通过这里定义的来取了 :
public static void main( String[] args ){ // System.out.println( "Hello World!" ); // 1. 往构造参数中传递配置类,把配置类当中的bean都注入到上下文当中去 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); // MyConfig.class中注入了MyBean // System.out.println(context.getBean(MyBean.class)); // 根据类型来获取 // 2. 我们也可以根据名称来获取, 默认的就是方法 的名称 // System.out.println(context.getBean("createMyBean")); // MyConfig当中的方法名 // 3. 我们通过Bean那里指定的名称来获取(这个是比较普遍的做法) System.out.println(context.getBean("myLxfBean")); // 一旦指定了这个名称,通过代码这里的2 默认方法名称是获取不到的了 context.close(); }
7. 默认情况下,这个Bean是单例的 :
那么,我们可以通过在Bean的定义那里来声明scope变量来达到多例的模式 :
@Configuration // 声明类是配置类(可以装入到容器当中去) public class MyConfig { @Bean(name="myLxfBean") // 定义Bean的名称 @Scope("prototype") // 转变成多例 public MyBean createMyBean(){ return new MyBean(); } }
8. 上面举例说明了 使用配置类注入到容器当中,进而从容器可以获取到bean的操作,同时还能改变单例多例模式,接下来,我们也可以实现FactoryBean这个接口,
注入自己想要的实体,就可以完成自己的Bean工厂。然后把Bean工厂放入到配置类当中(MyConfig.class),但是我们要改变一些在配置类的名称,因为我们写了
2个一样的bean必须要声明不同的name,要不然我们通过getBean(MyBean.class)是会报错说找到多个bean,如下:
public class MyBeanFactory implements FactoryBean<MyBean>{ // FactoryBean 接口也是创建bean的 @Override public MyBean getObject() throws Exception { return new MyBean(); // 这里不new 就生成不了对象的 } // 获取到Bean的类型 @Override public Class<?> getObjectType() { return MyBean.class; // 改写成我们想要放回的Bean的类型 } public boolean isSingleton(){ return false; // 重写接口的 是不是单例 false不是单例 } }
// System.out.println( "Hello World!" ); // 1. 往构造参数中传递配置类,把配置类当中的bean都注入到上下文当中去 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); // MyConfig.class中注入了MyBean System.out.println(context.getBean(MyBean.class)); // 根据类型来获取 // 2. 我们也可以根据名称来获取, 默认的就是方法 的名称 // System.out.println(context.getBean("createMyBean")); // MyConfig当中的方法名 // 3. 我们通过Bean那里指定的名称来获取(这个是比较普遍的做法) System.out.println(context.getBean("myLxfBean")); // 4. 我们通过往MyConfig当中注入我们的MyBeanFactory System.out.println(context.getBean(MyBeanFactory.class)); context.close();
@Configuration // 声明类是配置类(可以装入到容器当中去) public class MyConfig { @Bean(name="myLxfBean") // 定义Bean的名称 @Scope("prototype") // 转变成多例 public MyBean createMyBean(){ return new MyBean(); } @Bean // 不命名的话 ,容器 会说有找到多个bean会报错的 public MyBeanFactory createMyBeanFactory(){ return new MyBeanFactory(); } }
报错截图 : 很明显截图说找到2个bean了。
所以我们要在同一个配置类当中注入同样的bean的时候要注意给其命名。并且在容器中获取的时候要根据命名来获取,不要根据class的类型来获取。
如下 :
9. 那么我们怎么获取到MyBeanFactory这个类呢?而不是从中获取到MyBean呢,很明显,第一种答案就直接在App当中根据类型指定为MyBeanFactory就能
获取到。如果要通过名称的话,我们通过方法名获取到的是MyBean,我们给其加个&符号获取到的就是MyBeanFactory。
// 3. 我们通过Bean那里指定的名称来获取(这个是比较普遍的做法) System.out.println(context.getBean("myLxfBean")); -- com.CTO_Boot.snapshot.CTO.boot.MyBean@120d6fe6 // 4. 我们通过往MyConfig当中注入我们的MyBeanFactory中的MyBean System.out.println(context.getBean("myFactoryMyBean")); --com.CTO_Boot.snapshot.CTO.boot.MyBean@4ba2ca36
// 5. 获取到MyBeanFactory System.out.println(context.getBean(MyBeanFactory.class)); -- com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@25359ed8 // 6. 我们通过往MyConfig当中注入我们的MyBeanFactory,在容器中使用&符号获取源 System.out.println(context.getBean("&myFactoryMyBean")); --com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@25359ed8(因为使用了单例)
这个&符号可以在BeanFactory这个interface当中找到定义。(嗯,没事多看看源码),也就是说&获取本身,不去获取MyBeanFactory生产的Bean。
10. 如果我们不继承FactoryBean这个接口,我们直接使用普通的Factory呢?首先,我们创建一个JeepFactory,写一个方法为create,此方法是创建Jeep实体类。所以,很显然,
我们得把JeepFactory注入到MyConfig当中,然后再写个方法调用create方法即可放回Jeep这个Bean。
在MyConfig.java当中增加如下代码: @Bean public JeepFactory createJeepFactory(){ return new JeepFactory(); } /* @Bean public Jeep createJeep(){ return createJeepFactory().createJeep(); // 或者改造成传入JeepFactory都是行的 }*/ @Bean public Jeep createJeep(JeepFactory jeepFactory){ return jeepFactory.createJeep(); // 或者改造成传入JeepFactory都是行的 // 那么这个参数spring会默认从容器当中去获取的 }
11. 上面写了一些往容器中注入Bean再获取的一些做法。接下来我们介绍一些在bean生成、销毁等要做一些处理的时候该怎么做。
11.1 我们可以实现spring的一些接口,如实现 InitializingBean 接口
// 要处理的bean public class HandlingBean implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { // 在属性设置之后输出 System.out.println("--- HandlingBean的属性已经设置完毕!!! ----"); } }
输出来的结果是 :
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d646c37: startup date [Tue Apr 24 17:47:15 CST 2018]; root of context hierarchy --- HandlingBean的属性已经设置完毕!!! ---- com.CTO_Boot.snapshot.CTO.boot.MyBean@59717824 com.CTO_Boot.snapshot.CTO.boot.MyBean@146044d7 com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@3c0a50da com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@3c0a50da com.CTO_Boot.snapshot.CTO.boot.Jeep@646be2c3 com.CTO_Boot.snapshot.CTO.boot.HandlingBean@797badd3
11.2 我们要在销毁之前做处理的话,可以实现 DisposableBean 接口 :
// 要处理的bean public class HandlingBean implements InitializingBean,DisposableBean{ @Override public void afterPropertiesSet() throws Exception { // 在属性设置之后输出 System.out.println("--- HandlingBean的属性已经设置完毕!!! ----"); } @Override public void destroy() throws Exception { // 马上要销毁这个Bean了 System.out.println(this.getClass()+"-----该bean马上要销毁了------"); } }
--- HandlingBean的属性已经设置完毕!!! ---- com.CTO_Boot.snapshot.CTO.boot.MyBean@146044d7 com.CTO_Boot.snapshot.CTO.boot.MyBean@1e9e725a com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@646be2c3 com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@646be2c3 com.CTO_Boot.snapshot.CTO.boot.Jeep@797badd3 com.CTO_Boot.snapshot.CTO.boot.HandlingBean@77be656f 四月 24, 2018 5:52:02 下午 org.springframework.context.support.AbstractApplicationContext doClose 信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d646c37: startup date [Tue Apr 24 17:52:01 CST 2018]; root of context hierarchy class com.CTO_Boot.snapshot.CTO.boot.HandlingBean-----该bean马上要销毁了------
11.3 我们如果不实现这些接口的话,单纯的一个javaBean的话,我们也可以在装配到配置类当中的时候在@Bean这个当中指定init跟destroy方法
通过截图看到,我们我效果已经出来了,值得一提的是虽然我们主食了HandlingBean,但是我们依然给其注入到了容器中,所以容器装配了配置类他就会被New。
11.4 除了实现接口之外,在bean注解中指定之外,我们还可以在普通javaBean当中的方法上写注解来指定init跟destory方法:
public class AnimalHandlingBean { // 通过注解指定init跟destroy方法 @PostConstruct // 初始化 public void initDefine(){ System.out.println("--------AnimalHandlingBean已经初始化完成了----------"); } @PreDestroy // 销毁 public void destoryDefine(){ System.out.println("--------AnimalHandlingBean马上要销毁了----------"); } }
我们在把它装配到MyConfig.java当中,在APP当中打印下。
--- HandlingBean的属性已经设置完毕!!! ---- --------DefineHandlingBean已经初始化完成了---------- --------AnimalHandlingBean已经初始化完成了---------- 四月 24, 2018 6:13:12 下午 org.springframework.context.support.AbstractApplicationContext doClose 信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d646c37: startup date [Tue Apr 24 18:13:11 CST 2018]; root of context hierarchy com.CTO_Boot.snapshot.CTO.boot.MyBean@23a5fd2 com.CTO_Boot.snapshot.CTO.boot.MyBean@78a2da20 com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@7bc1a03d com.CTO_Boot.snapshot.CTO.boot.MyBeanFactory@7bc1a03d com.CTO_Boot.snapshot.CTO.boot.Jeep@70b0b186 com.CTO_Boot.snapshot.CTO.boot.DefineHandlingBean@ba8d91c com.CTO_Boot.snapshot.CTO.boot.AnimalHandlingBean@7364985f --------AnimalHandlingBean马上要销毁了---------- --------DefineHandlingBean马上要销毁了---------- class com.CTO_Boot.snapshot.CTO.boot.HandlingBean-----该bean马上要销毁了------
总结下:我们上面使用了3种方式完成bean初始化后和销毁前的操作,第一种使用spring的接口,第二种在bean装配那里指定,第三种基于注解
12. 我们继续来讲解bean的装配,先前我们使用 @Configuration 来把配置类装配到容器的,我们也可以使用 @Compent
@Component public class CompentBean { }
App.java: AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class,CompentBean.class); // MyConfig.class中注入了MyBean // 11. 通过@Compent注解获取到bean System.out.println(context.getBean(CompentBean.class));
13. 我们也可以使用context.getBeansOfType 来获取到指定class的map集合 :
14. 我们如果不想把compentBean注入到容器的话,我们把compentBean写到MyConfig当中,然后就只用在context当中注入MyConfig.java就行。
其实compent这个注解一般是在我们对该类没有一个明确的角色的划分我们就使用compent这个注解。
15. 在dao层使用 @Repository 注解,然后也要注入到context当中,再从容器当中获取(所以是不是想起了我们学习spring的时候,什么ssm框架都要扫描包的):
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository {
16. 同理,我们也可以使用@Service注解,注入到容器。
17. 同理,我们也可以使用@Controller注解,注入到容器当中,再从中获取出来。
18. 我们写一个AopTest的compent组件类,我们往里面写一个属性为TempService的类,我们再把aopTest注入到容器中,我们发现通过:
// 16. 测试能否获取到 在组件类当中不使用autowired注解能否获取到tempService TempService ts = context.getBean(AopTest.class).getTempService(); System.out.println(ts);
通过这种方式是获取不到tempService的,只有:
// 我们测试下 能够直接获取到 TempService对象 @Component public class AopTest { @Autowired private TempService tempService; // 有了autowired之后下面的get、set可有可无 public TempService getTempService() { return tempService; } public void setTempService(TempService tempService) { this.tempService = tempService; } }
只有写了autowired才能获取到。 所以,从这里我们可以看出@Bean跟@Autowired的区别了,当我们想要在容器中直接搞个组件,从组件获取到对象的时候一般都是@Bean;
而我们业务都是一套流程,所以一般都用@Autowired,因为谁想在开发业务的时候还要提前写好生成Bean的配置类,整个工作就让spring的AOP去完成就好了。也就是你可以这样理解autowired在运行时帮我们完成了这个Bean形式的new,就是Bean的创建。
19. 有人这个时候想做个骚操作了,我们在A这个组件类当中写Autowired注解,在B这个组件当中使用Bean的方式,然后把A跟B都注入到容器中,这个时候就会报错获取2个同样的类了。解决这个问题,可以在B这个里面使用 primary注解完成。
@Bean(name="myLxfBean") // 定义Bean的名称 @Scope("prototype") // 转变成多例 @Primary public MyBean createMyBean(){ return new MyBean(); }
或者在A当中指定名称,感觉其底层还是针对Bean的不同命名,到时候根据名称获取就行 :
@Autowired @Qualifier("zhidingmingcheng") private TempService tempService;
20. 如果你在上述19当中,往容器中直接注入了什么serivce类,dao类,那么他最先得到的是这个,而不是从A,B当中new出来的。(补充一点,有个跟autowired差不多的resource注解)
21. 其实写了这么多,我们往往在开发当中是把配置类是在一个包当中写的,而我们写了多个配置类肯定不愿意一个一个去扫描配置类,所以容器当中是有个扫描包的构造方法的 :
public static void main(String[] args) { // 使用扫描的形式,并且拒绝掉一些bean AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.CTO_Boot.snapshot.CTO.boot"); System.out.println(context.getBean(HandlingBean.class)); context.close(); }
22. 我们还有一种方法,就是在配置类当中写compoentScan,在配置类当中扫描包 :
@ComponentScan("com.CTO_Boot.snapshot.CTO.boot") @Configuration public class ConfigScan { }
public class App3 { public static void main(String[] args) { // 使用扫描的形式,并且拒绝掉一些bean AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigScan.class); System.out.println(context.getBean(HandlingBean.class)); context.close(); } }
23. 如果我们在上面22的基础上要排除一些Bean不让其注入进来呢,这个时候我们可以看@CompentScan的源码,里面有excludeFilters跟includeFilters;
我们怎么使用excludeFiters ,它告诉我们要传入filter数组,fitler是什么,我们继续点击进去,发现默认的有5种类型:
ANNOTATION,ASSIGNABLE_TYPE(某个具体的bean或者配置类),ASPECTJ,REGEX(正则表达式),CUSTOM(用户自定义);
我们测试下排除掉MyConfig.class,然后看能不能从容器当中获取到HandlingBean.class:
@ComponentScan(basePackages="com.CTO_Boot.snapshot.CTO.boot",excludeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, classes={MyConfig.class,TempController.class})) // 我们这里是可以写上 TempController,因为@Controller底层就有组件注解啊 @Configuration public class ConfigScan { }
public class App3 { public static void main(String[] args) { // 使用扫描的形式,并且拒绝掉一些bean AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigScan.class); System.out.println(context.getBean(HandlingBean.class)); context.close(); } }