复习笔记,个人记录用,有问题欢迎指出
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
- javadoc就是我们看源码时看到的那些API注释,比如
1.2.3 Java 7之后的新注解
@SafeVarargs
:忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告(Java 7)@FunctionalInterface
:标识一个匿名函数或函数式接口(Java 8)- 有兴趣可以了解下Lambda表达式相关,也是Java8的新特性,简单来说就是提供了更便利的函数式编程的方式,前提就是需要有一个函数式接口。
@Repeatable
:标识某注解可以在同一个声明上使用多次(Java 8)
1.3 注解的作用
长话短说:
- 编译时检查,比如
@Override
、@Deprecated
之类的。 - 反射中使用,比如我们可以通过注解过滤得到想invoke的method
- 帮助理解代码,这个就比较抽象了,只能说能帮我们理解整体的框架。
稍微啰嗦两句, 使用注解能带来很多便利,帮我们省去很多繁琐的实现细节(比如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的一个实践(模拟框架的服务管理与服务注入)