【Java笔记】如何创建自己的注解+注解怎么用于反射+SpringBoot常见注解


复习笔记,个人记录用,有问题欢迎指出

0. 为什么SpringBoot要整这么多注解?

说白了就是为了方便,SpringBoot通过注解维护各种 Bean 组件的简化开发流程的各种配置(也就是方便自动装配)。
下面我们暂时跳出SpringBoot框架,稍微回顾一下基础的Java常用注解跟一些基础知识。

1. 一些基础知识

1.1 什么是注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

注解就是我们平常看到的@开头的玩意儿,有时候加载方法上,有时候又会加在类、变量、参数上。别的注解没见过,那@Override总见过吧?这就是Java内置的注解之一。

1.2 Java内置注解

1.2.1 作用在代码上的注解

  • @Override:作用在方法上,检查该方法是否是重写方法,如果父类中没有这个方法(即尝试重写父类不存在的方法),编译时会报错。 当然,重写的方法并不是一定要加 @Override,这不是必须的。
  • @Deprecated : 作用在方法上,标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings:指示编译器去忽略注解中声明的警告。

1.2.2 作用在注解的注解(元注解)

  • @Retention:定义注解的保留策略,也就是标识这个注解怎么保存
    • @Retention(RetentionPolicy.SOURCE):注解仅存在于源码中,在class字节码文件中不包含;
    • @Retention(RetentionPolicy.CLASS) :注解会在class字节码文件中存在,但运行时无法获得(默认的保留策略);
    • @Retention(RetentionPolicy.RUNTIME):注解会在class字节码文件中存在,在运行时可以通过反射获取到。
  • @Target - 标记这个注解可以修饰的目标
    • @Target(ElementType.TYPE):接口、类
    • @Target(ElementType.FIELD) :属性
    • @Target(ElementType.METHOD):方法
    • @Target(ElementType.PARAMETER) :方法参数
    • @Target(ElementType.CONSTRUCTOR) :构造函数
    • @Target(ElementType.LOCAL_VARIABLE) :局部变量
    • @Target(ElementType.ANNOTATION_TYPE) :注解
    • @Target(ElementType.PACKAGE) :包
  • @Inherited - 指定被修饰的Annotation将具有继承性
  • @Documented - 标记这些注解能否被包含在javadoc中
    • javadoc就是我们看源码时看到的那些API注释,比如@Param

1.2.3 Java 7之后的新注解

  • @SafeVarargs :忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告(Java 7)
  • @FunctionalInterface:标识一个匿名函数或函数式接口(Java 8)
    • 有兴趣可以了解下Lambda表达式相关,也是Java8的新特性,简单来说就是提供了更便利的函数式编程的方式,前提就是需要有一个函数式接口。
  • @Repeatable:标识某注解可以在同一个声明上使用多次(Java 8)

1.3 注解的作用

长话短说:

  1. 编译时检查,比如 @Override@Deprecated之类的。
  2. 反射中使用,比如我们可以通过注解过滤得到想invoke的method
  3. 帮助理解代码,这个就比较抽象了,只能说能帮我们理解整体的框架。

稍微啰嗦两句, 使用注解能带来很多便利,帮我们省去很多繁琐的实现细节(比如Springboot的自动装配、依赖注入等等),但同时,对于不太了解相关注解的朋友来说反而更不好理解这些细节,办法也很简单,面试经典-读过XXX源码吗?

1.4 如何实现一个自己的注解?

1.4.1定义自己的注解

大概格式如下

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

可以看到用了几个元注解

  • @Retention(RetentionPolicy.RUNTIME):注解会在class字节码文件中存在,在运行时可以通过反射获取到;
  • @Target(ElementType.METHOD):注解可以加在函数方法上;
  • @Documented:可有可无,希望加入javadoc就加上。
  • 如果希望自定义注解在类的继承关系中可以传递,那也可以加上@Inherited

我们也可以给注解绑定一些数据:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String name() default "Hansdas";
    String value();
}

