spring应用手册
2、IOC(注解实现)
2.1注解实现spring的Helloworld
[1]创建maven项目,添加依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
[2]准备两个DAO接口和实现类:
接口:
/**
* @author 戴着假发的程序员
*
* @description
*/
public interface IAutorDAO {
public String get();
}
/**
* @author 戴着假发的程序员
*
* @description
*/
public interface IArticleDAO {
public int save(String title);
}
实现类:
/**
* @author 戴着假发的程序员
*
* @description
*/
public class ArticleDAO implements IArticleDAO {
@Override
public int save(String title) {
System.out.println("ArticleDAO-save->保存文章:"+title);
return 1;
}
}
/**
* @author 戴着假发的程序员
*
* @description
*/
public class AuthorDAO implements IAutorDAO {
@Override
public String get() {
return "戴着假发的程序员";
}
}
[3]准备ArticleService
/**
* @author 戴着假发的程序员
*
* @description
*/
public class ArticleService {
private IAutorDAO autorDAO;
private IArticleDAO articleDAO;
public void setAutorDAO(IAutorDAO autorDAO) {
this.autorDAO = autorDAO;
}
public void setArticleDAO(IArticleDAO articleDAO) {
this.articleDAO = articleDAO;
}
public int save(String title){
String author = autorDAO.get();
System.out.println("ArticleService-save:");
System.out.println("author:"+author);
return articleDAO.save(title);
};
}
[4]添加一个配置类,并且在配置类中使用@Bean的方式配置上面的是三个类:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
public class AppConfig {
//配置 ArticleDAO对象
@Bean
public IArticleDAO articleDAO(){
return new ArticleDAO();
}
//配置 AuthorDAO
@Bean
public IAutorDAO authorDAO(){
return new AuthorDAO();
}
//配置ArticleService对象
@Bean
public ArticleService articleService(){
ArticleService articleService = new ArticleService();
//注入对应的属性
articleService.setArticleDAO(articleDAO());
articleService.setAutorDAO(authorDAO());
return articleService;
}
}
[5]测试:
@Test
public void testAnnotation(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
ArticleService bean = ac.getBean(ArticleService.class);
bean.save("spring应用手册");
}
2.2Hellowrold程序的解析
[1]@Configuration
@Configuration注解表示要给类为配置类。
我们在AppConfig类上方添加@Configuration注解,表示当前类是一个配置类。配置类本身也是spring容器中的一个元素,spring也会加载配置类,并且为其生成对应的BeanDefinition缓存。
一个配置类和一个配置文件是对等。
/**
* @author 戴着假发的程序员
*
* @description
*/
@Configuration
public class AppConfig {
}
相当于:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" 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">
</beans>
你可以在配置类中按照注解的标准注解bean。
当然你可以像配置文件一样在你的工程中添加多个配置类。(多个配置类的使用参见后面的容器创建方式)
注意:@Configuration修饰的类不能是final的。理论上也不能是abstract修饰的。
[2]@Bean
@Bean注解的作用就是给spring容器中注册一个bean对象。
一个@Bean就类似于配置文件中的一个bean标签。(关于@bean的属性,参见后面的@Bean属性详解)
//配置 ArticleDAO对象
@Bean
public IArticleDAO articleDAO(){
return new ArticleDAO();
}
//相当于添加了如下的配置
//<bean id="articleDAO" class="com.st.dao.IArticleDAO">
@Bean都是写在方法上方的,表示这个方法的返回值对象将会被添加到spring的容器中。很明显通过@Bean的方式添加的对象,创建方式是由我们自己定义的。
@Bean添加的对象在默认情况下,这个bean的id就是对应的方法名。 比如:IArticleDAO对象的id就是articleDAO.
注意:@Bean方法返回值如果是void,则不会进行类注册动作。 @Bean的方法当然也可以是static修饰的。
[3]容器初始化 我们可以像加载配置文件一样加载配置类完成容器创建:
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
很明显我们使用的ApplicationContext类的实现类不在是ClassPathXmlApplicationContext类。
这里我们使用AnnotationConfigApplicationContext类,传入的参数是配置类的Class对象。
当然我们还可以这样使用:
public AnnotationConfigApplicationContext(org.springframework.beans.factory.support.DefaultListableBeanFactory beanFactory) { /* compiled code */ }
public AnnotationConfigApplicationContext(java.lang.Class<?>... annotatedClasses) { /* compiled code */ }
我们可以在创建AnnotationConfigApplicationContext时传入不同的参数:
beanFactory:我们可以传入一个beanFactory对象(关于beanFactory参看beanFactory的详解)
class类型的可变参数:如果我们有多个配置类,我们可以传入一个或者多个配置类的class对象,例如:
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class, AppConfig1.class);
2.3@Bean的value和name属性
源码:
@org.springframework.core.annotation.AliasFor("name")
java.lang.String[] value() default {};
@org.springframework.core.annotation.AliasFor("value")
java.lang.String[] name() default {};
@Bean中的name和value属性 和 配置文件中的bean标签的name属性有同样的功能。
@Bean配置的类的默认id是方法的名称,但是我们可以通过value或者name给这个bean取别名。 注意:value和name属性不能并存。 而且如果配置了value或者name,那么我们将无法在通过方法名称获取这个bean了。
例如:
//配置ArticleService对象
@Bean(value="aservice")
//@Bean(name="aservice")
public ArticleService articleService(){
ArticleService articleService = new ArticleService();
//注入对应的属性
articleService.setArticleDAO(articleDAO());
articleService.setAutorDAO(authorDAO());
return articleService;
}
我们可以通过下面的方式获取service对象:
通过类型获取:
ArticleService bean = ac.getBean(ArticleService.class);
通过方法名称获取:
如果配置了value或者name,那么下面的操作会失效
ArticleService bean = (ArticleService) ac.getBean("articleService");
通过别名获取:
value和name配置别名,但是两个属性不能共存,也不能使用特殊符号,也不能同时配置多个别名
ArticleService bean = (ArticleService) ac.getBean("aservice");
2.4@Bean的autowire属性
源码:
@java.lang.Deprecated
org.springframework.beans.factory.annotation.Autowire autowire() default org.springframework.beans.factory.annotation.Autowire.NO;
autowire和配置文件中的autowire有同样的作用。 表示自动组装方式。 通过读取源码我们发现spring5.x开始已经不建议使用了。
Autowrire的几个值:
No:不启用自动装配,这也是默认值。
byName: 通过属性的名字的方式查找JavaBean依赖的对象并为其注入。
byType:通过属性的类型查找JavaBean依赖的对象并为其注入。但是如果同一种类型出现多个bean就会出错。
constructor:和byType一样,也是通过类型查找依赖对象。但是是通过构造方法注入。spring5.x已经没有了
autodetect:在byType和constructor之间自动的选择注入方式。 spring5.x已经没有了
default:由上级标签beans的default-autowire属性确定。 spring 5.x已经没有了
我们可以测试一个byType配置:
我们将之前我们手动注入属性的代码注释,然后再进行测试:
//配置ArticleService对象
@Bean(name="aservice",autowire = Autowire.BY_TYPE)
public ArticleService articleService(){
ArticleService articleService = new ArticleService();
//注入对应的属性
// articleService.setArticleDAO(articleDAO());
// articleService.setAutorDAO(authorDAO());
return articleService;
}
2.5@Bean的autowireCandidate属性
boolean autowireCandidate() default true;
autowireCandidate和配置文件bean标签的autowireCandidate属性一样,就是让其他的bean在按照类型注入时,忽略当前的bean。 默认值true。
我们可以在添加一个ArticleDAO的实现类:
/**
* @author 戴着假发的程序员
*
* @description
*/
public class ArticleDAO_other implements IArticleDAO {
@Override
public int save(String title) {
System.out.println("ArticleDAO_other-save->保存文章:"+title);
return 1;
}
}
并且在AppConfig类中添加其注册信息:
@Bean
public static IArticleDAO articleDAOOther(){
return new ArticleDAO_other();
}
注意:ArticleService的装配方式依然是byType
@Bean(name="aservice",autowire = Autowire.BY_TYPE)
public ArticleService articleService(){
ArticleService articleService = new ArticleService();
return articleService;
}
这时我们再测试:报错:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'aservice' defined in com. st.dk.demo3.AppConfig: Unsatisfied dependency expressed through bean property 'articleDAO'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com. st.dk.demo3.dao.IArticleDAO' available: expected single matching bean but found 2: articleDAO,articleDAOOther
我们修改配置:
@Bean(autowireCandidate = false)
public static IArticleDAO articleDAOOther(){
return new ArticleDAO_other();
}
测试会通过。
由于我们在articleDAOOther方法上方的@Bean中配置autowireCandidate为false,则spring在给ArticleService装配IArticleDAO时,就忽略了ArticleDAO_other,那么就只能找到一个ArticleDAO,那么自然就了。
注意:如果你是按照名称注入的,那么无论你是否配置autowireCandidate,都可以正常注入。
2.6@Bean的initMethod属性
java.lang.String initMethod() default "";
initMethod属性通过bean标签中的initMethod属性一样,也是来配置实例化之后的初始化方法的。
参照spring的bean加载流程。
spring在创建bean对象之后就会调用initMethod指定的初始化回调方法。
案例:
我们修改ArticelDAO的程序:添加一个构造方法和一个初始化回调方法init。
/**
* @author 戴着假发的程序员
*
* @description
*/
public class ArticleDAO implements IArticleDAO {
public ArticleDAO(){
System.out.println("创建ArticleDAO对象");
}
public void init(){
System.out.println("执行ArticleDAO的初始化回调方法init");
}
@Override
public int save(String title) {
System.out.println("ArticleDAO-save->保存文章:"+title);
return 1;
}
}
在ArticleDAO的@Bean注册方法中添加配置:
//配置 ArticleDAO对象
@Bean(initMethod = "init")
public static IArticleDAO articleDAO(){
return new ArticleDAO();
}
在测试:
一旦创建容器就会创建对象,并且执行其初始化回调方法init。
2.7@Bean的destroyMethod属性
java.lang.String destroyMethod() default "(inferred)";
destroyMethod和bean标签的destroyMethod属性一样,是用来配置释放资源的回调方法。一旦配置了,spring会在销毁这个bean之前调用这个释放资源的回调方法。
我们在ArticleDAO中添加方法:
public void destroy(){
System.out.println("执行ArticleDAO的资源释放回调方法destroy");
}
修改配置,增加资源释放的回调方法配置:
//配置 ArticleDAO对象
@Bean(initMethod = "init",destroyMethod = "destroy")
public static IArticleDAO articleDAO(){
return new ArticleDAO();
}
测试:
我们会看到,spring在销毁容器之前,会先销毁bean,销毁bean之前会先调用bean的释放资源的回调方法。
2.8 spring自动扫描bean
我们可以通过配置basePackages让spring自动扫面有spring标准注释(参见对应的标准注释)的bean。
查看AnnotationConfigApplicationContext构造方法:
public AnnotationConfigApplicationContext(java.lang.String... basePackages)
我们可以在创建AnnotationConfigApplicationContext对象时传入参数basePackage,当然这是一个可变参数,所以你同时可以传入多个basePackage。
spring会自动扫描你传入的包以及其子孙包下的所有类,查看这些类上方是否有spring的标准注释。如果有spring会根据注释完成bean的加载。
案例:
我们在com. st.dk.demo4包下创建如下结构:
在三个DAO的实现类上添加注解@Component (@Component注解详情请参看@Component的详情章节)。
我们以下面的方式创建spring容器:
//创建spring容器,并且传入要扫面的包(这里可以传入多个)
ApplicationContext ac =
new AnnotationConfigApplicationContext("com.st.dk.demo4");
测试:
2.9 @Component注解注册Bean
@Component是spring的一个标准注解
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
@org.springframework.stereotype.Indexed
public @interface Component {
java.lang.String value() default "";
}
它的注册目标是TYPE,它的主要作用是将当前类标注为要注册在spring中的类。 那么spring在扫描这个类的时候就会对这个类进行加载,翻译,实例化。
案例:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component
public class AuthorDAO implements IAutorDAO {
@Override
public String get() {
return "戴着假发的程序员";
}
}
当srping扫描AuthorDAO类时会对该类进行加载。
我们可以通过@Component的value属性配置该类的对象在spring容器中的id/name。默认情况下如果不配置,spring会自动为改类的bean生成一个bean,规则就是将当前类的简称类名的首字母小写生成一个字符串。
例如:authorDAO 我们可以通过下面的方式获取
IAutorDAO autorDAO = (IAutorDAO) ac.getBean("authorDAO");
当然如果当前类的简称中如果前面连续两个大写字母,则spring就不会再将首字母小写了。
例如:原来的类名是:AUthorDAO,spring生成的name是:AUthorDAO。
我们也可以通过value属性制定name:
/**
* @author 戴着假发的程序员
*
* @description
*/
@Component("adao")
public class AuthorDAO implements IAutorDAO {
@Override
public String get() {
return "戴着假发的程序员";
}
}
注意:你一旦制定了value,spring就不会再自动生成name。那么你如果通过name获取bean则要使用制定的name。当然如果你是通过类型获取的,就不需要担心这个问题了。
IAutorDAO autorDAO = (IAutorDAO) ac.getBean("adao");
一般情况下我们都不会去修改这个value,但是如果在一个容器中出现了多个同类型的bean,我们就可以通过value指定name,方便我们通过name获取bean对象。
2.10@Repository、@Service和@Controller
这三个注解是我们在开发中经常用到的注解,我们来看看这些注解的源码。
package org.springframework.stereotype;
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
@org.springframework.stereotype.Component
public @interface Repository {
@org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
java.lang.String value() default "";
}
package org.springframework.stereotype;
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
@org.springframework.stereotype.Component
public @interface Service {
@org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
java.lang.String value() default "";
}
package org.springframework.stereotype;
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
@org.springframework.stereotype.Component
public @interface Controller {
@org.springframework.core.annotation.AliasFor(annotation = org.springframework.stereotype.Component.class)
java.lang.String value() default "";
}
通过查看源码,我发现这三个注解都“继承”自@org.springframework.stereotype.Component。而且各自都没有特殊的扩展和属性。 所以这三个注解的功能和@Component完全相同。 既然完全相同为什么要出现这三个注解呢?
主要是为了提高我们代码的可读性。 我们都知道在实际开发中我们都是通过包扫描和注解的方式完成bean注册。
那么为了方便我们标记各种bean的不同种类就有了这三个注解。
@Repository : 一般都是用来标记数据持久化的类。
@Service:一般都是用来标记业务处理的类。
@Controller:一般都是用来标记后端控制器的类(参照web模块)。
当然其实这四个注解是可以互换的。
未完待续...... 欢迎关注