携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
⭐️前面的话⭐️
✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《王道408》,📚《深入理解 Java 虚拟机-周志明》,📚《Java 核心技术卷》
💬算法刷题:✅力扣🌐牛客网
🎈Github
🎈码云Gitee
四、注解开发
11 IOC/DI 注解开发
# 要想真正简化开发,就需要用到Spring的注解开发,Spring对注解支持的版本历程:
2.0版开始支持注解
2.5版注解功能趋于完善
3.0版支持纯注解开发
# 关于注解开发,我们会讲解两块内容:注解开发定义bean和纯注解开发。
注解开发定义bean用的是2.5版提供的注解,纯注解开发用的是3.0版提供的注解。
11.1 注解开发定义bean Spring 2.5
添加注解
//@Component定义bean
//@Component("bookDao")
//@Repository:@Component衍生注解
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
@Component注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称获取
配置Spring的注解包扫描
<context:component-scan base-package="com.itheima"/>
base-package指定Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。
对于@Component注解,还衍生出了其他三个注解@Controller、@Service、@Repository
通过查看源码会发现:
这三个注解和@Component注解的作用是一样的,为什么要衍生出这三个呢?
方便我们后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类。
11.2 纯注解开发 Spring 3.0
上面已经可以使用注解来配置bean,但是依然有用到配置文件,在配置文件中对包进行了扫描,Spring在3.0版已经支持纯注解开发。
Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道。
- Java类代替Spring核心配置文件
//声明当前类为Spring配置类
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
public class SpringConfig {
}
@Configuration注解用于设定当前类为配置类
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
- @ComponentScan({com.itheima.service","com.itheima.dao"})
- 读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
11.3 注解开发bean作用范围与生命周期管理
使用注解已经完成了bean的管理,接下来按照前面所学习的内容,将通过配置实现的内容都换成对应的注解实现,包含两部分内容: bean作用范围和bean生命周期。
# 关于作用范围,要想将BookDaoImpl变成非单例
只需要在其类上添加@scope注解
默认值singleton(单例),可选值prototype(非单例)
# 如何对方法进行标识,哪个是初始化方法,哪个是销毁方法?
只需要在对应的方法上添加@PostConstruct和@PreDestroy注解即可。
// @Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
// @PostConstruct设置bean的初始化方法
// 在构造方法之后执行,替换 init-method
@PostConstruct
public void init() {
System.out.println("init ...");
}
// @PreDestroy设置bean的销毁方法
// 在销毁方法之前执行,替换 destroy-method
@PreDestroy
public void destroy() {
System.out.println("destroy ...");
}
}
测试
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();
}
}
11.4 小结
11.5 注解开发依赖注入
Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现。
按类型注入 @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();
}
}
# @Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是写在属性上并将 setter方法删除掉
# 为什么setter方法可以删除呢?
自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
普通反射只能获取public修饰的内容
暴力反射除了获取public修饰的内容还可以获取private修改的内容
所以此处无需提供setter方法
# @Autowired是按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加 BookDaoImpl2
# 此时,按照类型注入就无法区分到底注入哪个对象,
解决方案:按照名称注入
# 此时就可以注入成功,但是得思考个问题:
# @Autowired是按照类型注入的,给BookDao的两个实现起了名称,它还是有两个bean对象,为什么不报错?
@Autowired默认按照类型自动装配,如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配。
因为变量名叫bookDao而容器中也有一个booDao,所以可以成功注入。
按名称注入 @Autowired + @Qualifier
当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候该如何解决?
就需要使用到@Qualifier来指定注入哪个名称的bean对象。
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
//@Qualifier:自动装配bean时按bean名称装配
@Qualifier("bookDao")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
@Qualifier注解后的值就是需要注入的bean的名称。
注意:@Qualifier不能独立使用,必须和@Autowired一起使用
简单数据类型注入 @Value("itheima")
引用类型看完,简单类型注入就比较容易懂了。简单类型注入的是基本数据类型或者字符串类型。
下面在BookDaoImpl类中添加一个name属性,用其进行简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("itheima")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
介绍完后,会有一种感觉就是这个注解好像没什么用,跟直接赋值是一个效果,还没有直接赋值简单,所以这个注解存在的意义是什么?
读取properties配置文件 @Value("${name}")
@Value一般会被用在从properties配置文件中读取内容进行使用,具体如何实现?
1.resource下准备properties文件
2.使用注解加载properties配置文件
3.使用@Value读取配置文件中的内容
name=itheima888
@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
# 如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个
@PropertySource({"jdbc.properties","xxx.properties"})
# @PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件
@PropertySource({"classpath:jdbc.properties"})
12 IOC/DI 注解开发管理第三方bean
第三方bean管理 @Bean
# 前面定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办?
遇到上述问题,我们就需要有一种更加灵活的方式来定义bean,这种方式不能在原始代码上面书写注解,一样能定义bean。
这就用到了一个全新的注解@Bean。
- 使用独立的配置类管理第三方bean
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//1.定义一个方法获得要管理的对象
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 将独立的配置类加入核心注解
使用@Import注解手动加入配置类到核心配置,次注解只能添加一次,多个数据请用数组格式
@Configuration
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
第三方bean依赖注入
在使用@Bean创建bean对象的时候,如果方法在创建的过程中需要其他资源该怎么办?
这些资源会有两大类,分别是简单数据类型 和引用数据类型。
简单数据类型 @Value
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//1.定义一个方法获得要管理的对象
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
引用数据类型
假设在构建DataSource对象的时候,需要用到BookDao对象,该如何把BookDao对象注入进方法内让其使用呢?
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。
- 在SpringConfig中扫描BookDao
扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象
@Configuration
@ComponentScan("com.itheima")
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
- 在JdbcConfig类的方法上添加参数
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
// 属性设置
return ds;
}
13 注解开发总结
前面我们已经完成了XML配置和注解的开发实现,至于两者之间的差异,咱们放在一块去对比回顾下: