四 基于注解方式使用SpringIoc
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。
4.1 组件(Bean)的注册
思路: 使用注解标记组件,让spring框架识别注解并管理组件。
由于效率原因,spring框架并不会扫描所有的包,去寻找注解,这样效率很低。所以需要我们去告知spring,我们在那些包内使用了注解或者想要springioc管理的组件。
所以组件的注册分为两步: 1.添加注解,2.告知spring添加注解的包,即包扫描
ps:在使用第三方类的时候,如JdbcTemplate类,就无法使用注解去注册,还是要使用xml方式配置
4.1.1添加注解
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解 | 说明 |
---|---|
@Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 |
@Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
四个注解对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
与xml注册bean,相比较:
@Component
public class CommonComponent {
}
//还需要告诉spring,在哪里使用了注解
<bean id="commonComponent" class="com.ls.pojo.CommonComponent"/>
相比较注解不用强制写id值,有默认id值类名首字母小写,当然也可以自定义id值。value=“xxx”
4.1.2包扫描
情况1:基本扫描配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置自动扫描的包 -->
<!-- 1.包要精准,提高性能!
2.会扫描指定的包和子包内容
3.多个包可以使用 base-package="包1,包2,包3"
-->
<context:component-scan base-package="com.ls.components"/>
</beans>
情况2:扫描目标包中排除组件
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.ls.components">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
情况3:扫描目标包指定扫描组件
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.ls.ioc.components" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
之所以包扫描有下面两种方式是因为避免重复扫描,创建重复创建组件造成的资源浪费。:
在后面的三成架构中,室友两个容器 webioc容器,rootioc容器。webioc容器用来存放controller组件,root容器存放初了controller组件的其他所有组件。
4.2 周期性方法和作用域
使用 @PostConstruc @PreDestroy注解需要导入javax-annotation-api
@Component
public class Person {
//周期方法必须 public void 修饰,并且无参
//导入依赖javax.annotation-api,才能使用这个注解
@PostConstruct //注解指定bean的初始化方法
public void one(){
System.out.println("起床~~~~");
}
@PreDestroy//注解指定bean的销毁方法
public void end(){
System.out.println("睡觉~~~~~");
}
}
作用域:
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例 二选一
public class BeanOne {
//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
@PostConstruct //注解制指定初始化方法
public void init() {
// 初始化逻辑
}
}
4.3Bean的依赖注入
4.3.1@Autowired 引用类型注入
**注解@Autowired **
在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。前提参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void show(){
userDao.call();
}
}
这里存在的问题:
参考博客:Field injection is not recommended(Spring团队不推荐使用Field注入)-CSDN博客
注解不仅仅可以放在类上,也可以放在setter方法,和构造器上,对应着xml方式setter方法注入,和构造器注入
@Service
public class UserService {
@Autowired
private UserDao userDao;
//setter方法注入
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//构造器注入
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void show(){
userDao.call();
}
}
-
@Autowired注解细节
-
标记位置
- 成员变量
-
这是最主要的使用方式!
与xml进行bean ref引用不同,他不需要有set方法!
- 工作流程
首先根据所需要的组件类型到 IOC 容器中查找
-
能够找到唯一的 bean:直接执行装配
-
如果完全找不到匹配这个类型的 bean:装配失败
-
和所需类型匹配的 bean 不止一个
-
没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
-
能够找到:执行装配
-
找不到:装配失败
-
使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
-
能够找到:执行装配
-
找不到:装配失败
4.3.2扩展JSR-250注解@Resource
- **扩展JSR-250注解@Resource **
- 理解JSR系列注解
JSR(Java Specification Requests)是Java平台标准化进程中的一种技术规范,而JSR注解是其中一部分重要的内容。按照JSR的分类以及注解语义的不同,可以将JSR注解分为不同的系列,主要有以下几个系列:
1. JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解:
- `@Deprecated`: 标识一个程序元素(如类、方法或字段)已过时,并且在将来的版本中可能会被删除。
- `@Override`: 标识一个方法重写了父类中的方法。
- `@SuppressWarnings`: 抑制编译时产生的警告消息。
- `@SafeVarargs`: 标识一个有安全性警告的可变参数方法。
- `@FunctionalInterface`: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。
1. JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。该JSR主要定义了一些用于进行对象管理的注解,包括:
- `@Resource`: 标识一个需要注入的资源,是实现Java EE组件之间依赖关系的一种方式。
- `@PostConstruct`: 标识一个方法作为初始化方法。
- `@PreDestroy`: 标识一个方法作为销毁方法。
- `@Resource.AuthenticationType`: 标识注入的资源的身份验证类型。
- `@Resource.AuthenticationType`: 标识注入的资源的默认名称。
1. JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:
- `@SupportedAnnotationTypes`: 标识注解处理器所处理的注解类型。
- `@SupportedSourceVersion`: 标识注解处理器支持的Java源码版本。
1. JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:
- `@Named`: 标识一个被依赖注入的组件的名称。
- `@Inject`: 标识一个需要被注入的依赖组件。
- `@Singleton`: 标识一个组件的生命周期只有一个唯一的实例。
1. JSR-250: 这个JSR主要是Java EE 5中定义一些支持注解。该JSR包含了一些支持注解,可以用于对Java EE组件进行管理,包括:
- `@RolesAllowed`: 标识授权角色
- `@PermitAll`: 标识一个活动无需进行身份验证。
- `@DenyAll`: 标识不提供针对该方法的访问控制。
@DeclareRoles
: 声明安全角色。
但是你要理解JSR是Java提供的技术规范,也就是说,他只是规定了注解和注解的含义,JSR并不是直接提供特定的实现,而是提供标准和指导方针,由第三方框架(Spring)和库来实现和提供对应的功能。
- JSR-250 @Resource注解
@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
- @Autowired注解是Spring框架自己的。
- **@Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。**
- **@Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。**
- @Resource注解用在属性上、setter方法上。
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【高于JDK11或低于JDK8需要引入以下依赖】
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
- @Resource使用
@Controller
public class XxxController {
/**
* 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
* 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
* 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')
*/
@Resource
private XxxService xxxService;
//@Resource(name = "指定beanName")
//private XxxService xxxService;
public void show(){
System.out.println("XxxController.show");
xxxService.show();
}
}
4.3.3 基本属性赋值
@Value
通常用于注入外部化属性,正常情况下就直接==赋值
声明外部配置
application.properties
catalog.name=MovieCatalog
xml引入外部配置
<!-- 引入外部配置文件-->
<context:property-placeholder location="application.properties" />
@Value注解读取配置
package com.atguigu.components;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* projectName: com.atguigu.components
*
* description: 普通的组件
*/
@Component
public class CommonComponent {
/**
* 情况1: ${key} 取外部配置key对应的值!
* 情况2: ${key:defaultValue} 没有key,可以给与默认值
*/
@Value("${catalog:hahaha}")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.4总结
使用注解开发使用ioc,并不能完全摒弃xml配置,还需要使用xml配置
- 扫包,注解使用的范围
- 引入外部文件
- 第三方提供的类,还需要在xml配置,如druid,jdbctemplate
五 使用配置类以及注解方式使用ioc
5.1 什么是配置类,以及使用
什么是配置类,就是将配置文件,改成配置类。
在上述使用注解的过程中,发现仍需要使用xml配置文件,xml配置文件的需要做的事情是:
- 扫包 component-scan base-package=“xxx”
- 引入外部文件 property-placeholder location=“xxxx”
- 注册第三方提供的类
由于xml解析的效率较低,使用一个类实现以上功能,我们在此称这个类为配置类。
/**
* 配置类代替xml文件
* 1. 引入外部文件
* 2. 扫包
* 3. 注册第三方的类
*/
@Configuration
@ComponentScan({"com.ls.dao","com.ls.service"})
@PropertySource("classpath:jdbc.properties")
public class MainConfiguration {
//注册数据源---->使用druid第三方提供的类
@Bean
public DataSource dataSource(@Value("${jdbc.url}") String url,
@Value("${jdbc.driver}") String driverClassname,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassname);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
获得用配置类创建的ioc容器的时候是使用另一实现类AnnotationConfigApplicationcontext.
5.2@Bean的详细使用
- bean的详细使用 name,initMethod,destroyMethod,以及作用域Scope(“singleton"or"prototype”)
- bean之间的依赖
@Configuration
@ComponentScan({"com.ls.dao","com.ls.service"})
@PropertySource("classpath:jdbc.properties")
public class MainConfiguration {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driverClassname;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//注册数据源---->使用druid第三方提供的类
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassname);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//bean的详细使用 name,initMethod,destroyMethod,以及作用域Scope("singleton"or"prototype")
/**bean之间的依赖 ------->jdbcTemplate--->datasource
* 方式一: 如果需要注入的依赖,也是@Bean标签下的,可以直接使用方法引用
* 方式二: 使用参数注入,就是将需要注入的依赖,以参数的形式传入被注入的bean。
* 确保参数一定存在参数类型的bean
* 如果有多个类型,可以直接使用需要注入的名
*/
@Bean(name = "jdbcTemplate", initMethod = "",destroyMethod = "")
@Scope("singleton")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//如果需要注入的依赖,也是@Bean标签下的,可以直接使用方法引用
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
//使用参数传值,就是将需要注入的依赖,以参数的形式传入被注入的bean。
@Bean
public JdbcTemplate jdbcTemplate1(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
}
5.3 @Import使用
@Import 注释允许从另一个配置类加载 @Bean 定义,如以下示例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@Import的使用是作用在注解类上的,目的就是整合注解类,减少创建ioc时的参数