java注解的使用

Java 注解 (Annotation)

三类注解

编译器使用到的注解

@Override,@SuppressWarnings都是编译器使用到的注解,作用是告诉编译器一些事情,而不会进入编译后的.class文件

.class文件使用到的注解

需要通过工具对.class字节码文件进行修改的一些注解,某些工具会在类加载的时候,动态修改用某注解标注的.class文件,从而实现一些特殊的功能,一次性处理完成后,并不会存在于内存中,都是非常底层的工具库、框架会使用,对于开发人员来说,一般不会涉及到

运行期读取的注解

一直存在于JVM中,在运行期间可以读取的注解,也是最常用的注解,如Spring的@Controller,@Service,@Repository,@AutoWired,Mybatis的@Mapper,Junit的@Test等,这类注解很多都是工具框架自定义在运行期间发挥特殊作用的注解,一般开发人员也可以自定义这类注解。

###定义Annotation

使用@interface来定义一个注解

1
2
3
4
5
6
7
8
9
10

public @interface Table {
String value() default "";
}

public @interface Colum {
String value() default "";
String name() default "";
String dictType() default "";
}

注解主要用到了String类型,但实际上还可以是基本数据类型(不能为包装类)、枚举类型。

约定俗成: 最常用的参数应该命名为value,同时一般情况下我们都会通过default参数设置一个默认值。

元注解

可以修饰注解的注解即为元注解,Java已经定义了一些元注解,我们可以直接使用

可以使用元注解来修饰注解,元注解包括多个,必须设置@Target@Retention@Retention一般设置为RUNTIME

@Target

指定注解使用的目标对象,参数为ElementType[]

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

public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}


// ElementType枚举中定义的属性

public enum ElementType {
/** 通过ElementType.TYPE可以修饰类、接口、枚举 */
TYPE,

/** 通过ElementType.FIELD可以修饰类属性 */
FIELD,

/** 通过ElementType.METHOD可以修饰方法 */
METHOD,

/** 通过ElementType.PARAMETER可以修饰参数(如构造器或者方法中的) */
PARAMETER,

/** 通过ElementType.CONSTRUCTOR可以修改构造器 */
CONSTRUCTOR,

/** 通过ElementType.LOCAL_VARIABLE可以修饰方法内部的局部变量 */
LOCAL_VARIABLE,

/** 通过ElementType.ANNOTATION_TYPE可以修饰注解 */
ANNOTATION_TYPE,

/** 通过ElementType.PACKAGE可以修饰包 */
PACKAGE,

/**
* 可以用在Type的声明式前
*
* @since 1.8
*/
TYPE_PARAMETER,

/**
* 可以用在所有使用Type的地方(如泛型、类型转换等)
*
* @since 1.8
*/
TYPE_USE
}

ElementType.PACKAGE

1
2
3
4
5

@Target(ElementType.PACKAGE)
public @interface Table {
String value() default "";
}

需要 创建package-info.java文件

1
2
3
4
5
6
7
@Table
package annotation;
class PackageInfo {
public void hello() {
System.out.println("hello");
}
}

还可以自行了解下 ElementType.TYPE_PARAMETER和ElementType.TYPE_USE*

@Retention

用于定义注解的生命周期,参数为枚举RetentionPolicy

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
Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}


public enum RetentionPolicy {
/**
* 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中.
*/
SOURCE,

/**
* 【默认策略】,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到
*/
CLASS,

/**
* 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。
*/
RUNTIME
}

@Documented

表示是否将此注解的相关信息添加到javadoc文档中

@Inherited

该注解和子类的关系,使用此注解声明出来的自定义注解,在使用在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。注意,使用@Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Person {
String value() default "man";
}


@Person
public class Parent {
}
//子类也拥有@Person注解
class Son extends Parent {

}

Annotation处理

注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

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
//定义的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Colum {
String value() default "";
//用于表示某个属性代表的中文含义
String name() default "";
}

