Spring框架学习day_02:组件扫描 / 注解内部读解 / 组件扫描中配置作用域和生命周期 / 解耦 / 自动装配(两种方式) / 读取文件

2023-10-27

1. 组件扫描

首先,必须让Spring扫描组件所在的包,并且,组件类的声明之前必须添加@Component注解!

其实,除了@Component注解以外,还可以使用以下注解实现同样的效果:

  • @Controller:推荐添加在控制器类之前;
  • @Service:推荐添加在业务类之前;
  • @Repository:推荐添加在处理持久层的类之前.

以上4个注解在Spring框架的作用领域中,效果是完全相同的,用法也完全相同,只是语义不同。

在使用组件扫描时,还可以自定义某个类,作为配置类,在这个类的声明之前使用@ComponentScan注解来配置组件扫描的包:

package cn.tedu.spring;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("cn.tedu.spring")
public class SpringConfig {

}

后续,程序运行时,就需要加载这个配置类:

AnnotationConfigApplicationContext ac 
    = new AnnotationConfigApplicationContext(SpringConfig.class);

关于组件扫描的包,严格来说,是配置需要被扫描的“根包(base package)”,也就是说,在执行扫描时,会扫描所设置的包及其所有子孙包中的所有组件类!当设置为扫描cn.tedu包时,会把cn.tedu.spring甚至cn.tedu.spring.dao这些包中的组件类都扫描到!

2. 关于注解的使用

@Bean注解为例,其声明是:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
}

可以看到,注解都是通过@interface声明的!

在注解的声明之前,还添加了一系列的注解,例如以上的@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented,则表示当前@Bean注解同时具有以上3个注解的特性。也就是说,@Bean注解相当于以上3个注解的同时,还具有自身的特性!

@Bean注解内部,还有:

/**
 * Alias for {@link #name}.
 * <p>Intended to be used when no other attributes are needed, for example:
 * {@code @Bean("customBeanName")}.
 * @since 4.3.3
 * @see #name
 */
@AliasFor("name")
String[] value() default {};

以上String[] value() default {};有点像接口中的抽象方法,但是,在注解中,这是声明的注解属性!value是属性名称,所以,在使用当前注解时,可以配置:

@Bean(value=???)

以上源代码中的String[]看似是抽象方法的返回值,实则是value属性的值的数值类型!所以,可以配置为:

@Bean(value={"a", "b", "c"})

以上源代码中的default {}表示该属性的默认值,所以,以下2段配置是完全等效的:

@Bean
@Bean(value={})

在配置注解属性时,如果属性名称是value,它是默认的属性,在配置时,可以不用显式的写出value=部分,也就是说,以下2段配置是完全等效的:

@Bean(value={"a", "b", "c"})
@Bean({"a", "b", "c"})

在配置注解属性时,如果属性的值的类型是数组类型,但是,当前只需要配置1个值时,可以不用写成数组格式,只需要写成数组元素的格式即可!也就是说,以下2段配置是完全等效的:

@Bean({"a"})
@Bean("a")

所以,总的来说,关于@Bean注解的value属性,如果需要配置的值是"user",则以下4段代码都是完全等效的:

@Bean("user")
@Bean({"user"})
@Bean(value="user")
@Bean(value={"user"})

在以上源代码中,注释中还标明了@since 4.3.3,表示该属性从Spring框架4.3.3版本开始才加入的,如果当前使用的环境改为4.3.3以下的版本,将导致该属性不可用,因为在更低的版本中,根本就没有这个属性,甚至可能连个注解本身都不存在!

在以上源代码中,在value属性的声明之前还添加了@AliasFor("name")注解,表示当前value属性另有别名name,所以,在@Bean注解的源代码中,还有:

/**
 * The name of this bean, or if several names, a primary bean name plus aliases.
 * <p>If left unspecified, the name of the bean is the name of the annotated method.
 * If specified, the method name is ignored.
 * <p>The bean name and aliases may also be configured via the {@link #value}
 * attribute if no other attributes are declared.
 * @see #value
 */
