Spring中的注解和自定义注解

自带注解

@ModelAttribute

作用在接口方法或接口参数上

在接口方法上时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//没有返回值 为void  应该是无论请求什么接口 会率先执行
@ModelAttribute
public void populateModel(ModelMap model) {
model.addAttribute("attributeName", "123");
}
//不指定指定属性名称,方法返回一个对象,相当于model.addAttribute("user", user)
@ModelAttribute
public User addUser() {
User user = new User();
return user;
}
//或者 model.addAttribute("aaaa", user) 相同
@ModelAttribute("aaaa")
public User addUser() {
User user = new User();
return user;
}
//指定属性名称,方法返回一个字符串,相当于model.addAttribute("string1-key", "string1-value")
@ModelAttribute("string1-key")
public String addString() {
return "string1-value";
}

参数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//从模型中获取一个属性值,将其转换到对应类型的变量中 可以理解成解析对应的jsp中   abc.name   abc.password  然后提交过来 用user解析

@RequestMapping(value = "helloWorld")
public String helloWorld(@ModelAttribute("abc") User user,
@ModelAttribute("attributeName") String aName,
@ModelAttribute("string1-value") String svalue) {
return "helloWorld";
}

//从Form表单或者URL参数中获取属性参数值,放到对应类型的参数中,注意:此时表单中的组件名和参数属性名称一致,如User对象有两个属性,分别为username,password,则表单中input的名称必须为username,password,才能实现属性值注入。
//注意这个User类必须要有无参数的构造函数或者是setter方法
//此时@ModelAttribute可以不用显式写
@RequestMapping(value = "helloWorld2")
public String helloWorld2(@ModelAttribute("user") User user) {
System.out.println("---helloWorld---"+user.getUsername()+", "+user.getPassword());
return "helloWorld";
}

对比RequestParam、RequestBody

1
2
3
1. application/x-www-form-urlencoded,这种情况的数据@RequestParam、@ModelAttribute可以处理,@RequestBody也可以处理。
2. multipart/form-data,@RequestBody不能处理这种格式的数据。(form表单里面有文件上传时,必须要指定enctype属性值为multipart/form-data,意思是以二进制流的形式传输文件。)
3. application/json、application/xml等格式的数据,必须使用@RequestBody来处理。

@Order(n):bean的加载顺序

1
优先级 1 2 3 ...

@EnableAsync:开启异步(多线程)

@Async:异步

可以注解在类或方法上,但不能是 static 修改

异步使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 @Async
public void dealNoReturnTask() {
logger.info("返回值为void的异步调用开始" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("返回值为void的异步调用结束" + Thread.currentThread().getName());
}
// Future<String> 回调函数
@Async
public Future<String> dealHaveReturnTask(int i) {
logger.info("asyncInvokeReturnFuture, parementer=" + i);
Future<String> future;
try {
Thread.sleep(1000 * i);
future = new AsyncResult<String>("success:" + i);
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
}
return future;
}

判断是否执行结束

1
2
Future<String> f = Future<String>(1);
f.isDone()

@Converter(autoApply = true)

Spring data jpa 中的实体属性转换器

@EnableScheduling:启动定时任务

@Scheduled:定时任务注解

在方法上添加@Scheduled就可以定时执行了。

  • @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
  • @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
  • @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
  • @Scheduled(cron="*/5 * * * * *") :通过cron表达式定义规则

推荐

在线cron表达式网址

@EnableConfigurationProperties:读入自动配置类

比如

1
@ConfigurationProperties(prefix = "spring.datasource")

@ConditionalOnClass:加载自动配置类

1
2
3
4
5
6
@Configuration //配置类
//这里就是前面说的,这个注解读入我们的配置对象类
@EnableConfigurationProperties(HelloProperties.class)
//当类路径存在这个类时才会加载这个配置类,否则跳过,这个很有用比如不同jar包间类依赖,依赖的类不存在直接跳过,不会报错
@ConditionalOnClass(HelloService.class)
// HelloService 类上不需要加 Component

@EnableAutoConfiguration

不要自己添加

@Primary 优先选择

@Configuration 把一个类作为一个IoC容器

它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。

@Scope注解 作用域

@Lazy(true) 表示延迟初始化

@Service用于标注业务层组件、

@Controller用于标注控制层组件

如struts中的action)

