# 注解

返回:专题

为什么注解

  • 为什么学习注解?
  • 学习注解有什么好处?
  • 学完能做什么?

答:
1.能够读懂别人写的代码,特别是框架相关的代码;
2.让编程更加简洁,代码更加清晰;
3.让别人高看一眼。

Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。 因为平常开发少见,相信有不少的人员会认为注解的地位不高。其实同 classs 和 interface 一样,注解也属于一种类型。它是在 Java SE 5.0 版本中开始引入的概念。

🐉 校验数据相关注解实战 🐉 springBoot注解

# 注解怎么工作

back

# 注解是什么

注解即是元数据。不包含任何业务逻辑。元数据是关于数据的数据。所以注解是代码的元数据
注解是一种特殊的 Java 构造,用于修饰类、方法、字段、参数、变量、构造函数或包。这是 JSR-175 选择的提供元数据的工具

# 为什么引入注解

# 注解如何工作以及如何编写自定义注解

# 自带注解

返回顶部

# @Override

表示覆盖或重写父类的方法

# @Deprecated

表示该方法已经过时了。(当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不能再用,但不影响以前项目使用,提醒你新替代待的方法或是类。如果程序员不小心使用了它的元素,那么编译器会发出警告信息。)

# @SuppressWarnings

表示忽略指定警告,比如@Suppvisewarnings("Deprecation")

# 注解的分类

back

按运行机制(注解存在于程序的那个阶段)将注解分为三类:

  • 源码注解(只在源码存在)
  • 编译注解(在class文件中也存在)
  • 运行时注解(在运行阶段仍然起作用)

按照来源来分的话,有如下三类:

  • 1:JDK自带的注解

Java目前只内置了三种标准注解:@Override、@Deprecated、@SuppressWarnings,以及四种元注解:@Target、@Retention、@Documented、@Inherited)

  • 2:第三方的注解——这一类注解是我们接触最多和作用最大的一类
  • 3:自定义注解——也可以看作是我们编写的注解,其他的都是他人编写注解

按照功能来分的,还有,元注解——注解的注解

# 自定义注解

back

# 1.自定义注解的语法要求

/**
 - @interface关键字修饰注解
 - 可以用default为成员定义默认值
 - 成员的类型是受限的,合法的成员类型只能是原始类型以及String、Class、Annotation、Enumeration
 */
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiAuthen {
    boolean validate() default true;
}