@AliasFor("value")
String[] name() default {};

则在@Bean注解中,namevalue这2个注解是完全等效的!

之所以存在2个完全等效的属性,是因为:

  • value属性是默认的,在配置时可以不必显式的写出value=部分,配置时更加简单;
  • name属性表现的语义更好,更易于根据源代码读懂程序的意思,在其它注解中,也可能存在与value 等效的属性。

需要注意的是:在配置注解中的属性时,如果需要配置的是value属性的值,可以不用显式的写出value=部分,前提是当前注解只配置value这1个属性!如果需要配置多个属性,则必须写出每一个属性名,例如:

@Bean(value="user", initMethod="init")

而不能写成:

@Bean("user", initMethod="init")	// 错误

3. 使用组件扫描后配置作用域与生命周期

在类的声明之前,添加@Scope("prototype")即可将当前类配置为“非单例”的对象!例如:

package cn.tedu.spring;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

@Repository
@Scope("prototype")
public class User {

}

在单例的情况下,在类的声明之前添加@Lazy注解,就可以将对象配置为“懒加载”的模式:

package cn.tedu.spring;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;

@Repository
@Lazy
public class User {

}

如果需要配置当前类中的生命周期的处理,首先,还是需要在类中自定义2个方法,分别表示“初始化方法”和“销毁方法”,然后,在初始化方法之前添加@PostConstruct注解,在销毁方法之前添加@PreDestroy注解,例如:

package cn.tedu.spring;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;

@Repository
@Lazy
public class User {
	
	public User() {
		System.out.println("User.User()");
	}
	
	@PostConstruct
	public void init() {
		System.out.println("User.init()");
	}
	
	@PreDestroy
	public void destroy() {
		System.out.println("User.destroy()");
	}

}

注意:以上2个注解并不是Spring的注解,如果JRE环境版本太低,将无法识别以上2个注解,需要调整当前项目的JRE环境!

4. 关于Spring管理对象的小结

如果需要Spring管理某个类的对象,可以:

  • 自定义方法,将方法的返回值类型设置为期望管理的类型,并在方法中返回匹配类型的对象,最后,在方法的声明之前添加@Bean注解;
  • 设置组件扫描的包,并在类的声明之前添加@Component / @Controller / @Service / @Repository这4个注解中的某1个。

在实际使用时,大多采取第2种做法,但是,如果需要Spring管理的类并不是自定义的类,就只能采取第1种做法!

5. 关于Spring的解耦

在没有使用Spring框架的情况下,在项目中,各组件之间是存在依赖关系的,例如:

// 处理用户登录请求的Servlet组件类
public class UserLoginServlet {
    private UserJdbcDao userDao = new UserJdbcDao();
    public void doPost() {
        userDao.login();
    }
}
// 处理用户数据增删改查的组件
public class UserJdbcDao {
    public void login() {
        // 通过JDBC技术实现数据查询,判断用户名与密码是否正确
    }
}

以上代码就体现了类与类之前的依赖关系,具体表现就是UserLoginServlet是依赖于UserJdbcDao的!

如果直接依赖于某个类,将会导致耦合度过高的问题!

假设在UserJdbcDao中,是通过原生的JDBC技术实现数据访问的,后续,需要改为使用MyBatis框架技术来实现,则可能创建UserMybatisDao类,用于取代UserJdbcDao类!

如果需要替换,则项目中原有的以下代码:

private UserJdbcDao userDao = new UserJdbcDao();

全部需要替换为:

private UserMybatisDao userDao = new UserMybatisDao();

这种替换时需要调整大量原有代码的问题,就是高耦合的问题,我们希望的目标是低耦合,将原有高耦合的项目调整为低耦合的状态,就是解耦的做法!

可以将处理用户数据增删改查的相关操作声明在接口中,例如:

public interface UserDao {
    void login();
}

然后,各个处理用户数据增删改查的类都去实现这个接口:

public class UserJdbcDao implements UserDao {
    public void login() {
        // 通过JDBC实现处理用户登录
    }
}
public class UserMybatisDao implements UserDao {
    public void login() {
        // 通过MyBatis框架技术实现处理用户登录
    }
}