//用注解@Colum来修饰某个类的属性
public class Person {

@Colum(name = "姓名")
private String name;

@Colum(name = "性别")
private String gender;

@Colum(name = "年龄")
private int age;

@Colum(name = "住址")
private String address;

public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getGender() {return gender;}
public void setGender(String gender) {this.gender = gender;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public String getAddress() {return address;}
public void setAddress(String address) {this.address = address;}
}

//通过反射读取这个类的所有字段的中文含义,并保存到list中,然后打印出来
public static void main(String[] args) throws ClassNotFoundException {
List<String> columNames = new ArrayList<>();
Class clazz = Class.forName("annotation.Person");
//获取Person类所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields){
//获取该属性的Colum注解
Colum colum = field.getAnnotation(Colum.class);
//或者可以先判断有无该注解
field.isAnnotationPresent(Colum.class);
//将该属性通过注解配置好的中文含义取出来放到集合中
columNames.add(colum.name());
}

//打印集合
columNames.forEach((columName) -> System.out.println(columName));
}

SpringBoot 的启动原理

springboot启动原理

springboot 启动流程图

SpringBoot启动

SpringBootApplication

先来看看SpringBootApplication

1
2
3
4
5
6
7
8
9
10
11
12
@Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = { // 扫描路径设置(具体使用待确认)
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

实际上重要的只有三个Annotation

  • @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan

@Configuration

@Configuration对,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。

表达形式层面

基于XML配置的方式

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<!--bean定义-->
</beans>

基于JavaConfig的配置方式

1
2
3
4
@Configuration
public class MockConfiguration{
//bean定义
}

任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类

注册bean定义

基于XML的配置形式

1
2
3
<bean id="mockService" class="..MockServiceImpl">
...
</bean>

JavaConfig的配置形式

1
2
3
4
5
6
7
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}

**任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。 **

表达依赖注入关系层面

1
2
3
4
5
<bean id="mockService" class="..MockServiceImpl">
<propery name ="dependencyService" ref="dependencyService" />
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"></bean>

JavaConfig的配置形式

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl(dependencyService());
}

@Bean
public DependencyService dependencyService(){
return new DependencyServiceImpl();
}
}

**如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。 **

@ComponentScan

@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

@EnableAutoConfiguration

@Enable开头的Annotation定义:借助@Import的支持,收集和注册特定场景相关的bean定义

@EnableAutoConfiguration借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

其中重要的是:

  • @AutoConfigurationPackage:自动配置包
  • @Import: 导入自动配置的组件
AutoConfigurationPackage

它其实是注册了一个Bean的定义,new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的 同级以及子级 的包组件。

1
2
3
4
5
6
7
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}

SpringBoot启动

如图,他只会加载demo目录下的,不会加载到TestDemo, 这也就是为什么,我们要把DemoApplication放在项目的最高级中。

Import(AutoConfigurationImportSelector.class)

SpringBoot启动

AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector

ImportSelector有一个方法为:selectImports。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}

它其实是去加载 **public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;**外部文件。这个外部文件,有很多自动配置的类。如下:

SpringBoot启动

SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置

1
2
3
4
5
6
7
8
9
10
11
public abstract class SpringFactoriesLoader {
//...
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}


public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}

配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类

@EnableAutoConfiguration自动配置:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

所以,spring boot启动过程中,最关键的是@Import(EnableAutoConfigurationImportSelector.class),他借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration将SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

SpringBoot启动

启动boot查看bean

1
2
3
4
5
6
7
8
9
10
11
12
ApplicationContext ctx =   SpringApplication.run(GunsApplication.class, args);
//所有的bean,参考:http://412887952-qq-com.iteye.com/blog/2314051
String[] beanNames = ctx.getBeanDefinitionNames();
//String[] beanNames = ctx.getBeanNamesForAnnotation(RestController.class);//所有添加该注解的bean
logger.info("bean总数:{}", ctx.getBeanDefinitionCount());
int i = 0;
for (String str : beanNames) {
logger.info("{},beanName:{}", ++i, str);
}