其中,default可以指定默认值

1.4.2 使用自定义注解实现运行时调用

public class MyClass {
    public MyClass() {
    }

    @MyAnnotation(name = "Hansbas", value = "114514")
    public void MyMethod1(){
        System.out.println("Method1 used");
    }

    public void MyMethod2(){
        System.out.println("Method2 used");
    }
}

我们在MyMethod1上面加了自定义注解,在Method2上面没加,下面尝试通过反射的方式来在运行时获得Method1:

public class Test {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException {
        // 一般需要把new的对象交给一个container管理,这里省事直接new了
        MyClass myClass = new MyClass();
        // 下面通过反射调用方法
        String className = "Annotation.MyClass";
        Class<?> clazz = Class.forName(className);
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods){
            if(method.getDeclaredAnnotation(MyAnnotation.class) != null){
                method.invoke(myClass);
                MyAnnotation myAnno = method.getDeclaredAnnotation(MyAnnotation.class);
                System.out.println("name: "+myAnno.name()+", value: "+myAnno.value());
            }
        }
    }
}

结果如下,method1成功获得,并且成功获得了注解的各个属性值,而method2被过滤了
在这里插入图片描述

2. SpringBoot常见注解

上一节讲了Java中的一些基础注解,以及如何创建自己的注解并用于反射机制实现运行时调用。接下来理一下SpringBoot里常见的一些注解。

2.1 IOC于DI相关

IOC就是讲对象的创建与管理交给Spring容器管理,DI依赖注入则是调用容器中的Bean实例,二者都涉及到了很多注解来方便我们使用

2.1.1 IOC相关

  • @Component:表明一个类会作为组件类,并告知Spring要为这个类创建bean,交给IOC容器管理,也是最基础的一个注解,包含很多衍生类:

    注解说明位置
    @Component表明一个类会作为组件类,并告知Spring要为这个类创建bean不属于下面三类时用(比如一些工具类)
    @Controller标记的类就是一个SpringMVC Controller对象,分发处理器会扫描使用该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。标注在控制器上(Controller层的bean对象)
    @Service应用于业务层,用于标注业务层组件,表示定义一个bean,自动根据bean的类名实例化一个首写字母为小写的bean。标注在业务类上Service层的bean对象)
    @Repository只能标注在 DAO 类,除了将该类标记为bean以外,还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。标注在数据访问类上(Dao层的bean对象,由于与mybatis整合,用的少)
  • @Lazy :Spring在应用程序上下文启动时去创建所有的单例bean对象, 而@Lazy注解可以延迟加载bean对象,即在使用时才去初始化,好处包括:

    • 可以减少Spring的IOC容器启动时的加载时间
    • 可以解决bean的循环依赖问题
  • @Scope() :声明bean的作用域,常见的包括singleton、prototype等等

    • singleton 容器内同 名称 的 bean 只有一个实例(单例)(默认)
    • prototype 每次使用该 bean 时会创建新的实例(非单例)

另外需要注意的是,@Component时需要我们加在希望作为bean的类上的,但是如果bean来自第三方依赖,那就无法修改了,也就是不能使用@Component,这时候就需要通过配置类来将其交给IOC容器,涉及到的注解有:

  • @Configuration:声明配置类,类中写一个方法返回目标对象。底层也是@Component ,在项目启动时会生成一个bean
  • @Bean:声明第三方bean
  • @Import:可以导入带有@Configuration的配置类。只有使用@Import导入的类才会被Spring加载到IOC容器中!(不然Configuration就白整了)
//----------配置类----------
@Configuration
public class CommonConfig {
    @Bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
}
//----------启动类----------
@Import({CommonConfig.class})
@SpringBootApplication
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}
//----------测试注入---------
class xxApplicationTests {
    @Autowired
    private SAXReader saxReader; //IOC容器对象,默认是方法名小写
    //获取bean对象
    @Test
    public void testGetBean(){
       ...
    }
}

2.1.2 DI相关