后续,在各个Servlet组件中,就可以声明为接口类型:

private UserDao userDao = new UserMybatisDao();

通过以上代码调整,就可以使得Servlet组件依赖于接口,而不再是依赖于类,从而实现了解耦!

另外,还可以通过设计模式中的工厂模式来生产对象,例如:

public class UserDaoFactory {
    public static UserDao newInstance() {
        return new UserMybatisDao();
    }
}

当有了以上工厂后,原本在Servlet组件中声明持久层对象的代码就可以再调整为:

private UserDao userDao = UserDaoFactory.newInstance();

至此,在项目中到底是使用UserJdbcDao还是使用UserMybatisDao,在以上代码都不会体现出来了,也就意味着当需要切换/替换时,以上代码是不需要修改的,而是修改UserDaoFactory工厂类的方法的返回值这1处即可!

所以,通过定义接口和创建工厂类就可以实现解耦,但是,在实际项目开发时,不可能为每一个组件都创建专门的工厂类,而Spring框架就可以当作是一个庞大的工厂,开发人员可以通过Spring框架的使用约定,将某些类的对象交给Spring框架进行管理,后续,在具体使用过程中,就不必自行创建对象,而是获取对象即可!

6. 自动装配

在Spring框架的应用中,可以为需要被Spring自动赋值的属性添加@Autowired,则Spring框架会从Spring容器中找出匹配的值,并自动完成赋值!这就是Spring框架的自动装配机制!

当Spring尝试为某个属性实现自动装配时,采取的模式主要有:

  • byName:根据名称实现自动装配,在这种模式下,要求被装配的属性名称,与被Spring管理的对象的名称(调用getBean()方法给出的参数名)必须相同;
  • byType:根据类型实现自动装配,在这种模式,要求被装配的属性的类型,在Spring容器中存在匹配类型的对象,当应用这种机制时,必须在Spring容器中保证匹配类型的对象只有1个,否则,将会出现NoUniqueBeanDefinitionException异常;

当使用@Autowired尝试自动装配时,Spring框架会先根据byType模式找出所有匹配类型的对象,如果匹配类型的对象的数量为0,也就是没有匹配类型的对象,默认情况下会直接报错,提示信息例如:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.tedu.spring.UserDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

如果使用@Autowired时明确的配置为@Autowired(required=false),当没有匹配类型的对象时,也不会因为装配失败而报错!

如果匹配类型的对象的数量为1,则直接装配;

如果匹配类型的对象的数量超过1个(有2个甚至更多个),会尝试byName来装配,如果存在名称匹配的对象,则成功装配,如果名称均不匹配,则装配失败,会提示如下错误:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.tedu.spring.UserDao' available: expected single matching bean but found 2: userJdbcDao,userMybatisDao

当需要自动装配时,除了使用@Autowired注解以外,还可以使用@Resource注解!

当使用@Resource注解尝试自动装配时,其工作原理是先尝试byName装配,如果存在名称匹配的对象,则直接装配,如果没有名称匹配的对象,则尝试byType装配。

另外,如果某个方法是被Spring调用的,还可以将需要装配的对象设置为方法的参数(不需要添加注解即可正常使用),Spring也可以实现方法参数的自动装配!例如:

public void test(UserDao userDao) {}

7. 通过Spring框架读取.properties文件

首先,在案例的src/main/resources下创建jdbc.properties文件,并且,在文件中,添加一些自定义的配置信息:

url=jdbc:mysql://localhost:3306/db_name
driver=com.mysql.jdbc.Driver
username=root
password=1234

本次案例的目标是读取以上文件的信息,并不用于真实的连接某个数据库,所以,各属性的值可以不是真正使用的值!

如果要读取以上信息,可以将这些信息都读取到某个类的各个属性中去,则先创建一个类,并在类中声明4个属性(与以上jdbc.properties文件中的配置信息的数量保持一致):

package cn.tedu.spring;