/**
- 成员以无参、无异常方式声明;
- 如果只有一个成员,且定义为value(),使用时可以忽略成员名和(=)
- 注解可以没有成员,没有成员的注解叫做标识注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
    int value();
}
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

# 2.元注解

back

像@Documented、@Target等

现在逐行分析

@Target 是注解的作用域 :
表示该注解可以用于一个类中的那些属性及方法上,如果作用域类型有多个用英文逗号分隔

下面是注解的作用域的列表: Target 是目标的意思,@Target 指定了注解运用的地方。

你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

    - ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
    - ElementType.CONSTRUCTOR 可以给构造方法进行注解
    - ElementType.FIELD 可以给属性进行注解
    - ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    - ElementType.METHOD 可以给方法进行注解
    - ElementType.PACKAGE 可以给一个包进行注解
    - ElementType.PARAMETER 可以给一个方法内的参数进行注解
    - ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
1
2
3
4
5
6
7
8
  • @Retention:表示该注解的生命周期

注解按声明周期有个分类 Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下:

  • RetentionPolicy.SOURCE

注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

  • RetentionPolicy.CLASS

注解只被保留到编译进行的时候,它并不会被加载到 JVM 中,运行时忽略。

  • RetentionPolicy.RUNTIME

注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们(通过反射可获取)。

我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。

@Inherited:

此注解是标识性的元注解,表示当前注解可以由子注解来继承 Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 说的比较抽象。代码来解释。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
1
2
3
4
5
6

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
可以这样理解: 老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

  • @Documented:

表示生成javadoc的时候会包含注解

  • @Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

@interface Persons {
Person[]  value();
}

@Repeatable(Persons.class)
@interface Person{
String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。
什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。

我们再看看代码中的相关容器注解。

@interface Persons {
Person[]  value();
}
1
2
3

按照规定,它里面必须要有一个 value 的属性,
属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解。
Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。 我们可能对于 @Person(role=“PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM ,大家不明白正常,马上就讲到注解的属性这一块。

# 3.使用自定义注解

back

注解通过 @interface关键字进行定义。

public @interface TestAnnotation {
}
1
2

它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 TestAnnotaion 的注解。

你可以简单理解为创建了一张名字为 TestAnnotation 的标签。 上面创建了一个注解,那么注解的的使用方法是什么呢。

@TestAnnotation
public class Test {
}
1
2
3

创建一个类 Test,然后在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了。

你可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上面。 不过,要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HtAnnotation {
    String name();
    String sex();
    int age() default 16;
}

    /**
     - @注解名(成员名1=成员值1,成员名2=成员值2)
     */
    @HtAnnotation(name = "ht",sex = "male",age = 17)
    public void justTestAnno(){

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注解的定义看起来很像接口的定义,事实上,与其他任何Java接口一样,注解也将会编译成class文件。

定义注解时,会需要一些元注解(meta-annotation),如@Target和@Retention。@Target用来定义你的注解将用于什么地方(例如是一个方法或一个域)。
@Retention用来定义该注解在哪一个级别可用,在源代码(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。

# 4.解析注解

back

概念:通过反射获取类、函数或成员上运行时注解信息,从而实现动态控制程序运行的逻辑。

如:

@Test
public void justTestAnno(){
    try {
//        Class clazz = com.chlm.mysession.Thread.CallableTest.class;
        Class clazz = Class.forName("com.chlm.mysession.Thread.CallableTest");
        Assert.assertTrue("没有注解@HtAnnotation",clazz.isAnnotationPresent(HtAnnotation.class));
        HtAnnotation htAnnotation = (HtAnnotation) clazz.getAnnotation(HtAnnotation.class);
        System.out.println(htAnnotation.name());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        System.out.println(e.getMessage());
    } catch (Exception e){
        System.out.println(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用forName()方法加载类,并使用getAnnotation(Description.class)检查该类是否带有@Description注解。
注解的继承只能作用在类上,方法上的注解不会被继承,Interface中的所有注解不会被继承。

# 注解的提取

back

博文前面的部分讲了注解的基本语法,现在是时候检测我们所学的内容了。

我通过用标签来比作注解,前面的内容是讲怎么写注解,然后贴到哪个地方去,而现在我们要做的工作就是检阅这些标签内容。 形象的比喻就是你把这些注解标签在合适的时候撕下来,然后检阅上面的内容信息。

要想正确检阅注解,离不开一个手段,那就是反射。

# 注解与反射

back

注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
1

然后通过 getAnnotation() 方法来获取 Annotation 对象。

 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
1

或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}
1

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。

比如

@TestAnnotation()
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13

程序的运行结果是:

id:-1
msg:
1
2

这个正是 TestAnnotation 中 id 和 msg 的默认值。

上面的例子中,只是检阅出了注解在类上的注解,其实属性、方法上的注解照样是可以的。同样还是要假手于反射。

@TestAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
    TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
    //获取类的注解
    System.out.println("id:"+testAnnotation.id());
    System.out.println("msg:"+testAnnotation.msg());
}
try {
    Field a = Test.class.getDeclaredField("a");
    a.setAccessible(true);
    //获取一个成员变量上的注解
    Check check = a.getAnnotation(Check.class);
    if ( check != null ) {
        System.out.println("check value:"+check.value());
    }
    Method testMethod = Test.class.getDeclaredMethod("testMethod");
    if ( testMethod != null ) {
        // 获取方法中的注解
        Annotation[] ans = testMethod.getAnnotations();
        for( int i = 0;i < ans.length;i++) {
            System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
        }
    }
} catch (NoSuchFieldException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    System.out.println(e.getMessage());
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    System.out.println(e.getMessage());
}
}
}

@Test
    public void testListTest(){
        try {
            Class clazz = ListTest.class;
            Method testNormalList =clazz.getDeclaredMethod("testNormalList");
            if (testNormalList!=null){
                Annotation[] annotations =testNormalList.getAnnotations();
                Arrays.stream(annotations).forEach(annotation -> {
                    System.out.println(annotation.annotationType().getSimpleName());
                });
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

它们的结果如下:

id:-1
msg:hello
check value:hi
method testMethod annotation:Perform
1
2
3
4

需要注意的是,如果一个注解要在运行时被成功提取,那么 **@Retention(RetentionPolicy.RUNTIME)*- 是必须的。

# 注解的项目实战

back

# 一些常用注解

# Configuration

作用于类上,指明该类就相当于一个xml配置文件

# Bean(1)

作用于方法上,指明该方法相当于xml配置中的,注意方法名的命名规范

# PropertySource

指定读取的配置文件,引入多个value={"xxx:xxx","xxx:xxx"},ignoreResourceNotFound=true 文件不存在时忽略

# Value

back

获取配置文件的值,该注解还有很多语法知识,

// 注入普通字符
@Value("htring")
    private String firstName;
// 注入操作系统属性
@Value("#{systemProperties['os.name']}")
    private String firstName;
// 注入表达式结果,那个大写的T必须有
@Value("#{T(Math).random() - 100}")
    private String firstName;
// 注入其它bean属性,方法
@Value("#{personModelDao.count()}")
    private String firstName;
// 注入文件资源
@Value("classpath:com/clm/test.txt")
    private Resource firstName;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# PostConstruct和PreDestroy

从Java EE 5规范开始,Servlet中增加了两个影响Servlet生命周期的注解(Annotion);@PostConstruct和@PreDestroy。这两个注解被用来修饰一个非静态的void()方法 。写法有如下两种方式:

@PostConstruct

Public void someMethod() {}
1

或者

public @PostConstruct void someMethod(){}
1

被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行执行之后执行

我现在要说的是用实例说明它有什么作用。

比如说我有一种情况,在我的servlet初始化加载之前我想处理一些东西,像加载缓存等等。

怎么做。@PostConstruct就派上用场了。那为什么这玩意用的不多呢,这是因为如果初始化之前我们要加载或处理某些玩意完全可以在构造器初始化时就处理了,但这种方法需要自己重写构造器。好吧。直接上代码看看具体用它的时候怎么做的。

# SafeVarargs

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。

@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
1
2
3
4
5
6
7

上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。

Java 官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。

# FunctionalInterface

back

函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
比如

@FunctionalInterface
public interface Runnable {
    /**
     - When an object implementing interface <code>Runnable</code> is used
     - to create a thread, starting the thread causes the object's
     - <code>run</code> method to be called in that separately executing
     - thread.
     - <p>
     - The general contract of the method <code>run</code> is that it may
     - take any action whatsoever.
     *
     - @see     java.lang.Thread#run()
     */
    public abstract void run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,上面源码可以看到它就被 @FunctionalInterface 注解。
可能有人会疑惑,函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式。

# Transactional

back

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务声明式事务两种。

  • 编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:
try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}
1
2
3
4
5
6
7
  • 声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了。
@Transactional
@GetMapping("/test")
public String test() {
    int insert = cityInfoDictMapper.insert(cityInfoDict);
}
1
2
3
4
5
# @Transactional注解可以作用于哪些地方

@Transactional 可以作用在接口、类、类方法。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;

    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setParentCityId(2);
        cityInfoDict.setCityName("2");
        cityInfoDict.setCityLevel("2");
        cityInfoDict.setCityCode("2");
        int insert = cityInfoDictMapper.insert(cityInfoDict);
        return insert + "";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# @Transactional注有哪些属性
  • propagation属性:代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

    • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
    • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
    • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
    • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
    • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
    • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
    • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。
  • isolation 属性:事务的隔离级别,默认值为 Isolation.DEFAULT

    • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
    • Isolation.READ_UNCOMMITTED
    • Isolation.READ_COMMITTED
    • Isolation.REPEATABLE_READ
    • Isolation.SERIALIZABLE
  • timeout 属性:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

  • readOnly 属性:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

  • rollbackFor 属性:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

  • noRollbackFor属性:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

# @Transactional失效场景
  • @Transactional 应用在非 public 修饰的方法上

如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。

注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

  • @Transactional 注解属性 propagation 设置错误

TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

  • @Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。
Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;
其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
1
2

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

private int getDepth(Class<?> exceptionClass, int depth) {
    if (exceptionClass.getName().contains(this.exceptionName)) {
        // Found it!
        return depth;
}
    // If we've gone as far as we can go and haven't found it...
    if (exceptionClass == Throwable.class) {
        return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
1
2
3
4
5
6
7
8
9
10
11

4、同一个类中方法调用,导致@Transactional失效 开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("2");
    /**
        * B 插入字段为 3的数据
        */
    this.insertB();
    /**
        * A 插入字段为 2的数据
        */
    int insert = cityInfoDictMapper.insert(cityInfoDict);

    return insert;
}

@Transactional()
public Integer insertB() throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("3");
    cityInfoDict.setParentCityId(3);

    return cityInfoDictMapper.insert(cityInfoDict);
}
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

5、异常被你的 catch“吃了”导致@Transactional失效 这种情况是最常见的一种@Transactional注解失效场景,

@Transactional
private Integer A() throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
            * A 插入字段为 2的数据
            */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
            * B 插入字段为 3的数据
            */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

答案:不能!

会抛出异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 复制代码 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

6、数据库引擎不支持事务 这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

# jackson中的注解

返回顶部

# @JsonProperty

此注解可用于:属性、get、set上。用来表示外部属性名字,就是使用别名序列化,而不是对象的名字

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonProperty
{
    /**
     * Optional property that may be used to change the way visibility of
     * accessors (getter, field-as-getter) and mutators (contructor parameter,
     * setter, field-as-setter) is determined, either so that otherwise
     * non-visible accessors (like private getters) may be used; or that
     * otherwise visible accessors are ignored.
     *<p>
     * Default value os {@link Access#AUTO} which means that access is determined
     * solely based on visibility and other annotations.
     *
     * @since 2.6
     */
    Access access() default Access.AUTO;
    public enum Access
    {
    /**
        * Access setting which means that visibility rules are to be used
        * to automatically determine read- and/or write-access of this property.
        */
    AUTO,

    /**
        * Access setting that means that the property may only be read for serialization,
        * but not written (set) during deserialization.
        */
    READ_ONLY,

    /**
        * Access setting that means that the property may only be written (set)
        * for deserialization,
        * but will not be read (get) on serialization, that is, the value of the property
        * is not included in serialization.
        */
    WRITE_ONLY,

    /**
        * Access setting that means that the property will be accessed for both
        * serialization (writing out values as external representation)
        * and deserialization (reading values from external representation),
        * regardless of visibility rules.
        */
    READ_WRITE
    ;
    }
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50

新增access属性,此属性的值可为:AUTO、READ_ONLY、WRITE_ONLY、READ_WRITE

# 实现序列化、反序列化时忽略字段

/**
    * 告警内容
    */
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@TableField("ALARM")
private String alarm;
// 此写法,实现返回前端的json对象不包含此属性、前端回传时会包含此属性
1
2
3
4
5
6
7

# @JsonAutoDetect

类注解

@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonAutoDetect
{
}


@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC)
1
2
3
4
5
6
7
8
9

# @JsonIgnore

  • 放在get方法上,可以实现序列化(给前端)时忽略,反序列化时保留
  • 放在属性上,则序列化和反序列化时都被忽略

# @JsonSerialize

此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的代码,比如序列化一个double时在其后面限制两位小数点。

# @JsonDeserialize

此注解用于属性或者setter方法上,用于在反序列化时可以嵌入我们自定义的代码,类似于上面的@JsonSerialize。

# @JsonIgnoreProperties

此注解是类注解,作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。
实体类使用的注解,用于序列化的时候忽略指定的一系列属性,或者反序列化的时候忽略未知的属性(没有getter/setter的属性)。

  • 序列化的时候,@JsonIgnoreProperties({"prop1", "prop2"}),忽略列表中的属性。
  • 反序列化的时候,@JsonIgnoreProperties(ignoreUnknown=true),忽略没有get/set的属性。

# @JsonFormat

此注解用于属性或者方法上(最好是属性上),可以方便的把Date类型或者LocalDateTime类型直接转化为我们想要的模式。

# @JsonInclude

属性值为null的不参与序列化。例子:@JsonInclude(Include.NON_NULL)

# @JsonIgnoreType

实体类使用的注解,表示该类被忽略。

# @JsonUnwrapped

作用在属性字段或方法上,用来将子JSON对象的属性添加到封闭的JSON对象。

@JsonUnwrapped

如果没有@JsonUnwrapped,序列化后将为:

{"id":111,"name":{"firstName":"张","secondName":"三"}}
1

反之:

{"id":111,"firstName":"张","secondName":"三"}
1

# 反序列化的过程中操作

反序列化的过程中操作

@JacksonInject
@JsonAnySetter
@JsonCreator
@JsonSetter
@JsonEnumDefaultValue

# 序列化的过程中操作

序列化的过程中操作

@JsonAnyGetter
@JsonGetter
@JsonPropertyOrder
@JsonRawValue
@JsonValue
@JsonRootName

# 类型处理

类型处理

@JsonSubTypes
@JsonTypeId
@JsonTypeInfo
@JsonTypeName

# 对象引用及标识

对象引用及标识

@JsonManagedReference
@JsonIdentityInfo

# 元注释

元注释

@JacksonAnnotation
@JacksonAnnotationsInside

# spring注解

back

# Spring之缓存注解@Cacheable

实操项目中引用

cacheNames:该缓存的方法名称,cacheName也可以使用value
key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,方法的参数
unless:当方法返回空值时,就不会被缓存起来,决定是否要否定方法缓存,可以用来做条件判断
scf_company:结合redis,配置在redis的配置文件里
condition:属性指定发生的条件

# spring必须掌握的45个注解

back

注解 说明
SpringBoot_spring
@SpringBootApplication 包含@Configuration@EnableAutoConfiguration@ComponentScan通常用在主类上;
声明bean的注解
@Repository 用于标注数据访问组件,即DAO组件;
@Service 用于标注业务层组件;
@Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注;
切面(AOP)相关注解
@Aspect 声明一个切面(类上),使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After 在方法执行之后执行(方法上)
@Before 在方法执行之前执行(方法上)
@Around 在方法执行之前与之后执行(方法上)
@PointCut 声明切点,在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)
@RestController 用于标注控制层组件(如struts中的action),包含@Controller@ResponseBody
@Controller 用于标注是控制层组件,需要返回页面时请用@Controller而不是@RestController;
@ResponseBody 表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用,<br />在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中;比如异步获取json数据,加上@responsebody后,会直接返回json数据;
@RequestBody 参数前加上这个注解之后,认为该参数必填。表示接受json字符串转为对象 List等;
java配置类相关注解
@ComponentScan 组件扫描。个人理解相当于,如果扫描到有@Component @Controller @Service等这些注解的类,则把这些类注册为bean*;
@Configuration 指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上;
@Bean 相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理;
@EnableAutoConfiguration 让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,一般加在主类上;
@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
注入bean的注解
@AutoWired byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作;<br>当加上(required=false)时,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错;默认是true
@Resource(name=”name”,type=”type”) 没有括号内内容的话,默认byName。与@Autowired干类似的事;
@Inject 由JSR-330提供
@Qualifier 当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用;
@RequestMapping RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径;
@GetMapping、@PostMapping等 相当于@RequestMapping(value=”/”,method=RequestMethod.Get\Post\Put\Delete等),是个组合注解;
@RequestParam 用在方法的参数前面。相当于 request.getParameter();
@PathVariable 路径变量。如 RequestMapping(“user/get/mac/{macAddress}”) ;
Jpa
@Entity @Table(name=”“):表明这是一个实体类。一般用于jpa ,这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略;
@MappedSuperClass 用在确定是父类的entity上。父类的属性子类可以继承;
@NoRepositoryBean 一般用作父类的repository,有这个注解,spring不会去实例化该repository;
@Column 如果字段名与列名相同,则可以省略;
@Id 表示该属性为主键;
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator = “repair_seq”) 表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq;
@SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1) name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致;
@Transient 表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.<br />如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic;
@Basic(fetch=FetchType.LAZY) 标记可以指定实体属性的加载方式;
@JsonIgnore 作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响;
@JoinColumn(name=”loginId”) 一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键。
@OneToOne
@OneToMany
@ManyToOne: 对应Hibernate配置文件中的一对一,一对多,多对一。
全局异常处理
@ControllerAdvice 包含@Component。可以被扫描到。统一处理异常;
@ExceptionHandler(Exception.class) 用在方法上面表示遇到这个异常就执行以下方法。
springcloud
@EnableEurekaServer 用在springboot启动类上,表示这是一个eureka服务注册中心;
@EnableDiscoveryClient 用在springboot启动类上,表示这是一个服务,可以被注册中心找到;
@LoadBalanced 开启负载均衡能力;
@EnableCircuitBreaker 用在启动类上,开启断路器功能;
@HystrixCommand(fallbackMethod=”backMethod”) 用在方法上,fallbackMethod指定断路回调方法;
@EnableConfigServer 用在启动类上,表示这是一个配置中心,开启Config Server;
@EnableZuulProxy 开启zuul路由,用在启动类上;
@SpringCloudApplication 包含@SpringBootApplication、@EnableDiscovertyClient、@EnableCircuitBreaker,分别是SpringBoot注解、注册服务中心Eureka注解、断路器注解。对于SpringCloud来说,这是每一微服务必须应有的三个注解,所以才推出了@SpringCloudApplication这一注解集合。
@Bean的属性支持
@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean)其设置类型包括:Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),Protetype (每次调用新建一个bean),Request (web项目中,给每个http request新建一个bean),Session (web项目中,给每个http session新建一个bean),GlobalSession(给每一个 global http session新建一个Bean实例)
@StepScope 在Spring Batch中还有涉及
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
异步相关
@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)
@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
定时任务相关
@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
@Enable*注解说明 这些注解主要用来开启对xxx的支持。
@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持
@EnableAsync 开启异步方法的支持
@EnableScheduling 开启计划任务的支持
@EnableWebMvc 开启Web MVC的配置支持
@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories 开启对SpringData JPA Repository的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableCaching 开启注解式的缓存支持