@Repository 标注数据访问组件

即DAO组件。

@Component 组件<泛指>,当组件不好归类的时候

@PostConstruct 用于指定初始化方法(用在方法上)

@PreDestory 用于指定销毁方法(用在方法上)

@Resource 默认按名称装配,当找不到与名称匹配的bean才会按类型装配。

@DependsOn:定义Bean初始化及销毁时的顺序

@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常

@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用

@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用

@ConditionalOnMissingBean

当前bean 不存在时,使用备用bean

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

@Bean
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}

}

@Value

1
2
@Value(value = "${snowflake.data_center_id}")
private long dataCenterId = -1L;

@ConfigurationProperties(prefix = “”)

自定义配置类, prefix 为前缀.

比如 DataSourceProperties

1
2
3
4
5
6
7
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private String driverClassName;
private String url;
private String username;
private String password;
}

对于下面的配置,上面会自动注入

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root

@NestedConfigurationProperty

使用于 @ConfigurationProperties(prefix = "")中, 主要是为了嵌套配置, 比如当前配置中有 MapListclass等类型的属性时,需要用 @NestedConfigurationProperty 标记。

用来指示应该将常规(非内部)类视为嵌套类。

比如 MybatisPlusProperties

1
2
3
4
5
6
7
@ConfigurationProperties(prefix = Constants.MYBATIS_PLUS)
public class MybatisPlusProperties {
@NestedConfigurationProperty
private MybatisConfiguration configuration;
@NestedConfigurationProperty
private GlobalConfig globalConfig = GlobalConfigUtils.defaults();
}

自定义注解

1、元注解(meta-annotation):

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited
  这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target:

   @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

  取值(ElementType)有:

    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {

}

注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。

@Retention:

  @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

  取值(RetentionPoicy)有:

    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)

  Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例如下:

1
2
3
4
5
6
7
8
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}

Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理


  @Documented:

  **@**Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

1
2
3
4
5
6
7
8
9
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}

@Inherited:

  @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

  注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

  当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

  实例代码:

1
2
3
4
5
6
7
8
9
10
11
/**
*
* @author peida
*
*/
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}

自定义注解:

 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

  定义注解格式:
  public @interface 注解名 {定义体}

  注解参数的可支持数据类型:

    1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
    2.String类型
    3.Class类型
    4.enum类型
    5.Annotation类型
    6.以上所有类型的数组

  Annotation类型里面的参数该怎么设定:
  第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   
  第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  
  第三,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。

  简单的自定义注解和使用注解实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 水果名称注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 水果颜色注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};

/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.GREEN;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package annotation;

import annotation.FruitColor.Color;

public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;




public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}


public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}

public void displayName(){
System.out.println("水果的名字是:苹果");
}
}
1
2
3
4
5
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
String desc() default "无信息";
}

用反射机制来调用注解中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.dragon.test.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
* 用反射机制来调用注解中的内容
* Created by gmq on 2015/9/10.
*/
public class MyReflection
{
public static void main(String[] args) throws Exception
{
// 获得要调用的类
Class<MyTest> myTestClass = MyTest.class;
// 获得要调用的方法,output是要调用的方法名字,new Class[]{}为所需要的参数。空则不是这种
Method method = myTestClass.getMethod("output", new Class[]{});
// 是否有类型为MyAnnotation的注解
if (method.isAnnotationPresent(MyAnnotation.class))
{
// 获得注解
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// 调用注解的内容
System.out.println(annotation.hello());
System.out.println(annotation.world());
}
System.out.println("----------------------------------");
// 获得所有注解。必须是runtime类型的
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations)
{
// 遍历所有注解的名字
System.out.println(annotation.annotationType().getName());
}
}
}

自定义注解还可以放入AOP

注解 @UserAccess

1
2
3
4
5
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
String desc() default "无信息";
}

AOP 中的使用

1
2
3
4
@After("@annotation(userAccess)")
public void after(JoinPoint jp, UserAccess userAccess) {
log.debug("second after:" + userAccess.desc());
}

本文地址: https://github.com/maxzhao-it/blog/post/44534/