public class JdbcConfig {
	
	private String url;
	private String driver;
	private String username;
	private String password;

}

然后,在类的声明之前,通过@PropertySource配置需要读取的配置文件:

@PropertySource("classpath:jdbc.properties")

然后,在各个属性的声明之前,通过@Value注解读取配置信息中的值,并注入到属性中,其基本格式是@Value("${配置文件中的属性名称}"),例如:

package cn.tedu.spring;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
	
	@Value("${url}")
	private String url;
	@Value("${driver}")
	private String driver;
	@Value("${username}")
	private String username;
	@Value("${password}")
	private String password;

    @Override
	public String toString() {
		return "JdbcConfig [url=" + url + ", driver=" + driver + ", username=" + username + ", password=" + password
				+ "]";
	}
    
}

由于期望的是由Spring读取配置文件,并为以上类的各个属性赋值,所以,以上JdbcConfig应该是被Spring管理的!所以,先使用一个类来配置组件扫描:

package cn.tedu.spring;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("cn.tedu.spring")
public class SpringConfig {

}

然后,在JdbcConfig类的声明之前添加@Component注解即可!

注意:在Windows操作系统中,如果配置文件中的属性名是username,则最终注入属性的值将不是配置文件中的值,而是当前登录Windows操作系统的用户名,为了避免出现此类问题,建议在配置文件中,每个属性的名称之前都添加一些自定义的前缀。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring框架学习day_02:组件扫描 / 注解内部读解 / 组件扫描中配置作用域和生命周期 / 解耦 / 自动装配(两种方式) / 读取文件 的相关文章

