Java注解的前世今生
在Java开发中,你应该会经常看到一些怪怪的符号和字串,比如在代码上面有 @Override
或者 @Deprecated
。这些就是我们谓之为“注解”的东西。今天我会带你了解一下Java注解的知识,以及为什么它们这么有用!
什么是Java注解?
注解实际上是一种特殊的标记,它可以被用来提供关于代码的信息、功能或者行为的描述。Java注解就是这种会让源码比较清晰的标记,它远远不只是对人有用,还对机器有用。
注解通常有一个 @
符号开头,并应用在类、方法、变量上。常用的注解有多种,这里例如说明一些最基本的。
Spring注解的使用
如何使用注解呢?
- 第一步:加入aop的依赖(加入spring-context依赖之后,会关联加入aop的依赖)
- 第二步:在配置文件中添加context命名空间
- 第三步:在配置文件中指定扫描的包
- 第四步:在Bean类上使用注解
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.5-SNAPSHOT</version>
</dependency>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 在配置文件中指定扫描的包 -->
<context:component-scan base-package="bean"/>
</beans>
几种最常用的注解
-
@Override
这是一个超级常用的注解,用来指出你正在重写一个方法。它不会改变代码的行为,但在你不小心写错时,它会告诉你,你可能还没有正确地实现要重写的那个方法。
-
@Deprecated
如果你看到一个方法还被标注了
@Deprecated
,这个方法就是废弃了的意思。通常来说,你还是能用它,但是建议你找更新的方法来代替。这应该是置中时的“更新提示”吧。 -
@SuppressWarnings
此注解用来应对一些输出的警告信息。比如,在一个类或方法中有些输出的信息你并不想在之后看到,就可以使用这个注解来强制帮助隐藏一些不重要的警告。
自定义注解
我们还可以自定义注解,实现自己特定的功能。比如你要创建一个用来记录异常处理方法的注解,可以这样写:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}
解释自定义注解的各个部分
-
@Retention
@Retention
用来定义注解的生命周期,也就是注解会保留到哪个阶段。它有三个可选值:RetentionPolicy.SOURCE
:注解只在源代码中保留,编译后就没了。RetentionPolicy.CLASS
:注解会保留到编译后的字节码中,但在运行时不可见。RetentionPolicy.RUNTIME
:注解不仅会保留到字节码中,而且在运行时也可以通过反射访问。这是自定义注解通常使用的值,因为它允许我们在运行时获取注解的信息。
-
@Target
@Target
用来定义注解可以应用于哪些程序元素。例如:ElementType.TYPE
:可以应用于类、接口、枚举。ElementType.METHOD
:可以应用于方法。ElementType.FIELD
:可以应用于字段(成员变量)。
在上面的例子中,我们使用了
ElementType.METHOD
,意味着这个注解只能应用于方法。 -
注解的定义和属性
自定义注解通过
@interface
关键字定义。注解可以包含属性,这些属性类似于接口中的方法,可以用来传递一些元数据。属性的定义格式如下:public @interface MyAnnotation { String value(); int number() default 0; }
在这个例子中,
MyAnnotation
有两个属性:value
和number
。String value()
:定义了一个名为value
的属性,类型是String
。在使用注解时,必须为这个属性赋值。int number() default 0
:定义了一个名为number
的属性,类型是int
,并且有一个默认值0
。这意味着在使用注解时,如果不为number
赋值,它会自动使用默认值0
。
在使用自定义注解时,可以这样写:
@MyAnnotation(value = "This is a test annotation", number = 5) public void myMethod() { // 方法体 }
如果注解只有一个属性,并且这个属性名是
value
,那么在使用时可以省略属性名,直接赋值:@MyAnnotation("This is a test annotation") public void myMethod() { // 方法体 }
注解的属性可以是基本数据类型、
String
、枚举、其他注解,或者这些类型的数组。但注解的属性不能是复杂对象,因为注解的目标是提供一些简单的、可序列化的元数据。
如何通过反射机制读取注解
通过反射机制,我们可以在运行时获取注解的信息。这通常用于框架开发或运行时动态处理某些逻辑。下面是一个通过反射读取注解的例子:
public class Test {
public static void main(String[] args) throws Exception {
// 存放Bean的Map集合。key存储beanId。value存储Bean。
Map<String,Object> beanMap = new HashMap<>();
String packageName = "com.powernode.bean";
//将包名中的.转换为路径中的/
String path = packageName.replaceAll("\\.", "/");//com/powernode/bean
// 正则表达式.代表所有,因此要加上转义
/* 获取系统类加载器中的包路径
此时,url的值为包在文件系统中的位置,例如:
url file:/project-root/target/classes/com/powernode/bean
*/
URL url = ClassLoader.getSystemClassLoader().getResource(path);
/*将URL转换为文件并获取文件列表
*/
File file = new File(url.getPath());
//这里包含了 User.class、Product.class、Order.class。
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
String className = packageName + "." + f.getName().split("\\.")[0];
// 例如,当f是User.class时,className就是"com.powernode.bean.User"
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanId = component.value();
Object bean = clazz.newInstance();
beanMap.put(beanId, bean);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(beanMap);
}
}
在上面的代码中:
- 使用
Class<?> clazz = MyClass.class;
获取类的字节码对象。 - 使用
clazz.getDeclaredMethods()
获取类中所有的方法。 - 通过
method.isAnnotationPresent(MyAnnotation.class)
来判断方法上是否存在特定的注解。 - 如果存在,通过
method.getAnnotation(MyAnnotation.class)
获取注解的实例,并可以访问注解的属性值。
这样,你就可以在运行时通过反射机制读取自定义注解的信息,从而实现一些动态的行为。
自定义注解的应用场景
自定义注解在实际开发中有很多应用场景,比如:
-
代码生成和配置:通过注解来简化配置,减少XML或其他配置文件的使用。例如,Spring 框架中大量使用注解来进行依赖注入和配置。
-
运行时处理:使用自定义注解可以在运行时通过反射获取注解信息,从而实现一些动态功能。例如,你可以定义一个
@LogExecutionTime
注解,在方法执行时自动记录其执行时间。 -
框架开发:自定义注解广泛应用于框架开发中,用来简化使用者的操作。例如,许多ORM框架(如 Hibernate)使用注解来映射数据库表和类。
为什么要用注解?
注解的优势就是为了缩短代码和提高代码的可读性。它使用很简单,但能帮助程序员于实现设计模块化,及自动化一些功能的配置。当你写代码时,一个适当的注解能让你了解代码的目的,也让未来的你或其他工程师能更快地理解代码的意图。
最后,希望这篇文章能让你更近一步了解Java注解,并且试着在你的项目中用一用。有问题的话,欢迎留言或来讨论!注解的世界还有很多驱动等你去探索呢。
探索 Spring Bean 的注解:让你的代码更优雅
声明Bean的注解
1. @Component
@Component
可以说是所有注解的“老大哥”。它告诉 Spring:嘿,这个类我需要你管理它!
比如:
@Component("userBean")
public class User {
// User 类定义
}
这里 @Component("userBean")
就是告诉 Spring,创建一个叫 userBean
的实例,并且自动管理它的生命周期。是不是很简单?
如果没有写对应的value值,那么spring自动设置为Bean类名首字母小写。
2. @Service
@Service
是 @Component
的“亲兄弟”,专门用于服务层。它的作用和 @Component
一样,只不过它表示这个类里包含的是业务逻辑。
@Service("userService")
public class UserService {
// 业务逻辑代码
}
3. @Repository
@Repository
也是类似的,它用于数据访问层,通常是那些与数据库打交道的类。用这个注解还可以让 Spring 处理数据库异常。
@Repository("userRepository")
public class UserRepository {
// 数据库访问代码
}
4. @Controller
如果你做 Web 开发,肯定用过 @Controller
。它表示这是一个控制器类,用于处理用户的 HTTP 请求。
@Controller
public class UserController {
// 处理请求的逻辑代码
}
总结
通过源码可以看到,@Controller、@Service、@Repository
这三个注解都是@Component
注解的别名。
也就是说:这四个注解的功能都一样。用哪个都可以。
只是为了增强程序的可读性,建议:
- 控制器类上使用:Controller
- service类上使用:Service
- dao类上使用:Repository
他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。
选择性实例化Bean
假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?
@Component
public class A {
public A() {
System.out.println("A的无参数构造方法执行");
}
}
@Controller
class B {
public B() {
System.out.println("B的无参数构造方法执行");
}
}
@Service
class C {
public C() {
System.out.println("C的无参数构造方法执行");
}
}
@Repository
class D {
public D() {
System.out.println("D的无参数构造方法执行");
}
}
@Controller
class E {
public E() {
System.out.println("E的无参数构造方法执行");
}
}
@Controller
class F {
public F() {
System.out.println("F的无参数构造方法执行");
}
}
我只想实例化bean3包下的Controller。配置文件这样写:
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
use-default-filters="true"
表示:使用spring默认的规则,只要有Component、Controller、Service、Repository
中的任意一个注解标注,则进行实例化。
use-default-filters=“false” 表示:不再spring默认实例化规则,即使有Component、Controller、Service、Repository
这些注解标注,也不再实例化。
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
表示只有Controller
进行实例化。
也可以将use-default-filters设置为true(不写就是true),并且采用exclude-filter方式排出哪些注解标注的Bean不参与实例化:
<context:component-scan base-package="com.powernode.spring6.bean3">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
负责注入的注解
在现代 Java 开发中,注入(Injection)是依赖管理的重要部分,也是实现松耦合架构的核心思想之一。Spring 作为一个广泛应用的 Java 企业级开发框架,提供了多种用于实现依赖注入的注解,让开发者能够更便捷地管理对象之间的依赖关系。本文将详细介绍 Spring 中用于依赖注入的常见注解,帮助你掌握这些工具并在开发中熟练运用。
1. @Value
@Value
注解用于注入外部的值,如配置文件中的属性。它可以为 Bean 的字段注入字符串、数值等类型的数据(简单类型的注入),适合需要从配置文件加载配置的场景。
@Component
public class AppConfig {
@Value("张三")
private String appName;
}
在此例中,@Value("张三")
用于将外部配置文件中的 app.name
值注入到 appName
字段中。
注意:如果我们并没有给属性提供setter方法,但仍然可以完成属性赋值。
如果提供setter方法,并且在setter方法上添加@Value注解,同样可以完成注入。
为了简化代码,以后我们一般不提供setter方法,直接在属性上使用@Value
注解完成属性赋值。
总结:
@Value
注解可以出现在属性上、setter方法上、以及构造方法的形参上。可见Spring给我们提供了多样化的注入。
2. @Autowired
@Autowired
是 Spring 最常用的注解之一,用于自动注入 Spring 容器中的依赖。它可以标注在属性、构造方法、Setter 方法、形参上、注解等位置。
@Autowired
注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。
单独使用@Autowired
注解,默认根据类型装配。【默认是byType】
在源码中::该注解有一个 required
属性,默认值是true
,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。
如果required
属性设置为false
,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
-
属性注入:
@Component public class UserService { @Autowired private UserRepository userRepository; }
在上述代码中,Spring 自动为
userRepository
赋值,将容器中的UserRepository
Bean 注入到UserService
中。 -
构造方法注入:
@Component public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
构造方法注入在依赖于不可变字段时非常有用,有助于确保
UserService
实例一旦构造,userRepository
的值就不会改变。 -
Setter 方法注入:
@Component public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
Setter 方法注入适合在对象创建之后可能需要重新设置依赖的场景。
3. @Qualifier
当 Spring 容器中存在多个同类型的 Bean 时,@Autowired
无法决定应注入哪一个。@Qualifier
注解可以配合 @Autowired
使用,用于指定具体注入的 Bean。
@Component
public class UserService {
@Autowired
@Qualifier("customUserRepository")
private UserRepository userRepository;
}
通过 @Qualifier("customUserRepository")
,开发者可以明确指定要注入名为 customUserRepository
的 Bean,从而解决歧义问题。
3. @Resource
@Resource
是 JSR-250 规范的一部分,它既可以按名称也可以按类型进行注入,相当于 @Autowired
+ @Qualifier
的组合。与 @Autowired
不同的是,@Resource
默认按名称进行注入。
@Component
public class UserService {
@Resource(name = "userRepository")
private UserRepository userRepository;
}
在上述代码中,@Resource
将按名称查找容器中的 Bean,找到名为 userRepository
的对象并进行注入。
@Resource
注解也可以完成非简单类型注入。那它和@Autowired
注解有什么区别?
- @
Resource
注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。) - @
Autowired
注解是Spring框架自己的。 - @Resource注解默认根据名称装配
byName
,未指定name时,使用属性名作为name
。通过name找不到的话会自动启动通过类型byType
装配。 - @Autowired注解默认根据类型装配
byType
,如果想根据名称装配,需要配合@Qualifier
注解一起用。 - @Resource注解用在属性上、setter方法上。
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。】
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
全注解式开发
所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。
1. @Configuration 和 @Bean:
@Configuration
注解用于定义一个配置类,替代传统的 XML 配置文件。@Bean
注解则用于在配置类中定义需要注册到 Spring 容器中的 Bean。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
在上述代码中,AppConfig
类使用 @Configuration
注解,定义了两个 Bean:userService
和 userRepository
,它们将被 Spring 容器管理。
2.@ComponentScan:
@ComponentScan
注解用于自动扫描指定包下的组件(如 @Component、@Service、@Repository
等注解标注的类),从而自动将它们注册到 Spring
容器中。
@Configuration
// 存在两个包下面的bean需要管理
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}
@ComponentScan
可以减少手动注册 Bean
的工作量,尤其在项目中存在大量组件时非常有用。
此时的测试程序:
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
6. 使用注解进行注入的最佳实践
- 构造方法注入优先:构造方法注入有助于保证依赖的不可变性和类的线程安全,建议在可能的情况下优先使用构造方法注入。
- 避免字段注入:虽然字段注入很方便,但会使测试变得困难,因为它需要通过反射来注入依赖。优先选择构造方法注入或 Setter 方法注入。
- 使用
@Qualifier
或@Primary
:当容器中有多个同类型 Bean 时,使用@Qualifier
或@Primary
来解决依赖注入的歧义问题。
总结
Spring 提供了丰富的注解用于实现依赖注入,每个注解都有其特定的使用场景。@Autowired
是最常见的选择,结合 @Qualifier
能够解决多 Bean 注入的歧义。@Resource
和 @Inject
则用于提供与标准化注入规范的兼容性。通过合理地使用这些注解,可以大幅提高代码的可读性和可维护性,并构建灵活松耦合的应用程序架构。