logger.info("xxx is success!");

来源:

https://www.cnblogs.com/shamo89/p/8184960.html

git常用

起因

最近在使用自己搭建的git,用到一些命令操作,才发现自己很多命令不熟悉,标记一波,忘记了就来看看

常用命令

1.git 服务器搭建

  • 安装
    1
    yum install -y git
  • 查看版本
    1
    git --version

2.创建git用户

  • 查询是否存在这个用户
    1
    id universexin
  • 添加用户与设置密码
    1
    2
    3
    4
    添加用户
    useradd 用户名
    设置用户密码
    passwd 用户名

3.创建Git 仓库

  • 创建项目

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    创建项目
    mkdir -p /home/xx/xx.git
    初始化
    git init --bare /home/xx/xx.git
    赋予权限 - 对于用户
    cd /home/xx/
    chown -R universexin:universexin blog.git/



    a+rwx
  • 多用户提交权限配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 创建用户组
    groupadd gitgroup

    # 创建对应用户 xxx
    useradd xxx

    # 为用户设置密码
    passwd qixiao

    #用户qixiao添加到用户组gitgroup
    usermod -G gitgroup xxx

    #查看用户组
    vi /etc/group

    #修改git仓库所在目录/home/data/git/的用户组为gitgroup
    chgrp -R gitgroup /home/data/git/

    # 修改目录及其子文件的权限
    chmod -R 777 /home/data/git/
  • 客户端连接地址

    1
    2
    3
    4
    git clone 用户名@地址:路径/项目名
    如:
    git clone
    universexin@47.100.217.237:/home/git/wp_api.git
  • 不是22 默认端口情况

    1
    2
    3
    4
    git clone ssh://universexin@47.100.217.237/home/git/wp_api.git
    git clone ssh://universexin@39.108.249.87/home/git/dpzx-distributor.git
    git clone ssh://universexin@118.25.185.235/home/git/studyAll.git

4.禁止 git 用户 ssh 登录服务器

  • 编辑 /etc/passwd
    1
    2
    3
    4
    找到:
    git:x:502:504::/home/git:/bin/bash
    修改为
    git:x:502:504::/home/git:/bin/git-shell

使用

1. 提交本地创建的分支到远程

1
git push origin 分支名

2. 设置

1
2
3
$ git config --global user.name "defnngj"//给自己起个用户名
$ git config --global user.email "defnngj@gmail.com"//填写自己的邮箱
$ git init 初始化仓库

3. 提交

1
2
3
4
5
$ git add .   //(.)点表示当前目录下的所有内容,交给git管理,也就是提交到了git的本地仓库。
$ git commit –m”更新描述 ” //对你更新或修改了哪些内容做一个描述。
$ git remote add origin git@github.com:defnngj/hibernate-demo.git 选择提交地址(第一次提交使用)
$ git remote -v //查看你当前项目远程连接的是哪个仓库地址。
$ git push -u origin master //将本地的项目提交到远程仓库中。

4. 克隆(下载)

1
$ git clone git@github.com:defnngj/hibernate-demo.git 到想要保存的目录,下使用,将项目克隆到本地

5. 更新

1
2
$ git fetch origin    //取得远程更新
$ git merge origin/master //把更新的内容合并到本地分支/master

6. 查看

1
2
3
$ git status 查看是否有人更新
git log # 显示提交日志
git branch # 显示本地分支