# RequestMapping

back

该注解有六个属性:
params:指定request中必须包含某些参数值是,才让该方法处理。
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
value:指定请求的实际地址,指定的地址可以是URI Template 模式
method:指定请求的method类型, GET、POST、PUT、DELETE等
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html;
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。

# PathVariable

public String getByMacAddress(
 @PathVariable(“macAddress”) String macAddress){
//do something;}
1
2
3

参数与大括号里的名字相同的话,注解后括号里的内容可以不填。

# RequestBody

@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。

# ControllerAdvice

back

@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。@ControllerAdvice需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。
通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上, 这对所有注解了 @RequestMapping的控制器内的方法有效。

# ExceptionHandler

@ExceptionHander注解用于标注处理特定类型异常类所抛出异常的方法。当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法。

# ResponseStatus

@ResponseStatus注解可以标注请求处理方法。使用此注解,可以指定响应所需要的HTTP STATUS。特别地,我们可以使用HttpStauts类对该注解的value属性进行赋值。

# RequestParam

@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。
该注解的其他属性配置与@PathVariable的配置相同,特别的,如果传递的参数为空,还可以通过defaultValue设置一个默认值。

# RequestAttribute

@RequestAttribute这个注解很新,Spring4.3后才有;
我们可以使用API调用的方式(ServletRequest.getAttribute())来达到目的,而不用注解。
org.springframework.web.bind.annotation.SessionAttribute org.springframework.web.bind.annotation.RequestAttribute org.springframework.web.bind.annotation.ModelAttribute

@RequestAttribute只负责从request里面取属性值,至于你什么时候往里放值,是有多种方式的可以达到的:

  • @ModelAttribute注解预存
  • HandlerInterceptor拦截器中预存
  • 请求转发带过来

# ModelAttribute

back

通过此注解,可以通过模型索引名称来访问已经存在于控制器中的model。
与@PathVariable和@RequestParam注解一样,如果参数名与模型具有相同的名字,则不必指定索引名称
特别地,如果使用@ModelAttribute对方法进行标注,Spring会将方法的返回值绑定到具体的Model上。

# CrossOrigin

@CrossOrigin注解将为请求处理类或请求处理方法提供跨域调用支持。如果我们将此注解标注类,那么类中的所有方法都将获得支持跨域的能力。使用此注解的好处是可以微调跨域行为。

# InitBinder

@InitBinder注解用于标注初始化WebDataBinider的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。

# SpringDI注解

# DependsOn

@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。

# Bean(2)

back

@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethoddestroyMethod的用法。

# Scope

@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的该类的实例。在定义一个Bean时,可以设置Bean的scope属性为prototype:scope=“prototype”,也可以使用@Scope注解设置

@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)
1

