作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Spring5应用专栏_Aomsir的博客-CSDN博客
- 孙哥suns说Spring5~学不会Spring? 因为你没找对人~孙帅
- Spring官方文档
在之前的内容中,我们已经详细探讨了Spring的一些核心概念。从本篇开始,我们将转向更为现代和简洁的开发方式——注解开发 。通过注解,我们能更加直观和简洁地定义和配置Spring组件,极大地提高开发效率。本文将详细介绍Spring框架中的基础注解以及与注解相关的核心概念
注解编程是使用注解为代码添加元数据信息的一种编程方法。注解以@符号开始,如@Component
。
通过注解,我们可以为类、方法或变量提供额外的信息。这些信息可以被框架或库用来执行特定的操作或改变代码的行为。
在下面例子中,@Component是一个注解,它告诉Spring框架,这个类User应被视为一个Bean,交由Spring管理。
注解编程的优势在于它可以简化配置、提高代码清晰度,并帮助实现与Spring框架的紧密集成。但适量使用是关键,以保持代码的可读性。
@Component
public class User {
}
Spring 2.x
:注解被引入到Spring中,这标志着Spring开始从传统的XML配置转向基于注解的配置。Spring 3.x
:Spring进一步完善了基于注解的开发,为开发者提供了更多的注解选项,并优化了其与框架的集成。Spring Boot
:Spring Boot进一步推动了基于注解的开发。借助Spring Boot, 开发者可以通过注解轻松地配置和启动应用程序,实现“约定优于配置”的理念。综上所述,学习注解编程不仅可以使开发过程变得更加便捷和高效,而且对于现代的Spring开发来说,掌握注解已经变得不可或缺
Spring框架在其各个版本中逐步强化了对注解编程的支持,下面是Spring注解发展的历程:
@Component
、@Service
和@Scope
等。@Bean
、@Configuration
等,增强了框架对注解编程的支持。这一发展历程展示了Spring如何从初步地支持注解编程,到逐渐推崇其作为首选的开发方式。随着版本的迭代,Spring通过引入新的注解和工具,使注解编程变得更加强大和灵活
问题:使用注解开发后,由原先的XML配置转移到了注解中,那是否意味着代码的耦合度会增加?
答案:的确,将配置直接嵌入到代码中可能会增加某种程度的耦合,因为配置与业务逻辑现在都在同一处。但是,这种耦合大多数时候是可以接受的,因为它带来的便利性和直观性往往超过了这个小小的劣势。
更重要的是,Spring框架提供了足够的灵活性来应对这种情况。如果开发者对某个注解的配置不满意或希望在不同的环境中使用不同的配置,仍然可以使用Spring的XML配置文件来覆盖或调整这些注解设置。这种方式结合了注解的简洁性和XML配置的灵活性,确保了框架的强大和应用的可维护性
本篇文章主要讲的是Spring2.x提供的基础注解
⚠️注意:这个阶段的注解,仅仅是简化XML的配置,并不能够完全替代XML配置文件
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.aomsir.basic.annotation" />
beans>
当我们在Spring中使用@Component注解,相当于告诉Spring框架:请为这个类创建一个实例并将其放入IoC容器中。但是,与传统的XML配置方式相比,这种注解方式隐藏了很多细节。让我们来解开这其中的奥秘。
下面我创建一个User类,并写了一个测试,测试结果也如下。
细节分析:
@Data
@Component
public class User implements Serializable {
private Integer id;
private String name;
private String password;
}
public class TestAnnotation {
private static final Logger log = LoggerFactory.getLogger(TestAnnotation.class);
/**
* 用于测试 @Component注解
*/
@Test
public void test1() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
User user = ctx.getBean("user", User.class);
log.info("{}", user);
}
}
从@Component注解衍生出了多个特殊化的注解,包括@Repository
、@Service
和@Controller
等。
这些注解在本质上都是@Component,也可以称之为复合注解。它们在使用和功能上与@Component完全一致。推出这些派生注解的主要目的是为了在项目中区分不同的组件层级,从而使Bean的功能和责任更加明确。例如,我们通常在处理用户请求的控制器上使用@Controller注解。
另外,一个值得注意的细节是,在整合MyBatis时,我们通常不使用@Component或@Repository来标注Dao接口。这是因为在这种特定的整合场景下,有更专用的方法来标识和管理DAO接口。
注解@Scope
主要用于控制Bean对象的创建次数。具体来说,它决定了每次请求Bean时是否创建一个新的实例,还是每次都使用已经创建的同一个实例。在传统的XML配置中,我们可以通过scope属性来设置这一行为;而在注解开发中,我们可以直接在类上使用@Scope注解。该注解接受的值可以是“singleton”(表示每次都返回同一个实例)或“prototype”(表示每次返回一个新实例)。需要注意的是,如果不指定值,那么默认行为是“singleton”。
@Component
@Scope("prototype")
public class Customer {
}
public class TestAnnotation {
private static final Logger log = LoggerFactory.getLogger(TestAnnotation.class);
/**
* 用于测试 @Scope注解
*/
@Test
public void test2() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
for (int i = 0; i < 3; i++) {
Customer customer = ctx.getBean("customer", Customer.class);
log.info("{}", customer);
}
}
}
@Lazy注解
是用于延迟创建单实例Bean的。当我们使用@Scope注解并将其值设置为“singleton”时,这些Bean在Spring容器启动时会立即被实例化。然而,有些情况下,我们可能希望Bean只有在首次被真正请求时才被创建,以节省资源和初始化时间。为了实现这一目标,我们可以在类上直接添加@Lazy注解。这样,即使Bean是单实例的,也只会在首次请求时进行实例化,而不是在容器启动时。
@Component
@Lazy
public class Account {
private static final Logger log = LoggerFactory.getLogger(Account.class);
public Account() {
log.error("Account被创建了");
}
}
public class TestAnnotation {
private static final Logger log = LoggerFactory.getLogger(TestAnnotation.class);
/**
* 用于测试 @Lazy注解
*/
@Test
public void test3() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
// 下面三行第一次测试的时候先注释,第二次测试的时候再取消注释
for (int i = 0; i < 3; i++) {
Account account = ctx.getBean("account", Account.class);
log.info("{}", account);
}
}
}
该注解用于标识某个方法作为Bean的初始化方法。当Bean被Spring容器实例化后,该方法会立即执行。这样,我们可以在该方法中执行一些初始化操作。通过使用@PostConstruct
,我们可以避免实现InitializingBean接口
或在XML配置文件中配置初始化方法,从而大大简化了代码的结构
该注解用于标记类的销毁方法。当Bean的生命周期结束,即将被Spring容器销毁时,该方法会被调用。这为我们提供了在Bean销毁之前执行清理操作的机会。利用@PreDestroy
,我们可以避免实现特定的DisposableBean接口
或在XML配置文件中指定销毁方法,使代码更为简洁
@Component
public class Product {
private static final Logger log = LoggerFactory.getLogger(Product.class);
@PostConstruct
public void myInit() {
log.info("Product.myInit()");
}
@PreDestroy
public void myDestroy() {
log.info("Product.myDestroy()");
}
}
public class TestAnnotation {
private static final Logger log = LoggerFactory.getLogger(TestAnnotation.class);
/**
* 用于测试 @PostConstruct注解与@PreDestroy注解
*/
@Test
public void test4() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
Product product = ctx.getBean("product", Product.class);
((ClassPathXmlApplicationContext) ctx).close();
}
}
@PostConstruct 和 @PreDestroy 注解是JavaEE规范中的一部分,具体来说,它们由JSR-250标准定义。这两个注解不是Spring原生的,但Spring选择支持并整合它们,以增强其与JavaEE的兼容性。这样的选择进一步强调了通过注解来实现接口契约性的重要性。它证明了不同的技术和框架可以依赖统一的标准,从而为开发者提供一致的开发体验
在传统的基于XML的配置方式中,我们通常使用标签下的标签来实现依赖注入。然而,随着注解驱动开发的流行,这种繁琐的配置方式已经不再是唯一的选择。使用注解进行依赖注入,我们可以更加直观和简洁地在代码中标识组件之间的依赖关系
通过这种方式,开发者可以直接在Java类中明确地指定依赖关系,无需跳转到XML文件中进行配置。这不仅提高了开发的效率,还使代码的结构更加清晰
@Autowired
是Spring提供的一个核心注解,用于实现自动的类型依赖注入。该注解基于类型进行匹配,这意味着要注入的组件必须是目标成员变量的类型、子类或其实现类
细节分析:
@Qualifier注解
来指定需要注入的具体Bean名称,从而结合名称和类型进行注入。通过引入注解方式的依赖注入,Spring为开发者提供了一种更加直观和简洁的方式来描述Bean之间的依赖关系。
public interface UserDAO {
public void save();
}
@Repository
public class UserDAOImpl implements UserDAO {
private static final Logger log = LoggerFactory.getLogger(UserDAOImpl.class);
@Override
public void save() {
log.info("UserDAOImpl.save()");
}
}
public interface UserService {
public void register();
}
@Service
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
@Autowired
@Qualifier("userDAOImpl")
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register() {
this.userDAO.save();
}
}
public class TestAnnotation {
/**
* 用于测试 @Autowired注解
*/
@Test
public void test5() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
UserService userService = ctx.getBean("userServiceImpl", UserService.class);
userService.register();
}
}
@Resource
是一个用于依赖注入的注解,但它并非Spring原生提供,而是由JSR-250标准定义的。可以看作是@Autowired和@Qualifier两个注解的结合体
细节分析
名称注入
:通过@Resource注解的name属性,我们可以直接指定Bean的名称进行注入,这提供了一种基于名称的注入方式。类型注入
:若通过名称没有找到合适的Bean进行注入,@Resource注解会回退到基于类型的注入。它会尝试匹配容器中的相同类型、子类或实现类的Bean。这种注解提供了一种灵活的方式,允许开发者根据具体的需求选择基于名称或类型的注入方式,使得依赖注入更加灵活且简洁。
上述讨论的都是关于自定义类型的依赖注入。但如果我们尝试使用@Autowired或@Resource注解在如Integer这样的JDK基础类型
上,我们会发现这是行不通的。那么,如何为这些基本类型或者说原始数据类型注入值呢?
不用担心,Spring已经为我们考虑到了这一点。我们可以将这些基础类型的值存放在一个.properties
文件中,然后通过Spring提供的特定注解来读取并注入这些值。
使用步骤
使用细节
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.aomsir.basic.annotation" />
<context:property-placeholder location="classpath:init.properties" />
beans>
@Component
@Data
public class Category {
@Value("${id}")
private Integer id;
@Value("${name}")
private String name;
}
id = 1
name = Aomsir
public class TestAnnotation {
/**
* 用于测试:@Value注解
*/
@Test
public void test6() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
Category category = ctx.getBean("category", Category.class);
log.info("{}", category);
}
}
尽管我们在前面的步骤中已经通过配置文件加载了.properties文件,但这种方法相对繁琐。幸运的是,Spring为我们提供了一个更简洁的方式来加载属性文件,那就是使用@PropertySource
注解。
@PropertySource注解可以直接应用于配置类上,用于指定需要加载的属性文件的路径。这样,我们可以省去在XML配置中手动加载属性文件的步骤。此外,当我们迁移到SpringBoot进行开发时,这个注解还可以直接应用于主启动类上
@Data
@Component
@PropertySource("classpath:init.properties")
public class Category {
@Value("${id}")
private Integer id;
@Value("${name}")
private String name;
}