随机推荐

  • QT 新手小白USBCAN 学习

    一 介绍CAN总线 CAN总线介绍 二 USBCAN总线 2 1 产品介绍 USBCAN 转换器模块实现了将 CAN 总线接口与 USB 接口进行相互转换 可以 简单方便的通过电脑监视 CAN 总线网络 同时可以实现工业现场数据稳定的双 向
  • php xml数据类型,PHP实现XML与数据格式进行转换类实例

    xml2array will convert the given XML text to an array in the XML structure Link http www bin co com php scripts xml2arra
  • Java架构师成长升级历程

    目录 前言 一 学习途径整理 目录 前言 一 学习途径整理 二 如何高效的学习 三 书籍推荐 MQ相关 云原生相关 Redis相关 架构相关 Mybaits 四 优秀博文整理 五 极客时间 六 硬核公众号推荐 七 宝藏学习网站 八 架构设计
  • just4fun:神奇的代码生成好看的图片

    原文链接 用三段 140 字符以内的代码生成一张 1024 1024 的图片 在VS2015下 试了一下生成下图的代码 生成ppm格式图片的代码如下 include
  • 论文笔记:TIMESNET: TEMPORAL 2D-VARIATION MODELINGFOR GENERAL TIME SERIES ANALYSIS

    ICLR 2023 1 intro 时间序列一般是连续记录的 每个时刻只会记录一些标量 之前的很多工作着眼于时间维度的变化 以捕捉时间依赖关系 gt 可以反映出 提取出时间序列的很多内在特征 比如连续性 趋势 周期性等 但是现实时间序列数据
  • 服务器运行tomcat报错误java.security.UnrecoverableKeyException: Cannot recover key

    问题 项目部署在阿里云服务器上 一直都是正常运行 因业务需要重启服务器 之后就启动tomcat 然后就发现启动不了了 报错 java security UnrecoverableKeyException Cannot recover key
  • MVS、SFM的区别和联系

    文章目录 一 双目相机重建步骤 二 SFM重建步骤 三 MVS重建步骤 四 SFM 和MVS 各自的目的 五 传统MVS和深度学习MVS对比 一 双目三维重建 binocular stereo 输入左右两张图片 二 SFM Structur
  • 法律法规

    法律体系 我国法律体系基本框架 由宪法和宪法相关法 民法商法 行政法 经济法 社会法 刑法 诉讼及非诉讼程序法构成 法的形式 法的形式的4个含义 1 法律规范的创制机关的性质及级别 2 法律规范的外部表现形式 3 法律规范的效力等级 4 法
  • 平时都用Python绘制二维图,如果用来绘制三维图会有什么效果?

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 版权归原作者所有 如有问题请及时联系我们以作处理 通常我们用 Python 绘制的都是二维平面图 但有时也需要绘制三维场景图 比如像下面这样的 这些图怎么做出来呢 今
  • 备赛电赛学习STM32篇(九):ADC

    目录 一 ADC的简介 二 逐次逼近型ADC 2 1 逐次逼近型ADC框图 2 2 STM32 ADC内部介绍 2 2 1 STM32ADC的通道以及存储数据的寄存器 2 2 2 触发方式 2 2 3 STM32ADC时钟部分 2 2 4
  • 机器学习——聚类——距离聚类法——K-means

    目录 理论部分 1 1 聚类概念 1 1 1 定义 1 1 2 与分类的区别 1 2 相似度测量 1 2 1 欧式距离 1 2 2 马氏距离 1 3 聚类准则 1 3 1 试探方法 1 3 2 聚类准则法 1 4 常见聚类方法 1 5 K均
  • 判断(if)语句

    先说一下if语句的应用场景 生活中我们会有这样那样的如果 发工资为例 我们将它转化成代码 if 今天发工资 先还信用卡的钱 四个空格或tap键 不能混用 if 有剩余 就要买买买 else 难受ing else 期待下个月 正是有了判断 我
  • C#使用protobuf简述

    编译依赖项 首先 创建一个C 4 5以上project 因为最新的protobuf依赖于C 5 0的语言特性 然后 我们通过nuget 为项目添加对protobuf的引用 搜索protobuf就可以开始安装了 安装完成后 可以编译一次工程
  • JVM运行原理及优化之 jstat -gc

    我们写好的代码 是要通过JVM才能运行的 JVM 想要执行一个类 首先要加载类 在加载类之前 需要先编译成字节码class 文件 然后就执行类的加载过程 JVM 加载类的话 需要类加载器 类加载器是分层级的 遵循双亲委派机制 最上层是Boo
  • 服务端与移动端交互信任的锚点---维护授信证书与私钥

    HTTPS 分为 HTTP SSL 安全套接字层 后面SSL3 0之后被重命名为TLS1 0 其实就是SSL3 0的进化版本 TLS1 0 Transport Layer Security 安全传输层协议 可以说TLS就是SSL的新版本3
  • css中只使用vue的变量

    参考 https blog csdn net FellAsleep article details 130617163 1 必须作用在用一个div上 2 变量必须有双横杠 span class bb 11 span data return
  • 低代码开发平台能开发什么类型的系统和软件?

    低代码开发平台能开发什么类型的系统和软件 1 数据分析和报告系统 使用低代码平台 企业可以创建数据看板 集成不同数据源 自动提取 分析和可视化数据 这种系统适用于监控业务指标 分析趋势 并为决策提供数据支持 2 信息管理系统 低代码平台能够
  • Mock代理对象失效问题分析

    Mockito 简介 Mockito是一种常用的java单测框架 主要功能就是用来模拟接口的实现 对于测试环境无法执行的方法可以通过mock来执行我们定义好的逻辑 通常代码写法如下 public class AimServiceTest 将
  • http-2.4版本编译安装

    httpd 2 4版本新特性 1 mpm支持运行dos机制 2 支持event mpm 3 支持异步读写 4 支持每模块及每个目录分别使用各自的日志级别 5 每请求配置 6 增强版的表达式分析 7 支持毫秒级别的keeplivetimeou
  • Spring框架学习day_02:组件扫描 / 注解内部读解 / 组件扫描中配置作用域和生命周期 / 解耦 / 自动装配(两种方式) / 读取文件

    1 组件扫描 首先 必须让Spring扫描组件所在的包 并且 组件类的声明之前必须添加 Component注解 其实 除了 Component注解以外 还可以使用以下注解实现同样的效果 Controller 推荐添加在控制器类之前 Serv