# Primary

当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。

# PostConstruct与PreDestroy

back

值得注意的是,这两个注解不属于Spring,它们是源于JSR-250中的两个注解,位于common-annotations.jar中。@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法@PreDestroy注解用于标注Bean被销毁前需要执行的方法

# Qualifier

当系统中存在同一类型的多个Bean时,@Autowired在进行依赖注入的时候就不知道该选择哪一个实现类进行注入。此时,我们可以使用@Qualifier注解来微调,帮助@Autowired选择正确的依赖项。

# springBoot注解

back

# SpringBootApplication

# EnableAutoConfiguration

@EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。

# Conditional

@Conditional注解可以控制更为复杂的配置条件。在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。

# Autowired

Field injection is not recommended Inspection info: Spring Team recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".

属性字段注入的方式不推荐,检查到的问题是:Spring团队建议:"始终在bean中使用基于构造函数的依赖项注入,始终对强制性依赖项使用断言"

-「基于构造函数的依赖注入」

public class UserServiceImpl implents UserService{
    private UserDao userDao;
    
    @Autowired
    public UserServiceImpl(UserDao userDao){
        this.userDao = userDao;
    }
}
1
2
3
4
5
6
7
8

-「基于Setter的依赖注入」

public class UserServiceImpl implents UserService{
     private UserDao userDao;
     