其他

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
git init                                                  # 初始化本地git仓库(创建新仓库)
git config --global user.name "xxx" # 配置用户名
git config --global user.email "xxx@xxx.com" # 配置邮件
git config --global color.ui true # git status等命令自动着色
git config --global color.status auto
git config --global color.diff auto
git config --global color.branch auto
git config --global color.interactive auto
git clone git+ssh://git@192.168.53.168/VT.git # clone远程仓库
git status # 查看当前版本状态(是否修改)
git add xyz # 添加xyz文件至index
git add . # 增加当前子目录下所有更改过的文件至index
git commit -m 'xxx' # 提交
git commit --amend -m 'xxx' # 合并上一次提交(用于反复修改)
git commit -am 'xxx' # 将add和commit合为一步
git rm xxx # 删除index中的文件
git rm -r * # 递归删除
git log # 显示提交日志
git log -1 # 显示1行日志 -n为n行
git log -5
git log --stat # 显示提交日志及相关变动文件
git log -p -m
git show dfb02e6e4f2f7b573337763e5c0013802e392818 # 显示某个提交的详细内容
git show dfb02 # 可只用commitid的前几位
git show HEAD # 显示HEAD提交日志
git show HEAD^ # 显示HEAD的父(上一个版本)的提交日志 ^^为上两个版本 ^5为上5个版本
git tag # 显示已存在的tag
git tag -a v2.0 -m 'xxx' # 增加v2.0的tag
git show v2.0 # 显示v2.0的日志及详细内容
git log v2.0 # 显示v2.0的日志
git diff # 显示所有未添加至index的变更
git diff --cached # 显示所有已添加index但还未commit的变更
git diff HEAD^ # 比较与上一个版本的差异
git diff HEAD -- ./lib # 比较与HEAD版本lib目录的差异
git diff origin/master..master # 比较远程分支master上有本地分支master上没有的
git diff origin/master..master --stat # 只显示差异的文件,不显示具体内容
git remote add origin git+ssh://git@192.168.53.168/VT.git # 增加远程定义(用于push/pull/fetch)
git branch # 显示本地分支
git branch --contains 50089 # 显示包含提交50089的分支
git branch -a # 显示所有分支
git branch -r # 显示所有原创分支
git branch --merged # 显示所有已合并到当前分支的分支
git branch --no-merged # 显示所有未合并到当前分支的分支
git branch -m master master_copy # 本地分支改名
git checkout -b master_copy # 从当前分支创建新分支master_copy并检出
git checkout -b master master_copy # 上面的完整版
git checkout features/performance # 检出已存在的features/performance分支
git checkout --track hotfixes/BJVEP933 # 检出远程分支hotfixes/BJVEP933并创建本地跟踪分支
git checkout v2.0 # 检出版本v2.0
git checkout -b devel origin/develop # 从远程分支develop创建新本地分支devel并检出
git checkout -- README # 检出head版本的README文件(可用于修改错误回退)
git merge origin/master # 合并远程master分支至当前分支
git cherry-pick ff44785404a8e # 合并提交ff44785404a8e的修改
git push origin master # 将当前分支push到远程master分支
git push origin :hotfixes/BJVEP933 # 删除远程仓库的hotfixes/BJVEP933分支
git push --tags # 把所有tag推送到远程仓库
git fetch # 获取所有远程分支(不更新本地分支,另需merge)
git fetch --prune # 获取所有原创分支并清除服务器上已删掉的分支
git pull origin master # 获取远程分支master并merge到当前分支
git mv README README2 # 重命名文件README为README2
git reset --hard HEAD # 将当前版本重置为HEAD(通常用于merge失败回退)
git rebase
git branch -d hotfixes/BJVEP933 # 删除分支hotfixes/BJVEP933(本分支修改已合并到其他分支)
git branch -D hotfixes/BJVEP933 # 强制删除分支hotfixes/BJVEP933
git ls-files # 列出git index包含的文件
git show-branch # 图示当前分支历史
git show-branch --all # 图示所有分支历史
git whatchanged # 显示提交历史对应的文件修改
git revert dfb02e6e4f2f7b573337763e5c0013802e392818 # 撤销提交dfb02e6e4f2f7b573337763e5c0013802e392818
git ls-tree HEAD # 内部命令:显示某个git对象
git rev-parse v2.0 # 内部命令:显示某个ref对于的SHA1 HASH
git reflog # 显示所有提交,包括孤立节点
git show HEAD@{5}
git show master@{yesterday} # 显示master分支昨天的状态
git log --pretty=format:'%h %s' --graph # 图示提交日志
git show HEAD~3
git show -s --pretty=raw 2be7fcb476
git stash # 暂存当前修改,将所有至为HEAD状态
git stash list # 查看所有暂存
git stash show -p stash@{0} # 参考第一次暂存
git stash apply stash@{0} # 应用第一次暂存
git grep "delete from" # 文件中搜索文本“delete from”
git grep -e '#define' --and -e SORT_DIRENT
git gc
git fsck
git pull 更新