经典面试问题:@Autowired与@Resource的区别:
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入

  • @Autowired:默认按照类型进行,如果容器中存在多个相同类型的bean,会报错
    • @Primary:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现
  • @Resource:是按照bean的名称进行注入,但这个严格来说是JDK提供的

2.2 SpringBoot启动类中

@SpringBootApplication :标记为启动类,Spring Boot 会运行这个类的 main 方法来启动 Spring Boot 应用。源码如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...

可以看到里面包含了前面提到的一些元注解:

  • @Target({ElementType.TYPE}):前面提到过的元注解之一,声明注解可以加在类上;
  • @Retention(RetentionPolicy.RUNTIME):前面提到过的元注解之一,注解存在于字节码文件,且能运行时获得;
  • @Documented:前面提到过的元注解之一,加入javadoc;
  • @Inherited:前面提到过的元注解之一,标记注解能否被继承继承,如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解;

以及其他的一些Spring相关注解

  • @SpringBootConfiguration@Configuration的封装
    • @Configuration声明一个配置类,底层也是@Component ,在项目启动时会生成一个bean;
  • @EnableAutoConfiguration:开启SpringBoot自动配置,简化Spring原先繁琐的XML配置,加快开发效率;
  • @ComponentScan:自动扫描当前包及其子包下标注了@Component@Controller@Service@Repository 类,并加入spring容器管理。

2.3 Controller中

Controller,当然是需要@Controller来声明这个Controller类可以交给IOC容器管理
此外,Controller中由于涉及到请求的解析与返回,对于请求处理、输入数据与返回数据也有一些常见的注解。

2.3.1 Controller参数接收

  • @RequestParam:参数名与形参变量名相同,定义形参即可接收参数,过程中会自动类型转换,对不上则就收不到,为null,此时可以用RequestMapping

  • @DateTimeFormat:完成日期格式转换

  • @PathVariable:路径参数

  • @RequestBody:将HTTP请求体中的JSON装配到目标类

2.3.2 Controller响应数据

  • @ResponseBody:作用在Controller方法/类上,返回return的结果,如果是实体类则返回json

  • @CrossOrigin:来开启跨域请求,让其他域的请求可以访问该controller,属性包括:

    • origin属性:允许可访问的域列表
    • maxAge属性:准备响应前的缓存持续的最大时间(以秒为单位)

2.3.3 Controller请求绑定

首先,需要@RequestMapping绑定url路径到当前controller上,一般用于获得整个controller各个函数的公共路径
然后是根据HTTP请求类型绑定到对应函数:

  • @GetMapping:处理Get请求
  • @PostMapping:处理Post请求,新增操作
  • @PutMapping:处理put请求,执行大规模的替换操作(而不是更新操作)
  • @DeleteMapping:处理delete请求,删除
  • @PatchMapping:处理patch请求,对资源数据打补丁或局部更新

举个简单的例子(来自黑马的Spring基础教程)

@RestController
@RequestMapping("/depts") // 抽取公共请求路径
public class DeptController {
	@GetMapping
    public Result list(){
        // 部门列表查询,调用对应的service
        List<Dept> deptList = deptService.list();
        return Result.success(deptList);
    }
    
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        // 调用对应的service根据ID查找部门
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }
    
    @DeleteMapping("/{id}")
    // - `@PathVariable` :接收路径参数
    //- `@DeleteMapping` :删除注解
    public Result delete(@PathVariable Integer id) throws Exception{
        // 调用service删除部门
        deptService.delete(id);
        log.info("根据id删除部门:{}",id);
        return Result.success();
    }

	@PostMapping
    public Result add(@RequestBody Dept dept){
        // 调用service新增部门
        deptService.add(dept);
        return Result.success();

    }
}

Reference

Java项目的程序里为什么老用注解?注解有哪些作用
Java 注解(Annotation)
一文搞懂🔥SpringBoot自动配置原理
【Java笔记】Reflection的一个实践(模拟框架的服务管理与服务注入)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值