     @Autowired
     public serUserDao(UserDao userDao){
         this.userDao = userDao;
     }
 }
1
2
3
4
5
6
7
8

-「基于字段的依赖注入」

public class UserServiceImpl implents UserService{
     @Autowired
     private UserDao userDao;
 }
1
2
3
4

# 基于字段的依赖注入缺点

-「对于有final修饰的变量不好使」

Spring的IOC对待属性的注入使用的是set形式,但是final类型的变量在调用class的构造函数的这个过程当中就得初始化完成,这个是「基于字段的依赖注入」做不到的地方.只能使用「基于构造函数的依赖注入」的方式

-「掩盖单一职责的设计思想」

我们都知道在OOP的设计当中有一个「单一职责思想」,如果你采用的是「基于构造函数的依赖注入」的方式来使用Spring的IOC的时候,当你注入的太多的时候,这个构造方法的参数就会很庞大

-「与Spring的IOC机制紧密耦合」

当你使用「基于字段的依赖注入」方式的时候,确实可以省略构造方法和setter这些个模板类型的方法,但是,你把控制权全给Spring的IOC了,别的类想重新设置下你的某个注入属性,没法处理(当然反射可以做到).

本身Spring的目的就是解藕和依赖反转,结果通过再次与类注入器(在本例中为Spring)耦合,失去了通过自动装配类字段而实现的对类的解耦,从而使类在Spring容器之外无效.