hibernate 神奇坑 BigDecimal保存的数据被放大

起因

公司项目开发,遇到一个问题,保存手续费信息是发现,数据库中存在奇异数字10w, what ,支付5元手续费10w是要逆天? 询问测试发现并没有改数据库。。代码问题?

查找问题

使用同样的报文,进行测试,发现无法触发该问题。。有毒。。(半个小时过去了)查阅了下上下文的调用,发现,出现问题的情况都是先调用了收银台支付,才发生这个情况。
测试了下,发现能触发。(重现问题)重现后,调用获取手续费,断点并打印,发现??? 传的是0.10??对象没问题呀,保存前保存后,都没问题,但是,数据库存入的数据是10w??找db抓数据,发现发送给数据库的数据是10w,这尼玛什么鬼。。。怀疑前面的计算手续费有问题,直接不实用计算的手续费了,new 了个 BigDecimal 0.1 保存,同样 10w。。搞事情。。。
不明所以
不知道该怎么办了,查看代码,既然都是手续费,那么。。网关也触发了手续费呢。。是否由于网关的手续费导致的呢,写死网关保存的手续费0.10,然后调用。发现,不触发了。。查看原先网关情况

原因

由于原先网关保存手续费中,手续费是空的,所以数据库里面存了个空进去。之后所以触发的保存,这个BigDecimal 字段 都是被 乘了10w。。

解决

将所以的 BigDecimal 这个对象,保存时,如果为空都设定Zero进去。。(后续尽量不要将BigDecimal 的null 被报错入数据库中,最好赋值Zero 报错,以防发生这个神奇数字的问题)
同上 10w 是哪里来的呢,我做了个测试发现是小数点后几位,就是10的几次方。 DECIMAL_DIGITS = 4 就 10的4次方 1w 如果 是6 就是 10的 6次方 100w了

其他

hibernate版本, 5.3.5 数据库 sybase

SpringBoot自动装配

配置排序依赖技巧

org.springframework.boot.autoconfigure 下的注解:

1
2
3
4
5
6
@AutoConfigureAfter
@AutoConfigureBefore
@AutoConfigureOrder
@AutoConfigurationPackage
@EnableAutoConfiguration
@SpringBootApplication

前 3 个是不能在普通项目中使用的,这 3 个注解是特地用于 autoconfigure 类的项目,后面 3 个注解是可以用于我们自己项目中的。

autoconfigure 类项目

在 Spring Boot starter 开发规范中,项目中会有一个空的名为 xxx-spring-boot-starter 的项目,这个项目主要靠 pom.xml 将所有需要的依赖引入进来。同时项目还会有一个 xxx-spring-boot-autoconfigure 项目,这个项目主要写带 @Configuration 注解的配置类,在这个类或者类中带 @Bean 的方法上,可以使用和顺序有关的注解,也就是前面提到的自己不能使用的这部分注解。xxx-spring-boot-autoconfigure 就是这里提到的 autoconfigure 类项目。

上面的注解只在 AutoConfigurationSorter 类中排序时用到了。被排序的这些类,都是通过 xxx-spring-boot-autoconfigure 项目中的 src/resources/META-INF/spring.factories 配置文件获取的,这个文件中的配置内容一般为

1
2
3
4
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration

Spring Boot 只会对从这个文件读取的配置类进行排序。

