要想真正简化开发,就需要用到 Spring 的注解开发,Spring 对注解支持的版本历程:
关于注解开发,我们会讲解两块内容注解开发定义bean
和纯注解开发
。
注解开发定义 bean 用的是 2.5 版提供的注解,纯注解开发用的是 3.0 版提供的注解。
创建一个 Maven 项目
pom.xml 添加 Spring 的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
resources 下添加 applicationContext.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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
添加 BookDao、BookDaoImpl、BookService、BookServiceImpl 类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
public void save() {
System.out.println("book service save ...");
}
}
创建运行类 App
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
将配置文件中的<bean>
标签删除掉
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
在 BookDaoImpl 类上添加@Component
注解
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
注意:@Component 注解不可以添加在接口上,因为接口是无法创建对象的。
XML 与注解配置的对应关系:
为了让 Spring 框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描
<?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">
<context:component-scan base-package="com.itheima"/>
</beans>
说明:
component-scan
base-package 指定 Spring 框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。
运行App
类查看打印结果
在 BookServiceImpl 类上也添加@Component
交给 Spring 框架管理
@Component
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
在 App 类中,从 IOC 容器中获取 BookServiceImpl 对应的 bean 对象,打印
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
//按类型获取bean
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
说明:
BookServiceImpl 类没有起名称,所以在 App 中是按照类型来获取 bean 对象
@Component 注解如果不起名称,会有一个默认值就是当前类名首字母小写
,所以也可以按照名称获取,如
BookService bookService = (BookService)ctx.getBean("bookServiceImpl");
System.out.println(bookService);
对于@Component 注解,还衍生出了其他三个注解@Controller
、@Service
、@Repository
通过查看源码会发现:
这三个注解和@Component 注解的作用是一样的,为什么要衍生出这三个呢?
方便我们后期在编写类的时候能很好的区分出这个类是属于表现层
、业务层
还是数据层
的类。
名称 | @Component/@Controller/@Service/@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 spring 管理的 bean |
属性 | value(默认):定义 bean 的 id |
上面已经可以使用注解来配置 bean,但是依然有用到配置文件,在配置文件中对包进行了扫描,Spring 在 3.0 版已经支持纯注解开发
具体如何实现?
实现思路为:
创建一个配置类SpringConfig
public class SpringConfig {
}
在配置类上添加@Configuration
注解,将其标识为一个配置类,替换applicationContext.xml
@Configuration
public class SpringConfig {
}
在配置类上添加包扫描注解@ComponentScan
替换<context:component-scan base-package=""/>
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
创建一个新的运行类AppForAnnotation
public class AppForAnnotation {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
运行 AppForAnnotation,可以看到两个对象依然被获取成功
至此,纯注解开发的方式就已经完成了,主要内容包括:
@Configuration 注解用于设定当前类为配置类
@ComponentScan 注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@ComponentScan({com.itheima.service","com.itheima.dao"})
读取 Spring 核心配置文件初始化容器对象切换为读取 Java 配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
名称 | @Configuration |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 spring 配置类 |
属性 | value(默认):定义 bean 的 id |
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置 spring 配置类扫描路径,用于加载使用注解格式定义的 bean |
属性 | value(默认):扫描路径,此路径可以逐层向下扫描 |
小结:
这一节重点掌握的是使用注解完成 Spring 的 bean 管理,需要掌握的内容为:
<context:component-san/>
的作用是指定扫描包路径,注解为@ComponentScan
使用注解已经完成了 bean 的管理,接下来按照前面所学习的内容,将通过配置实现的内容都换成对应的注解实现,包含两部分内容:bean作用范围
和bean生命周期
。
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围 可用于设置创建出的 bean 是否为单例对象 |
属性 | value(默认):定义 bean 作用范围, 默认值 singleton(单例),可选值 prototype(非单例) |
(1)在 BookDaoImpl 中添加两个方法,init
和destroy
,方法名可以任意
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
public void init() {
System.out.println("init ...");
}
public void destroy() {
System.out.println("destroy ...");
}
}
(2)如何对方法进行标识,哪个是初始化方法,哪个是销毁方法?
只需要在对应的方法上添加@PostConstruct
和@PreDestroy
注解即可。
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@PostConstruct //在构造方法之后执行,替换 init-method
public void init() {
System.out.println("init ...");
}
@PreDestroy //在销毁方法之前执行,替换 destroy-method
public void destroy() {
System.out.println("destroy ...");
}
}
(3)要想看到两个方法执行,需要注意的是destroy
只有在容器关闭的时候,才会执行,所以需要修改 App 的类
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao1 = ctx.getBean(BookDao.class);
BookDao bookDao2 = ctx.getBean(BookDao.class);
System.out.println(bookDao1);
System.out.println(bookDao2);
ctx.close(); //关闭容器
}
}
(4)运行 App,类查看打印结果,证明 init 和 destroy 方法都被执行了。
注意:@PostConstruct 和@PreDestroy 注解如果找不到,需要导入下面的 jar 包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
找不到的原因是,从 JDK9 以后 jdk 中的 javax.annotation 包被移除了,这两个注解刚好就在这个包中。
名称 | @PostConstruct |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为初始化方法 |
属性 | 无 |
名称 | @PreDestroy |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为销毁方法 |
属性 | 无 |
小结
(1) 在 BookServiceImpl 类的 bookDao 属性上添加@Autowired
注解
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:
写在属性上并将setter方法删除掉
(2)@Autowired 是按照类型注入,那么对应 BookDao 接口如果有多个实现类,比如添加 BookDaoImpl2
@Repository
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2");
}
}
这个时候再次运行 App,就会报错
此时,按照类型注入就无法区分到底注入哪个对象,解决方案:按照名称注入
先给两个 Dao 类分别起个名称
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2" );
}
}
此时就可以注入成功,但是得思考个问题:
bookDao
而容器中也有一个booDao
,所以可以成功注入。
bookDao
名称去找,因为 IOC 容器只有名称叫bookDao1
和bookDao2
,所以找不到,会报NoUniqueBeanDefinitionException
当根据类型在容器中找到多个 bean,注入参数的属性名又和容器中 bean 的名称不一致,这个时候该如何解决,就需要使用到@Qualifier
来指定注入哪个名称的 bean 对象。
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
@Qualifier 注解后的值就是需要注入的 bean 的名称。
注意:@Qualifier 不能独立使用,必须和@Autowired 一起使用
引用类型看完,简单类型注入就比较容易懂了。简单类型注入的是基本数据类型或者字符串类型,下面在BookDaoImpl
类中添加一个name
属性,用其进行简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
数据类型换了,对应的注解也要跟着换,这次使用@Value
注解,将值写入注解的参数中就行了
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("itheima")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
注意数据格式要匹配,如将"abc"注入给 int 值,这样程序就会报错。
介绍完后,会有一种感觉就是这个注解好像没什么用,跟直接赋值是一个效果,还没有直接赋值简单,所以这个注解存在的意义是什么?
注解读取 properties 配置文件
@Value
一般会被用在从 properties 配置文件中读取内容进行使用,具体如何实现?
jdbc.properties
name=itheima888
在配置类上添加@PropertySource
注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
步骤 4:运行程序
运行 App 类,查看运行结果,说明配置文件中的内容已经被加载到
注意:
如果读取的 properties 配置文件有多个,可以使用@PropertySource
的属性来指定多个
@PropertySource({"jdbc.properties","xxx.properties"})
@PropertySource
注解属性中不支持使用通配符*
,运行会报错
@PropertySource({"*.properties"})
@PropertySource
注解属性中可以把classpath:
加上,代表从当前项目的根路径找文件
@PropertySource({"classpath:jdbc.properties"})
名称 | @Autowired |
---|---|
类型 | 属性注解 或 方法注解(了解) 或 方法形参注解(了解) |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 或 方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true/false,定义该属性是否允许为 null |
名称 | @Qualifier |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 |
作用 | 为引用类型属性指定注入的 beanId |
属性 | value(默认):设置注入的 beanId |
名称 | @Value |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 |
作用 | 为 基本数据类型 或 字符串类型 属性设置值 |
属性 | value(默认):要注入的属性值 |
名称 | @PropertySource |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 加载 properties 文件中的属性值 |
属性 | value(默认):设置加载的 properties 文件对应的文件名或文件名组成的数组 |