-「隐藏依赖性」

当你使用Spring的IOC的时候,被注入的类应当使用一些public类型(构造方法,和setter类型方法)的方法来向外界表达:我需要什么依赖.但是「基于字段的依赖注入」的方式,基本都是private形式的,private把属性都给封印到class当中了.

-「无法对注入的属性进行安检」

「基于字段的依赖注入」方式,你在程序启动的时候无法拿到这个类,只有在真正的业务使用的时候才会拿到,一般情况下,这个注入的都是非null的,万一要是null怎么办,在业务处理的时候错误才爆出来,时间有点晚了,如果在启动的时候就暴露出来,那么bug就可以很快得到修复(当然你可以加注解校验).

如果你想在属性注入的时候,想根据这个注入的对象操作点东西,你无法办到.我碰到过的例子:一些配置信息啊,有些人总是会配错误,等到了自己测试业务阶段才知道配错了,例如线程初始个数不小心配置成了3000,机器真的是狂叫啊!这个时候就需要再某些Value注入的时候做一个检测机制.

# Profile注解详解

back

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件。默认时可以用@Profile("default")

@Profile("dev")
@Bean("dev")
public DataSource devDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(password);
    dataSource.setDriverClass(driverClass);
    return dataSource;
}
1
2
3
4
5
6
7
8
9