但是你不要以为将自己的配置类也配置在 spring.factories 中就能实现排序,如果你的类被自己 Spring Boot 启动类扫描到了,这个类的顺序会优先于所有通过 spring.factories 读取的配置类。所以当你的配置类对顺序有要求时就会出错。

外部配置加载

Spring Boot 支持多种外部配置方式,如下所示,从上往下加载优先级由高到低,内容相同时覆盖,不相同时累加。

1
2
3
4
5
6
7
8
9
10
11
12
13
命令行参数
来自java:comp/env的JNDI属性
使用“spring.config.location”改变默认的配置文件位置
Java系统属性(System.getProperties())
操作系统环境变量
RandomValuePropertySource配置的random.*属性值
jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
jar包外部的application.properties或application.yml(不带spring.profile)配置文件
jar包内部的application.properties或application.yml(不带spring.profile)配置文件
@Configuration注解类上的@PropertySource
通过SpringApplication.setDefaultProperties指定的默认属性

所以,当发现排序问题的时候:我还是用了@AutoConfigureOrder(-10000)解决的。感觉这样会不会有点残暴。。。

SpringBoot配置文件读取

起因

由于需要在项目中读取配置文件 :

把配置文件的信息,读取并自动封装成实体类,这样子,我们在代码里面使用就轻松方便多了,这时候,我们就可以使用@ConfigurationProperties,它可以把同类的配置信息自动封装成实体类

我们在配置文件中配置信息

1
2
3
base:
swagger-open: true #是否开启swagger (true/false)
spring-session-open: false

然后,我们可以定义一个实体类在装载配置文件信息

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
@Component
@ConfigurationProperties(prefix=BaseProperties.PREFIX)
public class BaseProperties {

public static final String PREFIX = "base";

private String swaggerOpen = false;;
private String springSessionOpen = false;

public String getSwaggerOpen() {
return swaggerOpen;
}
public void setSwaggerOpen(String swaggerOpen) {
this.swaggerOpen = swaggerOpen;
}
public String getSpringSessionOpen() {
return springSessionOpen;
}
public void setSpringSessionOpen(String springSessionOpen) {
this.springSessionOpen = springSessionOpen;
}
public String getPassword() {
return password;
}

}

需要使用的时候就直接注入即可

1
2
@Autowired
private BaseProperties baseProperties;

Spring boot 403 跨域问题处理

跨域问题处理

问题如下

1
2
3
OPTIONS http://192.168.10.196:8081/ims/dev/agent/1 403 
XMLHttpRequest connot load http://192.168.10.196:8081/ims/dev/agent/1. Respose to preflight request doesn't pass access control check : No 'Access-Control_Allow_origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. The response had Http status code 403.

解决方法

加filter:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import java.io.IOException;  
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;


public class CorsFilter implements Filter {
private static final boolean debug = true;
private FilterConfig filterConfig = null;

public CorsFilter() {
super();
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
if (filterConfig != null) {
if (debug) {
log("CrossFilter:Initializing filter");
}
}

}

@Override
public String toString() {
if (filterConfig == null) {
return ("CrossFilter()");
}
StringBuffer sb = new StringBuffer("CrossFilter(");
sb.append(filterConfig);
sb.append(")");
return (sb.toString());
}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (debug) {
log("CrossFilter:doFilter()");
}

if(response instanceof HttpServletResponse){
HttpServletResponse alteredResponse = ((HttpServletResponse)response);
// I need to find a way to make sure this only gets called on 200-300 http responses
// TODO: see above comment
addHeadersFor200Response(alteredResponse);
}
doBeforeProcessing(request, response);

Throwable problem = null;
try {
chain.doFilter(request, response);
} catch (Throwable t) {
// If an exception is thrown somewhere down the filter chain,
// we still want to execute our after processing, and then
// rethrow the problem after that.
problem = t;
t.printStackTrace();
}

doAfterProcessing(request, response);

// If there was a problem, we want to rethrow it if it is
// a known type, otherwise log it.
if (problem != null) {
if (problem instanceof ServletException) {
throw (ServletException) problem;
}
if (problem instanceof IOException) {
throw (IOException) problem;
}
sendProcessingError(problem, response);
}


}

