Spring
Spring容器是如何工作的
定义Spring Bean
关于@Configuration和@Bean的配置说明
@Configuration
public class ApplicationConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource());
}
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost/transfer");
dataSource.setUsername("transfer-app");
dataSource.setPassword("secret45");
return dataSource;
}
}
在Application Context中访问Bean
Application Context获取bean对象的方式
ApplicationContext context = SpringApplication.run(...);
// 通过bean id,需要进行类型转换
TransferService ts1 = (TransferService) context.getBean("transferService");
// 使用带类型参数的方法,以避免类型转换
TransferService ts2 = context.getBean("transferService", TransferService.class);
// 如果该类型是唯一的,则是不需要bean id
TransferService ts3 = context.getBean(TransferService.class);
@Bean方法参数注入
- 定义@Bean方法的参数- Spring会找到匹配类型的bean,并注入参数
Bean的配置
显式配置Bean - @Bean
@Configuration
public class TransferModuleConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
@Bean
public AccountRepository accountRepository() {
...
}
}
隐式配置Bean
- 在类的上方添加组件类注解 - @Component
- 在配置类的上方添加注解@ComponentScan指定扫描的包,从而让Spring到指定包下扫描添加了组件类注解的类,将其装配为Bean 组件扫描
组件扫描和BeanId的命名规则
- 默认情况下,beanid取决于类名- 类名首字母大写,第二个字母小写,则beanid为首字母小写后的名称- 否则beanid为类全名
- 若想自定义beanid,可以在注解中定义 @Component("mybeanid")
隐式配置之组件类注解
- @Componnent 通用组件注解
- @Controller 控制器组件
- @Service 业务层组件
- @Repository 持久层组件
注意:这四个注解我们一般是按需添加,但是如果分不清,随便添加至少不会出错
关于组件扫描-@ComponentScan
- 各组件会在启动时被扫描- JAR依赖也会被扫描!- 如果需要扫描的文件太多,可能导致启动变慢- 特别是对于大型应用程序- 在最坏的情况下会慢几秒
- 最佳实践是什么样的?
组件扫描最佳实践
- 非常糟糕:
@ComponentScan({"org", "com"})
- 仍然很糟糕:
@ComponentScan("com")
- 还不错:(平时开发使用)
@ComponentScan("com.bank.app")
- 最优的:(Spring 推荐使用)
@ComponentScan({"com.bank.app.repository", "com.bank.app.service", "com.bank.app.controller"})
从严谨的角度上来说,我们应该把包配得很细致,从实践的角度上来说,可能不会很在意启动上的一点点你效率,同时在项目中也会出现遗漏的问题,所以可以把包配得略大一些,只要项目中取得名字不会和其他冲突,慢的那点时间是可以接受的
什么时候使用哪一种?
显式配置
- 优点:- 集中在一个(或几个)地方- 编写任何你需要的Java代码- 可以对配置类进行单元测试- 可用于所有类(不只是你自己的类)
- 缺点:- 比注解更加冗余
隐式配置-组件扫描
- 对你自己的Bean非常友好
- 优点:- 编辑位置单一(就在类中)- 允许非常快速的开发
- 缺点:- 配置分布在你的代码库中- 难以调试/维护(可以通过规范的命名和规范的分包等手段来让问题变的更小,当达到某种共识时问题可以说不存在了)- 只适用于你自己的代码(不可改变的缺点)
显式配置与隐式配置的混合使用
- 你可以通过多种方式进行混合
- 常用方法- 将隐式配置用于:- 你自己的类
- 将显式配置用于:- 没有添加注解的第三方Bean- 不能更改的遗留代码- 当在单一逻辑位置管理配置是一个很重要的问题的时候
Bean的作用域
Bean作用域:默认
- 默认的作用域是单例的
当一个数据有单例特征,那同时也拥有常驻内存的效果,所以我们可以随时随地的去调用它
Bean作用域:prototype
- 作用域“prototype”(原型:可以理解为非单例的)- 每次引到bean时都会创建新的实例
@Scope 可以加到方法上面 也可以加到类上面 .默认值是singleton ,可以省略,所以一般用于非单例里面
常用Spring作用域
- 最常用的作用域有:
singleton只使用1个实例prototype每次引用到bean时都会创建新的实例session每个用户会话创建新的实例 - 仅限Web环境request每个请求创建新的实例 - 仅限Web环境
引用:可用的作用域
作用域描述singleton持续时间与 ApplicationContext 一致prototype每次调用 getBean() 都会返回一个新的对象 持续时间与持有引用的时间一致,没有引用后,会被作为垃圾而回收session持续时间与用户的HTTP会话一致request持续时间与用户的HTTP请求一致application持续时间与ServletContext一致(Spring 4.0)global持续时间与Portlet应用程序中的全局HttpSession一致(从Spring 5开始过期)thread持续时间与所在的线程一致,在Spring中已定义,但默认未注册websocket持续时间与websocket一致(Spring 4.2)refresh可以超过其application context的重新加载时间 难以确保效果,假设Spring Cloud配置服务器
依赖注入Bean
- @Autowired注入
- @Resource注入
@Autowired注入
- 该注入是Spring框架提供的注解
- 可用于构造方法注入,set方法注入,字段注入
- @Autowired默认是根据类型来匹配的
@Autowired注入方式-3种
- 构造方法注入(Spring推荐的做法,但实际上说一套做一套)
@Autowired // 如果这是唯一的构造方法,该注解是可选的
public TransferServiceImpl(AccountRepository repo) {
this.accountRepository = repo;
}
必须存在唯一的匹配类型的依赖
- set方法注入(Spring 会为参数赋值)
@Autowired
public void setAccountRepository(AccountRepository repo) {
this.accountRepository = repo;
}
注意:构造方法注入和setter注入均支持多参数注入
Spring会从容器中查找符合的值去为参数赋值
- 字段注入
@Autowired
private AccountRepository accountRepository;
即便是private字段也可以注入
但是,不易于单元测试
Spring会从容器中查找符合的值去为属性赋值
- 若需要注入的依赖项不存在,此时会发生什么?
@Autowired依赖:是必须的还是可选的?
- 默认行为:必须的
@Autowired
public void setAccountRepository(AccountRepository repo) {
this.accountRepository = repo;
}
如果依赖项不存在则会出现异常
- 使用required属性覆盖默认行为
@Autowired(required=false)
public void setAccountRepository(AccountRepository repo) {
this.accountRepository = repo;
}
仅当依赖项存在时才会注入
- 若需要注入的依赖类型对应的实例存在多个,会发生什么?
自动装配和消除歧义 - 1
@Autowired注入歧义问题
- 若注入的类型对应的实例存在多个,则编译报错/运行时报错,解决办法有2种:- 注入对象的名称对应某个beanId,则可以注入成功- @Autowired的注入机制:- 先根据类型匹配1. 若没有匹配类型,报错2. 有匹配类型,对应的实例有多个,则自动根据name匹配- 添加@Qualifer注解指定beanId
自动装配和消除歧义 - 2
使用 @Qualifer 注解
@Qualifer也适用于方法注入和属性注入。
组件的名称应该不体现实现细节,除非同一个接口有2个相同的实现(如上所示)
自动装配和消除歧义 - 3
- 自动布线的规则1. 查找与所需类型匹配的唯一bean2. 如果提供了 @Qualifer 则使用3. 尝试根据名称查找匹配的bean
- 示例- 我们有多个Queue bean- Spring会查找id被设置为“ack”的bean
@Resource
- 来自JSR-250,能被EJB 3.0和Spring支持- 根据名称,而不是类型来识别依赖项- 名称是Spring的Bean名称- @Autowired是根据类型来匹配的
- 仅支持Setter和字段注入
1.Setter注入
@Resource(name="jdbcAccountRepository")
public void setAccountRepository(AccountRepository repo) {
this.accountRepository = repo;
}
- 字段注入
@Resource(name="jdbcAccountRepository")
private AccountRepository accountRepository;
关于@Resource
- 当没有提供名称时:- 根据属性/字段的名称进行推断- 若找不到匹配的name,则直接回退到根据类型注入
- 示例- 查找名为accountRepository的Bean- 因为方法名称是setAccountRepository- 然后,查找类型为AccountRepository的Bean
@Resource
public void setAccountRepository(AccountRepository repo) {
this.accountRepository = repo;
}
@Autowired:先根据类型,再根据名称
@Resource:先根据名称,再根据类型
组合注解和元注解
- Stereotype注解
- Meta注解
Stereotype注解(组合注解)
- 组件扫描会检查自身就带有@Component注解的那些注解- 也就是所谓的Stereotype注解
@Service注解是Spring框架的一部分。
预定义的Stereotype注解
- Spring框架的Stereotype注解
注意:其它Spring项目(Spring Web-Service,Spring Integration)提供了它们自己的Stereotype注解。
Meta注解(元注解)
- 可添加在其它注解上的注解- 例如:所有的业务Bean都应该可以使用组件扫描进行配置,并且是事务性的
版权归原作者 可釦--稀饭 所有, 如有侵权,请联系我们删除。