@Override
public void destroy() {

}

private void doBeforeProcessing(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (debug) {
log("CrossFilter:DoBeforeProcessing");
}

}

private void doAfterProcessing(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (debug) {
log("CrossFilter:DoAfterProcessing");
}
}

private void addHeadersFor200Response(HttpServletResponse response){
//TODO: externalize the Allow-Origin
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, HEAD");
response.addHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Origin, X-Requested-With, Content-Type, Accept");
response.addHeader("Access-Control-Max-Age", "1728000");
}

private void sendProcessingError(Throwable t, ServletResponse response) {
String stackTrace = getStackTrace(t);

if (stackTrace != null && !stackTrace.equals("")) {
try {
response.setContentType("text/html");
PrintStream ps = new PrintStream(response.getOutputStream());
PrintWriter pw = new PrintWriter(ps);
pw.print("<html>\n<head>\n<title>Error</title>\n</head>\n<body>\n"); //NOI18N

// PENDING! Localize this for next official release
pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
pw.print(stackTrace);
pw.print("</pre></body>\n</html>"); //NOI18N
pw.close();
ps.close();
response.getOutputStream().close();
} catch (Exception ex) {
}
} else {
try {
PrintStream ps = new PrintStream(response.getOutputStream());
t.printStackTrace(ps);
ps.close();
response.getOutputStream().close();
} catch (Exception ex) {
}
}
}

public static String getStackTrace(Throwable t) {
String stackTrace = null;
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
sw.close();
stackTrace = sw.getBuffer().toString();
} catch (Exception ex) {
}
return stackTrace;
}

public void log(String msg) {
filterConfig.getServletContext().log(msg);
}


}

xml 中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.tyb.smartbox.ims.filter.CORSFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(function () {
$.ajax({
//url: "http://192.168.1.101:58080/smartbox_ims/dev/agent/a2a183813aab49f484bc6959f3ce9d3d",
url: "http://127.0.0.1:8099/ims/dev/agent/05681ab4e4aa431aa81eaa1416f7bd5f",
type: 'PUT',

"contentType" : "application/json",

"data" : JSON.stringify({
name : "测试数据02"
}),
dataType: "JSON",
success: function (data) {
alert("success");
},
//异常处理
error: function (data) {
alert("error");
}
});
});

servlet_web xml配置 描述

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?xml version="1.0" encoding="UTF-8"?> 
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!-- 在Spring框架中是如何解决从页面传来的字符串的编码问题的呢?
下面我们来看看Spring框架给我们提供过滤器CharacterEncodingFilter
这个过滤器就是针对于每次浏览器请求进行过滤的,然后再其之上添加了父类没有的功能即处理字符编码。
其中encoding用来设置编码格式,forceEncoding用来设置是否理会 request.getCharacterEncoding()方法,设置为true则强制覆盖之前的编码格式。-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 项目中使用Spring 时,applicationContext.xml配置文件中并没有BeanFactory,要想在业务层中的class 文件中直接引用Spring容器管理的bean可通过以下方式-->
<!--1、在web.xml配置监听器ContextLoaderListener-->
<!--ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。
在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。
它的API说明
第一段说明ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成。
如果查看ContextLoaderServlet的API,可以看到它也关联了ContextLoader这个类而且它实现了HttpServlet这个接口
第二段,ContextLoader创建的是 XmlWebApplicationContext这样一个类,它实现的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->
BeanFactory这样一来spring中的所有bean都由这个类来创建
IUploaddatafileManager uploadmanager = (IUploaddatafileManager) ContextLoaderListener.getCurrentWebApplicationContext().getBean("uploadManager");
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--2、部署applicationContext的xml文件-->
<!--如果在web.xml中不写任何参数配置信息,默认的路径是"/WEB-INF/applicationContext.xml,
在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml。
如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
在<param-value> </param-value>里指定相应的xml文件名,如果有多个xml文件,可以写在一起并以“,”号分隔。
也可以这样applicationContext-*.xml采用通配符,比如这那个目录下有applicationContext-ibatis-base.xml,
applicationContext-action.xml,applicationContext-ibatis-dao.xml等文件,都会一同被载入。
在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>

<!--如果你的DispatcherServlet拦截"/",为了实现REST风格,拦截了所有的请求,那么同时对*.js,*.jpg等静态文件的访问也就被拦截了。-->
<!--方案一:激活Tomcat的defaultServlet来处理静态文件-->
<!--要写在DispatcherServlet的前面, 让 defaultServlet先拦截请求,这样请求就不会进入Spring了,我想性能是最好的吧。-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.swf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.xml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.json</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.map</url-pattern>
</servlet-mapping>
<!--使用Spring MVC,配置DispatcherServlet是第一步。DispatcherServlet是一个Servlet,,所以可以配置多个DispatcherServlet-->
<!--DispatcherServlet是前置控制器,配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据某某规则分发到目标Controller(我们写的Action)来处理。-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name><!--在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指明了配置文件的文件名,不使用默认配置文件名,而使用dispatcher-servlet.xml配置文件。-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--其中<param-value>**.xml</param-value> 这里可以使用多种写法-->
<!--1、不写,使用默认值:/WEB-INF/<servlet-name>-servlet.xml-->
<!--2、<param-value>/WEB-INF/classes/dispatcher-servlet.xml</param-value>-->
<!--3、<param-value>classpath*:dispatcher-servlet.xml</param-value>-->
<!--4、多个值用逗号分隔-->
<param-value>classpath:spring/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup><!--是启动顺序,让这个Servlet随Servletp容器一起启动。-->
</servlet>
<servlet-mapping>
<!--这个Servlet的名字是dispatcher,可以有多个DispatcherServlet,是通过名字来区分的。每一个DispatcherServlet有自己的WebApplicationContext上下文对象。同时保存的ServletContext中和Request对象中.-->
<!--ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了:P,Spring把Bean放在这个容器中,在需要的时候,用getBean方法取出-->
<servlet-name>DispatcherServlet</servlet-name>
<!--Servlet拦截匹配规则可以自已定义,当映射为@RequestMapping("/user/add")时,为例,拦截哪种URL合适?-->
<!--1、拦截*.do、*.htm, 例如:/user/add.do,这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。-->
<!--2、拦截/,例如:/user/add,可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。 -->
<url-pattern>/</url-pattern> <!--会拦截URL中带“/”的请求。-->
</servlet-mapping>

<welcome-file-list><!--指定欢迎页面-->
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<error-page> <!--当系统出现404错误,跳转到页面nopage.html-->
<error-code>404</error-code>
<location>/nopage.html</location>
</error-page>
<error-page> <!--当系统出现java.lang.NullPointerException,跳转到页面error.html-->
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error.html</location>
</error-page>
<session-config><!--会话超时配置,单位分钟-->
<session-timeout>360</session-timeout>
</session-config>
</web-app>

3种创建线程的方式

前言

面试的时候,面试官问我有几种线程创建方式。。我想了想,就2种,是呀,在这之前,我一直以为是2种的,尴尬。。
后来面试官告诉我,有3种,我表示。。奇怪了。平时都只有2中呀,他告诉我说,还有一种有返回值的。回来后我去查了一下啊。。还真的是。。。

1. 基础 Thread类型,(常见,解释了)
2. Runnable(常见)
3. 通过Callable和Future创建线程,竟然是Util包下的,尴尬。。。

直接贴了下网上的代码。

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
package com.universexin.blog.web;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable<Integer>{

public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}

}

@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
创建线程的三种方式的对比

采用实现Runnable、Callable接口的方式创见多线程时,
*优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

*劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时

  • 优势是:
    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

  • 劣势是:
    线程类已经继承了Thread类,所以不能再继承其他父类。