程序猿的博客


  • 首页

  • 标签

  • 分类

  • 归档

Spring扩展接口

发表于 2018-10-09 | 分类于 Spring

扩展接口

bean的生命周期过程的接口

Spring为容器内的bean生命周期提供了大量的扩展接口。可以实现这些接口,在Spring bean生命周期过程中对bean实例进行扩展。。

ApplicationContext中bean的生命周期

Spring中一个bean被创建过程的执行流程。
image

BeanFactoryPostProcessor

Spring IoC容器允许BeanFactoryPostProcessor在容器实例化任何bean之前读取bean的定义(配置元数据),并可以修改它。同时可以定义多个BeanFactoryPostProcessor,通过设置’order’属性来确定各个BeanFactoryPostProcessor执行顺序。

1
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;

在Spring中内置了一些BeanFactoryPostProcessor实现类:

org.springframework.beans.factory.config.PropertyPlaceholderConfigurer : 读取配置文件,在bean初始化之前,根据注解或xml配置给bean设置值。
org.springframework.beans.factory.config.PropertyOverrideConfigurer:似于PropertyPlaceholderConfigurer,PropertyOverrideConfigurer对于bean属性可以有缺省值或者根本没有值
org.springframework.beans.factory.config.CustomEditorConfigurer:注册自定义属性编辑器,用于给bean属性转化类型

InstantiationAwareBeanPostProcessor

InstantiationAwareBeanPostProcessor 继承至BeanPostProcessor,比起BeanPostProcessor。InstantiationAwareBeanPostProcessor多出以下4个方法。主要处理的是bean的实例化过程。

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
/**
* 在bean实例化前执行(构造函数执行前)
* @param beanClass 被实例化bean的class
* @param beanName bean的名字
* @return 返回null就会执行默认的实例化过程,返回Object会替代默认的实例化bean。
* @throws BeansException
*/
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}

/**
* 在bean实例化后执行
* @param bean bean被创建的实例对象,实例(依赖Spring注入的属性)属性值还没赋值。
* @param beanName bean
* @return 如果返回true,执行下面的属性值注入。返回false,属性设置行为会被跳过。
* @throws BeansException
*/
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}

/**
* 用于给bean处理值注入,@Autowire就在这个过程处理注入
* @param pvs Spring工厂中存在的属性值
* @param bean bean的实例对象
* @param beanName bean的名字
* @return 返回实例真实设置的值,返回null,继承当前pvs,继续执行后续的postProcessProperties处理值。
* @throws BeansException
*/
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return null;
}

/**
* Spring5已经过期,用postProcessProperties代替。
* @param pvs
* @param pds
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Deprecated
@Nullable
default PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
} /**
* 实例化对象前
*
*/

BeanPostProcessor

BeanPostProcessor有2个方法,扩展bean初始化流程(实例化早于初始化,初始化是为对象赋值,注入属性之类的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   /**
* 在类初始化前被调用
* @param bean bean的实例对象
* @param beanName bean的名字
* @return 返回bean的实例对象,如果返回null,后续的BeanPostProcessors 将不会被调用。
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

/**
* bean初始化之后
* @param bean bean的实例对象
* @param beanName bean的名字
* @return 返回bean的实例对象,如果返回null,后续的BeanPostProcessors 将不会被调用。
* @throws org.springframework.beans.BeansException in case of errors
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

InitializingBean

实现该接口的bean的所有属性都被设置后执行。

1
2
3
4
   /**
* bean的所有属性都被设置完成时执行
*/
void afterPropertiesSet() throws Exception;

DisposableBean

实现该接口的bean的销毁时执行,用于释放资源

1
2

void destroy() throws Exception;

FactoryBean

某些bean的实例化过程比较复杂,如果通过配置方式进行可能需要进行非常繁琐的配置。FactoryBean的作用就是用来实现这些bean的实例化。实现FactoryBean接口的bean,不能正常的通过Spring容器使用,通过Spring容器获取的总是它的getObject方法创建的实例。

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
   /** 
* 返回一个实例
* @return 一个bean实例,可以为null
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
@Nullable
T getObject() throws Exception;

/**
* @return 返回bean实例的类型
* @see ListableBeanFactory#getBeansOfType
*/
@Nullable
Class<?> getObjectType();

/**
*返回由FactoryBean创建的bean实例的作用域是singleton还是prototype,如果isSingleton()返回true,则该实 *例会放到Spring容器中单实例缓存池中
* @return 对象是否为单例
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
default boolean isSingleton() {
return true;
}

Spring中实用工具类

发表于 2018-10-08 | 分类于 Spring

ResolvableType 泛型处理

获取接口上的泛型

1
2
3
4
5
6
7
ResolvableType resolvableType = ResolvableType.forClass(clazz);
ResolvableType[] interfaces = resolvableType.getInterfaces();
if(interfaces==null || interfaces.length==0) {
return null;
}
Class<?> resolve = interfaces[0].getGeneric(0).resolve();
return resolve;

获取父类上的泛型

1
2
3
ResolvableType resolvableType = ResolvableType.forClass(clazz);
Class<?> resolve = resolvableType.getSuperType().getGeneric(0).resolve();
return resolve;

反射工具类 ReflectionUtils

反射处理

ResourcePatternResolver

PathMatchingResourcePatternResolver 用于加载资源,支持ant风格
配合MetadataReaderFactory获取资源元数据(class文件数据)

对象工具类 ObjectsUtils

ant路径匹配 AntPathMatcher

Enviroment 环境变量和程序变量处理,可用于解析路径 ${} 变量

默认实现类是StandardEnvironment

1
2
3
Enviroment enviroment =new StandardEnviroment();
//将环境中变量test(从System.getProperty和System.getenv获取)替换掉${test}
enviroment.resolveRequiredPlaceholders("${test}_ff");

Hibernate

发表于 2018-10-04 | 分类于 hibernate

UserType

hibernate中可以自定义映射属性的类型,例如我实体类中是一个List类型的字段,我想将其存储成json字符串。可以同时实现UserType接口,实现自定义的JsonType。

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
/**
* 数据库中字段类型,取Types类中的值
* @see java.sql.Types
* @return int[] the typecodes
*/
int[] sqlTypes();

/**
*nullSafeGet 放回的类型,也是实体类中字段类型
* @return Class
*/
Class returnedClass();

/**
*
* 对比实体对象和数据库对象
* @param x
* @param y
* @return boolean
*/
boolean equals(Object x, Object y) throws HibernateException;

/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
int hashCode(Object x) throws HibernateException;

/**
* 将该列数据库类型化为实体类中的类型
* @param rs a JDBC result set
* @param names the column names
* @param session
*@param owner the containing entity @return Object
* @throws HibernateException
* @throws SQLException
*/
Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException;

/**
*
* 将实体属性类型转成数据库类型保存
* @param st a JDBC prepared statement
* @param value the object to write
* @param index statement parameter index
* @param session
* @throws HibernateException
* @throws SQLException
*/
void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException;

/**
*
* 用户自己定义的类型的深复制方法
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
Object deepCopy(Object value) throws HibernateException;

/**
* 是否可变
*
* @return boolean
*/
boolean isMutable();

/**
* 副本对象,可变对象要保证是value的深复制对象
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws HibernateException
*/
Serializable disassemble(Object value) throws HibernateException;

/**
* cached对象可变的话,可缓存对象的深复制
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws HibernateException
*/
Object assemble(Serializable cached, Object owner) throws HibernateException;

/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param original the value from the detached entity being merged
* @param target the value in the managed entity
* @return the value to be merged
*/
Object replace(Object original, Object target, Object owner) throws HibernateException;

JsonType实现

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
public class JsonType implements UserType {
@Override
public int[] sqlTypes() {
return new int[]{Types.CLOB};
}

@Override
public Class returnedClass() {
return Object.class;
}

@Override
public boolean equals(Object o, Object o1) throws HibernateException {
return JSONUtils.toJSONString(o).equals(o1);
}

@Override
public int hashCode(Object o) throws HibernateException {
return JSONUtils.toJSONString(o).hashCode();
}

@Override
public Object nullSafeGet(ResultSet resultSet, String[] strings, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException, SQLException {
String json = resultSet.getString(strings[0]);
if (StringUtils.isEmpty(json)) {
return null;
}
return JSONUtils.parse(json);
}

@Override
public void nullSafeSet(PreparedStatement preparedStatement, Object o, int i, SharedSessionContractImplementor sharedSessionContractImplementor) throws HibernateException, SQLException {
if (o == null) {
preparedStatement.setString(i, "");
} else {
preparedStatement.setString(i, JSONUtils.toJSONString(o));
}
}

@Override
public Object deepCopy(Object o) throws HibernateException {
String s = JSONUtils.toJSONString(o);
return JSONUtils.parse(s);
}

@Override
public boolean isMutable() {
return true;
}

@Override
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) deepCopy(o);
}

@Override
public Object assemble(Serializable serializable, Object o) throws HibernateException {
return deepCopy(serializable);
}

@Override
public Object replace(Object o, Object o1, Object o2) throws HibernateException {
return deepCopy(o);
}
}

使用

1
2
3
4
5
6
7
@Entity
@TypeDef(name = "json",typeClass = JsonType.class)
public class Entity implements IEntity<String> {
@Type(type = "json")
private List<Integer> ids;
...
}

java字节码框架——ASM

发表于 2018-10-01 | 分类于 工具类

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

1
2
3
4
5
 <dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>

主要类

ClassReader

用于读取解析字节码文件

1
ClassReader cr = new ClassReader(Test.class.getName());

ClassWriter

用于生产一个新的字节码文件,继承ClassVisitor。

1
ClassWriter cw = new ClassWriter(0);

ClassAdapter

ClassAdapter继承ClassVisitor。构造一个ClassAdapter需要传入一个ClassWriter。这是装饰模式。对ClassWirter的增强。

1
ClassAdapter ca= new ClassAdapter(cw);

ClassAdapter可以被一个ClassReader接受,将ClassReader产生的事件传递给ClassWriter。这样方便基于ClassReader读取的class上设置新的class文件。默认的ClassAdapter会完全将ClassReader复制到ClassWriter,所以一般都会根据实现ClassAdapter的自定义子类。

1
2
3
4
ClassReader cr = new ClassReader(Test.class.getName());
ClassWriter cw = new ClassWriter(0);
ClassAdapter ca= new ClassAdapter(cw);
cr.accept(ca,0);

ClassVisitor

ClassVisitor的接口API。ClassAdapter和ClassWriter都继承ClassVisitor,他们的API都一样。
ClassAdapter的对应方法参数都是从ClassReader中读取的。ClassWriter通过设置对应方法这些参数生成类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
//类头(类版本号,访问控制标识,类名,类签名(包括泛型),父类名,接口)
public void visit(int version, int access, String name, String signture, String superName, String[] interfaces);
//源文件
public void visitSource(String source, String debug);
//
public void visitOuterClass(String owner, String name, String desc);
//注解
AnnotationVisitor visitAnnotation(String desc, boolean visible);
//属性
public void visitAttribute(Attribute attr);
//内部类
public void visitInnerClass(String name, String outerName, String innerName, int access);
//字段
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);
//方法
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);

//用于添加属性
void visitEnd();
}

使用例子

在Bean类加一个字段,并生成class文件

1
2
3
4
5
6
7
8
9
10
11
public class Bean {
private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1.读取Bean类
ClassReader cr = new ClassReader(Bean.class.getName());
//2.创建ClassWriter
ClassWriter cv = new ClassWriter(0);
ClassAdapter ca= new ClassAdapter(cv){

//一般添加信息都在visitEnd上添加,修改的话就在对应的方法上修改,如要把id字段的类型改为int,
//就在ClassAdapter的visitField上调用cv.visitField。不重写的方法,ClassWriter都会复制ClassReader中的全部信息
@Override
public void visitEnd() {
//调用ClassWriter的visitField添加字段, 不知道参数怎么配置的可以将ClassReader的配置打印出来参考
cv.visitField(2,"test","Ljava/lang/String;",null,null);
}
};
cr.accept(ca,0);
byte[] data = cw.toByteArray();
File file = new File("C://Bean.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();

添加方法

这个添加一个无参数构造方法的实现。

1
2
3
4
5
6
7
8
9
MethodVisitor mv = classWriter.visitMethod(1, "<init>", "()V", null, null);
//添加方法methodVisitor需要执行下面的方法,生成方法体
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
//第二个参数是父类名,这里是调用父类的构造函数
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();

将classWriter转成Class

调用classLoader的defineClass方法将字节数组转化为Class,defineClass方法是protected方法,所以要继承ClassLoader才可以使用。

1
2
byte[] bytes = classWriter.toByteArray();
return defineClass(className, bytes, 0, bytes.length);

jdbc连接问题集

发表于 2018-09-30 | 分类于 问题集合 , jdbc
  1. 使用mysql-connector-java 6.x版本触发问题
    1
    You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.

解决:url加上serverTimezone参数

1
jdbc:mysql://localhost:3306/mmorpg?serverTimezone=UTC&characterEncoding=utf-8

  1. druid连接池导致异常:java.sql.SQLException: validateConnection false
    解决方法,加上效率配置
    1
    2
    3
    4
    5
    6
    7
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    destroy-method="close">
    ....
    <!--验证是否链接成功-->
    <property name="validationQuery" value="${jdbc.validateQuery}"/>
    <property name="validationQueryTimeout" value="${jdbc.validationQueryTimeout}"/>
    </bean>

jexl-解析字符串为java代码

发表于 2018-09-30

jexl可以将字符串解析成java代码来执行

  1. maven导入jar

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl3</artifactId>
    <version>3.1</version>
    </dependency>
  2. 代码例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //1. 创建jexl引擎
    JexlEngine jexlEngine = new Engine();
    //2. 设置变量,可以设置对象来引用对象方法
    JexlContext jc = new MapContext();
    jc.set("b",1);
    jc.set("a",2);
    //3. 创建执行的java表达式
    JexlExpression expression = jexlEngine.createExpression("a+b");
    //4. 计算结果
    Object evaluate = expression.evaluate(jc);
    System.out.println(evaluate);

高性能java缓存库-Caffeine

发表于 2018-09-30 | 分类于 缓存 , caffeine

Caffeine简介

Caffeine基于java8的高性能,接近最优的缓存库。Caffeine提供的内存缓存使用参考Google guava的API。Caffeine是基于Google guava和 ConcurrentLinkedHashMap的设计经验上改进的成果。

Caffeine可以通过建造者模式灵活的组合以下特性:

  1. 通过异步自动加载实体到缓存中
  2. 基于大小的回收策略
  3. 基于时间的回收策略
  4. 自动刷新
  5. key自动封装虚引用
  6. value自动封装弱引用或软引用
  7. 实体过期或被删除的通知
  8. 写入外部资源
  9. 统计累计访问缓存

加载策略

Caffeine提供了3种加载策略:手动加载,同步加载,异步加载

手动加载

1
2
3
4
5
6
7
8
9
10
11
12
Cache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 检索一个entry,如果没有则为null
Graph graph = cache.getIfPresent(key);
// 检索一个entry,如果entry为null,则通过key创建一个entry并加入缓存
graph = cache.get(key, k -> createExpensiveGraph(key));
// 插入或更新一个实体
cache.put(key, graph);
// 移除一个实体
cache.invalidate(key);

同步加载

构造Cache时候,build方法传入一个CacheLoader实现类。实现load方法,通过key加载value。

1
2
3
4
5
6
7
8
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
//如果缓存种没有对应的value,通过createExpensiveGraph方法加载
Graph graph = cache.get(key);

Map<Key, Graph> graphs = cache.getAll(keys);

异步加载

1
2
3
4
5
6
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
CompletableFuture<Graph> graph = cache.get(key);
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

AsyncLoadingCache 是 LoadingCache 的变体, 可以异步计算实体在一个线程池(Executor)上并且返回 CompletableFuture.

回收策略

Caffeine提供了3种回收策略:基于大小回收,基于时间回收,基于引用回收

基于大小回收

1
2
3
4
5
6
7
8
9
10
// 基于实体数量淘汰实体
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> createExpensiveGraph(key));

// 通过权重来计算,每个实体都有不同的权重,总权重到达最高时淘汰实体。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((Key key, Graph graph) -> graph.vertices().size())
.build(key -> createExpensiveGraph(key));

到达最大大小时淘汰最近最少使用的实体

基于时间回收

  1. 实体被访问之后,在实体被读或被写后的一段时间后过期

    1
    2
    3
    LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterAccess(5, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));
  2. 基于写之后,在实体被写入后的一段时间后过期

    1
    2
    3
    LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));
  3. 自定义策略Expiry,可以自定义在实体被读,被更新,被创建后的时间过期。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, Graph>() {
    public long expireAfterCreate(Key key, Graph graph, long currentTime) {
    // Use wall clock time, rather than nanotime, if from an external resource
    long seconds = graph.creationDate().plusHours(5)
    .minus(System.currentTimeMillis(), MILLIS)
    .toEpochSecond();
    return TimeUnit.SECONDS.toNanos(seconds);
    }
    public long expireAfterUpdate(Key key, Graph graph,
    long currentTime, long currentDuration) {
    return currentDuration;
    }
    public long expireAfterRead(Key key, Graph graph,
    long currentTime, long currentDuration) {
    return currentDuration;
    }
    })
    .build(key -> createExpensiveGraph(key));

基于引用回收

java种有四种引用:强引用,软引用,弱引用和虚引用,caffeine可以将值封装成弱引用或软引用。
软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

1
2
3
4
5
6
7
8
9
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> createExpensiveGraph(key));


LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.softValues()
.build(key -> createExpensiveGraph(key));

自动刷新

1
2
3
4
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));

在写后的持续时间过后,调用createExpensiveGraph刷新

移除通知

1
2
3
4
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.removalListener((Key key, Graph graph, RemovalCause cause) ->
System.out.printf("Key %s was removed (%s)%n", key, cause))
.build();

通过removalListener添加实体移除监听器

写到外部存储

通过CacheWriter 可以将缓存回写的外部存储中。

1
2
3
4
5
6
7
8
9
10
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.writer(new CacheWriter<Key, Graph>() {
@Override public void write(Key key, Graph graph) {
// 写入到外部存储或二级缓存
}
@Override public void delete(Key key, Graph graph, RemovalCause cause) {
// 删除外部存储或者二级缓存
}
})
.build(key -> createExpensiveGraph(key));

使用场景

  1. 缓存同步数据库
  2. 多级缓存同步

注意,CacheWriter不能与弱键或AsyncLoadingCache一起使用

统计缓存使用情况

1
2
3
4
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()
.build();

通过使用Caffeine.recordStats(), 可以转化成一个统计的集合. 通过 Cache.stats() 返回一个CacheStats。CacheStats提供以下统计方法

hitRate(): 返回缓存命中率
evictionCount(): 缓存回收数量
averageLoadPenalty(): 加载新值的平均时间

protobuf

发表于 2018-09-28 | 分类于 编解码 , jprotobuf

maven导包

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf</artifactId>
<version>2.2.0</version>
</dependency>

注解

@Protobuf 修饰字段
@ProtobufClass 修饰类,支持默认字段识别能力。相等于所有字段都用@Protobuf修饰了。

使用jprotobuf API进行序列化与反序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Codec<SimpleTypeTest> simpleTypeCodec = ProtobufProxy
.create(SimpleTypeTest.class);

SimpleTypeTest stt = new SimpleTypeTest();
stt.name = "abc";
stt.setValue(100);
try {
// 序列化
byte[] bb = simpleTypeCodec.encode(stt);
// 反序列化
SimpleTypeTest newStt = simpleTypeCodec.decode(bb);
} catch (IOException e) {
e.printStackTrace();
}

由注解对象动态生成Protobuf的IDL描述文件内容

JProtobuf提供一个非常实用的功能,可以动态生成Protobuf的IDL描述文件内容

//返回的内容即为 Protobuf的IDL描述文件
String code = ProtobufIDLGenerator.getIDL(SimpleTypeTest.class);

Spring IOC

发表于 2018-09-27 | 分类于 Spring

IOC容器

IOC简介

控制反转(IoC)也称为依赖注入(DI)。IOC指的是对象通过构造函数参数,工厂方法的参数,对象实例上设置的属性来定义它们的依赖关系,然后容器在创建bean时注入这些依赖项的过程。这个过程是基础bean的控制反转,通过使用类的直接构造或诸如服务定位器模式之类的机制来控制bean自身的实例化和依赖项的位置。

org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是BeanFactory的子接口。它增加了新的功能如:

  1. 更容易与Spring的AOP功能集成
  2. 消息资源处理(用于国际化)
  3. 事件发布
  4. 应用程序层特殊的上下文,例如用于Web应用程序的WebApplicationContext 。

简而言之,BeanFactory提供了配置框架和基本功能。ApplicationContext添加了更多企业特定的功能。有关使用BeanFactory的更多,看到 的BeanFactory更多信息。

在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化,组装和管理的对象。bean只是应用程序中众多对象之一。IOC容器使用的配置元数据描述了bean及其之间的依赖关系。

ApplicationContext

ApplicationContextSpring提供了几种接口实现。在独立应用程序中,通常会创建一个ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext的实例 。虽然XML是定义配置元数据的传统格式,但您可以通过提供少量XML配置来声明容器而使用Java注释或代码作为元数据格式。以XML声明方式启用对这些其他元数据格式的支持。

初始化容器

ClassPathXmlApplicationContext,顾名思义,从CLASSPATH路径加载配置元数据。

1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

使用applicationContextn获取bean

ApplicationContext是高级工厂的接口,能够维护不同bean及其依赖项的注册表。通过使用该方法T getBean(String name, Class requiredType),检索Bean的实例。

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
PetStoreService service = context.getBean("petStore", PetStoreService.class);
List<String> userList = service.getUsernameList();

GenericApplicationContext

最灵活的变体是GenericApplicationContext与Reader委托相结合,通过Reader的不同可以读取不同的配置文件 - 例如,XmlBeanDefinitionReader对于XML文件,如以下示例所示:

1
2
3
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

您还可以使用GroovyBeanDefinitionReaderfor Groovy文件,如以下示例所示:

1
2
3
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

您可以ApplicationContext在不同的配置源中读取和匹配此类读取器委托,读取bean定义。

Spring 配置元数据

Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML 定义的形式 )。在容器内,这些bean定义表示为BeanDefinition 对象,其中包含以下元数据(以及其他信息):

  1. 包限定类名。
  2. bean的行为(Scope,生命周期)
  3. 依赖项
  4. 属性

元数据由以下属性组成
属性 | 意义
– |–
Class | 实现类名
Name | bean的标识符
Scope | bean的范围,在范围内的bean是单例的。默认singleton,全局单例。prototype,多例。
Constructor arguments | 通过构造函数依赖注入
Properties| 通过属性依赖注入
Autowiring mode| 注入类型,byName,ByType等
Lazy initialization mode| 懒加载
Initialization method| 初始化回调方法
Destruction method| 销毁回调方法

有关在Spring容器中使用其他形式的元数据的信息,请参阅:

  1. 基于XML的配置:定义配置元数据的传统格式
  2. 基于注解的配置:Spring 2.5引入了对基于注解的配置元数据的支持。
  3. 基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为核心Spring Framework的一部分。因此,您可以使用Java而不是XML文件在应用程序类外部定义bean。要使用这些新功能,要借助 @Configuration, @Bean, @Import,和@DependsOn注释。
  4. 基于Groovy DSL的配置:从Spring 4.0开始支持Groovy DSL配置bean。

Spring配置由容器必须管理的至少一个且通常不止一个bean定义组成。基于XML的配置元数据将这些bean配置为顶级元素内的元素
以下示例显示了基于XML的配置元数据的基本结构:

基于XML的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.xsd">

<bean id="..." class="...">
<!-- bean的依赖和配置写在这 -->
</bean>

<bean id="..." class="...">
<!-- bean的依赖和配置写在这 -->
</bean>
</beans>

name配置

每个bean都有一个或多个标识符。这些标识符在托管bean的容器中必须是唯一的。bean通常只有一个标识符。但是,如果它需要多个,则额外的可以被视为别名。可以使用id属性,name属性或两者来指定bean标识符。

1
2
<alias name="person" alias="p"/>
<bean name="person" id="person" class="com.qworldr.test.Person"/>

Scope配置

Scope配置特定bean定义创建的对象的范围
Scope |Description
–|–
singleton|(默认) 一个bean定义在一个IOC容器中只有一个对象实例
prototype|一个bean定义在一个IOC容器中有多个对象实例
request|一个bean定义在每次Request请求都有一个对象实例
session|一个bean定义在每次Http会话都有一个对象实例
application|一个bean定义一个web应用有一个对象实例
websocket|一个bean定义一个webSocket有一个对象实例

1
<bean  id="player" class="com.qworldr.mmorpg.logic.player.Player" scope="prototype"/>

自动注入配置

模式 说明
no (默认)无自动装配。Bean引用必须由ref元素定义。
byName 按属性名称自动装配。Spring查找与需要自动装配的属性同名的bean。例如,如果bean定义设置autowire为byName并且它包含一个master属性(即,它有一个 setMaster(..)方法),则Spring会查找名为master的bean的定义并使用它来设置属性。
byType 如果容器中只存在一个属性类型的bean,则允许自动装配属性。如果存在多个,则抛出致命异常,这表示您可能不会byType对该bean 使用自动装配。如果没有匹配的bean,则不会发生任何事情(未设置该属性)。
constructor 类似byType但适用于构造函数参数。如果容器中没有构造函数参数类型的一个bean,则会引发致命错误。

会将Spring中定义的bean注入到Player中相同名字的属性中。

1
2
<bean  id="player" class="com.qworldr.mmorpg.logic.player.Player" 
autowire="byName"/>

懒加载

默认情况下,ApplicationContext实现会在启动时创建和配置所有 单例 bean作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误会被立即发现的,而不是几小时甚至几天后。如果不希望出现这种情况,可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时创建。

1
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

等同下面注解配置

1
2
3
4
5
@Lazy
@Component
public class ExpensiveToCreateBean{

}

初始化和销毁回调

可以通过以下三种方法配置bean的初始化和销毁回调方法

  1. 在InitializingBean和 DisposableBean回调接口
  2. xml配置init-method和 destroy-method
  3. @PostConstruct和@PreDestroy 注释
    1
    <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init" destroy-method="cleanup" />

可以在beans标签上配置默认的初始化和销毁回调方法

1
<beans default-destroy-method="destroy" default-init-method="init" ></beans>

依赖

使用构造器注入

通过参数的顺序
1
2
3
4
5
6
7
8
<bean id="person" class="com.qworldr.bean.Person" />
<constructor-arg index="0">
<value>张三</value>
</constructor-arg>
<constructor-arg index="1">
<value>56</value>
</constructor-arg>
</bean>
通过参数的类型
1
2
3
4
5
6
7
8
<bean id="person" class="com.qworldr.bean.Person" />
<constructor-arg type="java.lang.Integer">
<value>56</value>
</constructor-arg>
<constructor-arg type="java.lang.String">
<value>张三</value>
</constructor-arg>
</bean>

使用属性setting方法进行注入

简单Bean的注入
简单Bean包括两种类型:包装类型和String

1
2
3
4
5
<bean id="person" class="com.qworldr.bean.Person" />
<!-- 基本类型,string类型-->
<property name="age" value="20"></property>
<property name="name" value="张无忌"></property>
</bean>

引用其他Bean

1
2
3
<bean id="personService"  class="com.qworldr.bean.impl.PersonServiceImpl" />
<property name="person" ref="person" />
</bean>

注入集合

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
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>

空值和空字符串注入

空字符串

1
2
3
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>

空值

1
2
3
4
5
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>

depends-on

如果bean是另一个bean的依赖项,那通常意味着将一个bean设置为另一个bean的属性。但是,有时bean之间的依赖关系不那么直接。例如,需要触发类中的静态初始化程序,例如数据库驱动程序注册。depends-on在初始化使用此元素的bean之前,该属性可以显式强制初始化一个或多个bean。以下示例使用该depends-on属性表示对单个bean的依赖关系:

1
2
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

要表示对多个bean的依赖关系,depends-on属性的值可以用逗号,空格和分号分隔

继承关系

abstract=true的bean不能单独实例化,只能做为父类,被子类通过parent属性引用。

1
2
3
4
5
6
7
8
9
10
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

基于注解的配置

包扫描

注解配置首先要开启包扫描.扫描包范围的类,对带注解@Component的类进行处理,注册进Spring。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>

使用context:component-scan隐式启用功能 context:annotation-config

过滤器

可以通过include-filter和exclude-filter添加或过滤扫描的类。

1
2
3
4
5
6
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

过滤的类型
“annotation” 根据组件的注解过滤
“assignable” 根据组件的类型过滤(包括父类或接口类型)
“aspectj” 根据切面表达式过滤
“regex” 根据正则匹配类名
“custom” 自定义实现 org.springframework.core.type.TypeFilter interface.

命名生成

为扫描的组件生成标识符,可以实现 BeanNameGenerator 接口来实现自定义的命名规则。

1
2
3
4
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>

范围解析器

Spring提供6种Scope(singleton,prototype,4种web环境的范围),可以实现该 ScopeMetadataResolver 接口,来实现自定义的Scope范围解析器,通过xml配置解析器

1
2
3
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

使用某些非单例范围时,可能需要为范围对象生成代理。为此,组件扫描元素上提供了scoped-proxy属性。三个可能的值是:no,interfaces,和targetClass。这是代理的模式,interfaces要求类必须由接口,用的时jdk的动态代理。targetClass时cglib的动态代理。

1
2
3
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

例子:userManager是singleton,所有userManager仅被初始化一次,并且其属性userPreferences也仅被注射一次。当session失效后,userManager仍将保留userPreferences实例,这时userPreferences的范围不符合session。但是如果userPerfereneces加上aop:scoped-proxy/,userManager的属性userPreferences指向的是com.foo.UserPreferences实例的代理,当session过期后,userManager的属性userPreferences自然也不能再使用。

1
2
3
4
5
6
7
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>

注解方式配置包扫描

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class, scopeResolver = MyScopeResolver.class, scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
...
}

注解方式注册bean定义

@Component,@Service,和 @Controller。@Component是任何Spring管理组件的通用注解。 @Repository,@Service和,@Controller是@Component更具体的用例(分别在持久性,服务和表示层),都是和@Component组合的注解

1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";

}

包扫描时,会扫描加了这些注解的类,将类定义注册进入Spring中。

Scope注解

@Scope注解配置特定bean定义创建的对象的范围,同xml的Scope配置一样

1
2
3
4
5
@Scope("prototype")
@Component
public class MovieFinderImpl implements MovieFinder {
// ...
}

注解注入

注解注入有以下3种

  1. @Autowired ,默认byType注入,可以和@Qualifier组合通过byName注入。
  2. @Inject ,默认byType注入,可以和@Named组合通过byName注入。
  3. @Resource,默认byName注入,失败会转为byType

@PostConstruct和@PreDestroy

  1. @PostConstruct bean的初始化方法
  2. @PreDestroy bean的销毁方法

JSR-330注解和Spring注解

对应注解功能一样
Spring | javax.inject.*
–|–
@Autowired|@Inject
@Component|@Named / @ManagedBean
@Scope(“singleton”)|@Singleton
@Qualifier |@Qualifier / @Named

基于java的配置

配置类@Configuration

带@Configuration的类表示其作用是用于配置,由Spring管理。@Configuration也是和@Component组合的注解,功能一样。@Bean用来修饰一个返回对象实例的方法,相等于xml的标签

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {

@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

AnnotationConfigApplicationContext启动

1
2
3
4
5
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

register方法注册配置类

1
2
3
4
5
6
7
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

@Bean使用

@Bean用来修饰一个返回对象实例的方法,相等于xml的标签

依赖注入

通过方法参数实现依赖注入

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {

@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}

通过方法内调用@Bean方法获取对象注入。
clientService1和clientService2都调用了clientDao,clientDao被调用2次,应该会创建2个clientDao对象。但是实际上Spring只会创建一个对象,他们获取到的clientDao是一样的。所有@Configuration类启动的都是CGLIB生成的子类。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域),有的话不会再创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class AppConfig {

@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}

@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}

@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}

生命周期回调

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
public class BeanOne {

public void init() {
// initialization logic
}
}

public class BeanTwo {

public void cleanup() {
// destruction logic
}
}

@Configuration
public class AppConfig {

@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}

@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}

Bean的范围

1
2
3
4
5
6
7
8
9
@Configuration
public class MyConfiguration {

@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}

@Import

和xml的标签功能一样,导入其他配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class ConfigA {

@Bean
public A a() {
return new A();
}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

@Bean
public B b() {
return new B();
}
}

scan扫描包

1
2
3
4
5
6
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}

Spring环境配置

profile区分生产环境和开发环境

通过@Profile注解标识配置文件的使用环境。@Profile也可以加载类上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class AppConfig {

@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}

@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

xml配置文件profile属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">

<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">

<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

激活配置文件

java代码激活配置文件

1
2
3
4
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(AppConfig.class);
ctx.refresh();

spring.profiles.active属性激活配置文件

spring.profiles.active属性声明性地激活配置文件,该属性可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml或甚至JNDI中的条目来指定

1
-Dspring.profiles.active="profile1,profile2"

默认配置环境

Environment.setDefaultProfiles() 设置默认环境。如果没有激活配置文件,dataSource则创建该配置文件。您可以将此视为一种为一个或多个bean提供默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。

PropertySource

SpringStandardEnvironment 配置有两个PropertySource对象 - 一个表示JVM系统属性集(System.getProperties()),另一个表示系统环境变量集(System.getenv())。

层次结构如下,最高优先级条目位于顶部:

  1. ServletConfig参数(如果适用 - 例如,在DispatcherServlet上下文的情况下)
  2. ServletContext参数(web.xml context-param条目)
  3. JNDI环境变量(java:comp/env/条目)
  4. JVM系统属性(-D命令行参数)
  5. JVM系统环境(操作系统环境变量)

@PropertySource

通过@PropertySource添加PropertySource 到Spring的Environment
app.properties

1
testbean.name=myTestBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

@Autowired
Environment env;

@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}

可以使用占位符${},值从已注册的属性中找

1
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")

ApplicationContext扩展功能

国际化MessageSource

ApplicationContext接口扩展了一个名为的接口MessageSource,提供了国际化(“i18n”)功能。
MessageSource方法

1
2
3
4
5
String getMessage(String code, Object[] args, String default, Locale loc):MessageSource用于从中检索消息的基本方法。如果未找到指定区域设置的消息,则使用默认消息。传入的任何参数都使用MessageFormat标准库提供的功能成为替换值。

String getMessage(String code, Object[] args, Locale loc):基本上与前一个方法相同,但有一点不同:无法指定默认消息。如果找不到该消息,则抛出NoSuchMessageException。

String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有属性也包装在一个名为MessageSourceResolvable的类中 ,您可以使用此方法。

当ApplicationContext被加载时,自动在Spring容器中搜索MessageSource的bean。bean名称必须是messageSource。如果找到这样的bean,则对前面方法的所有调用都被委托给这个MessageSource。如果未找到任何MessageSource,ApplicationContext尝试查找包含具有相同名称的bean的父级。如果存在,它将使用该bean作为MessageSource。如果 ApplicationContext找不到任何消息源,一个空的DelegatingMessageSource被实例化,以便能够接受对上面定义的方法的调用。

Spring提供了两种MessageSource实现方式,ResourceBundleMessageSource和 StaticMessageSource。两者都实现了HierarchicalMessageSource为了进行嵌套消息传递。StaticMessageSource提供了编程的方式向消息源添加消息,但很少使用。

ResourceBundleMessageSource使用例子

加载类路径下的format.properties,exceptions.properties和windows.properties

1
2
3
4
5
6
7
8
9
10
11
12
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>

1
2
3
4
5
# in format.properties
message=Alligators rock!

# in exceptions.properties
argument.required=The {0} argument is required.

使用MessageSource,所有ApplicationContext都是MessageSource的实现,因此可以转换为MessageSource接口

1
2
3
4
5
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}

输出结果

1
Alligators rock!

配置文件名可以加上语言后缀
format_en_GB.properties

1
2
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.

设置语言和参数

1
2
3
4
5
6
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}

输出结果

1
Ebagum lad, the 'userDao' argument is required, I say, required.

ReloadableResourceBundleMessageSource

Spring提供了一个 ReloadableResourceBundleMessageSource类替代ResourceBundleMessageSource。ReloadableResourceBundleMessageSource支持从任何Spring资源位置(不仅从类路径)读取文件,并支持属性文件的热重新加载(同时在其间有效地缓存它们)

1
2
3
4
5
6
7
8
9
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:message"/>
<property name="fallbackToSystemLocale" value="false"/>
<property name="defaultEncoding" value="UTF-8"/>
<!--默认缓存时间-1,永久缓存-->
<property name="cacheSeconds" value="1200"/>
<!--是否允许刷新,默认允许-->
<property name="concurrentRefresh" value="true">
</bean>

使用上和ResourceBundleMessageSource基本一致。

实现MessageSourceAware接口

MessageSourceAware会将MessageSource注入到bean中。

资源访问ResourceLoader

ApplicationContext实现了ResourceLoader接口,可以通过getResource方法加载Resource。Resource是对资源的描述,可以获取资源的流。通过ResourceLoaderAware接口可以获取ResourceLoader。
Resource可以透明方式从几乎任何位置获取资源,包括类路径,文件系统位置,可用标准URL描述的任何位置。

1
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

1
2
3
4
5
6
//类路径
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
//本地文件路径
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
//http网络路径
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");

在不加资源描述符的路径,路径取决于ApplicationContext的确切类型,如ClassPathXmlApplicationContext的描述符默认是classpath:

事件处理

ApplicationContext通过ApplicationEvent 类和ApplicationListener接口提供事件处理。如果将实现ApplicationListener接口的bean 部署到上下文中,则每次 ApplicationEvent将其发布到该ApplicationContextbean时,都会通知该bean。

内置事件

事件 说明
ContextRefreshedEvent ApplicationContext初始化或刷新时发布(例如,通过refresh()在ConfigurableApplicationContext接口上使用该方法)。这里,“初始化”意味着加载所有bean,post-processor beans 检测并激活,预先实例化单例,并ApplicationContext准备好使用该对象。只要上下文尚未关闭,只要所选择的ApplicationContext实际支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持 。
ContextStartedEvent ApplicationContext通过start()启动时发布。这里,“已启动”意味着所有Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未为自动启动配置的组件(例如,在初始化时尚未启动的组件)。
ContextStoppedEvent ApplicationContext通过stop()停止时发布。这里,“停止”意味着所有Lifecycle bean都会收到明确的停止信号。可以通过start()呼叫重新启动已停止的上下文 。
ContextClosedEvent ApplicationContext通过close()关闭时发布。这里,“关闭”意味着所有单例bean都被销毁。封闭的环境达到其寿命终结。它无法刷新或重新启动。
RequestHandledEvent 一个特定于Web的事件,告诉所有bean已经为HTTP请求提供服务。请求完成后发布此事件。此事件仅适用于使用Spring的Web应用程序。

自定义事件

  1. 继承ApplicationEvent 自定义事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
    super(source);
    this.address = address;
    this.content = content;
    }

    // accessor and other methods...
    }
  2. 实现ApplicationEventPublisherAware 接口获取ApplicationEventPublisher事件发布器
    ApplicationContext也是一个ApplicationEventPublisher。可以通过获取ApplicationContext发布事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
    this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
    this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
    if (blackList.contains(address)) {
    publisher.publishEvent(new BlackListEvent(this, address, content));
    return;
    }
    // send email...
    }
    }
  3. 事件监听者,触发事件后会回调事件监听者

1
2
3
4
5
6
7
8
9
10
11
12
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

private String notificationAddress;

public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}

public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notif bicationAddress...
}
}

注解方式设置监听器

通过@EventListener代替ApplicationListener接口

1
2
3
4
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

监听多个事件

1
2
3
4
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}

通过SpEL表达式过滤事件

1
2
3
4
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
Name Location Description Example
事件 root object 发生的事件 #root.event
参数数组 root object 方法参数(做为数组) #root.args[0]
参数名 调用方法的参数名 #blEvent or #a0

异步监听

1
2
3
4
5
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent 被一个单独的线程执行
}

异步监听需要配置executor才可以。
SimpleApplicationEventMulticaster.multicastEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
} else {
invokeListener(listener, event);
}
}
}

异步监听注意事项

  1. 事件监听器抛出异常,无法传播到调用者。

事件排序 @Order

入果需要在另一个监听器之前调用一个监听器,则可以将@Order 注释添加到方法声明中,如以下示例所示:

1
2
3
4
5
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

泛型事件监听

1
2
3
4
5
6
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}

由于类型擦除,仅当泛型参数在被触发的事件的类上定义(即 class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ })时,此方法才有效。

ScheduledThreadPoolExecutor源码解析

发表于 2018-09-25 | 分类于 多线程

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor和实现了ScheduledExecutorService接口。是用于执行延时任务和周期任务的线程池。

ScheduledThreadPoolExecutor的延时执行的实现是通过队列DelayedWorkQueue和ScheduledFutureTask来实现。

核心内部类

ScheduledFutureTask

ScheduledFutureTask用于封装定期任务和获取任务结果。

1
2
3
4
5
6
 ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}

ScheduledFutureTask的继承关系

ScheduledFutureTask的继承关系

看继承关系可以看到,父接口有一个Delayed接口。Delayed接口继承了Comparable接口。这两个方法非常重要。DelayedWorkQueue队列会根据compareTo的排序规则给队列元素排序,将执行时间早的任务放在队头。getDelay方法用于判断任务是否到了执行时间。下面是实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//还需要延迟多久
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
//按预定的执行时间排序
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

DelayedWorkQueue

用于存储延迟执行的任务的阻塞队列。内部用数组实现,初始容量16。容量不足时会扩容50%。
queue数组表示一个二叉堆。

当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆。 当父节点的键值
总是小于或等于任何一个子节点的键值时为最小堆

1
2
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];

入队

入队的任务是ScheduledFutureTask对象,会通过ScheduledFutureTask的compareTo进行任务的比较。将数组queue排列成最小堆。最早执行的任务(getDelay最小的)是根节点queue[0]。

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
 public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
//1. 判断队列容量是否大于等于数组容量,是则需要扩容
if (i >= queue.length)
//2. 扩容,每次扩容50%
grow();
//3. 队列容量+1
size = i + 1;
//4. 队列中还没有元素
if (i == 0) {
//5. 加入第一个元素
queue[0] = e;
setIndex(e, 0);
} else {
//6. 队列中已经有元素就需要进行排序。
siftUp(i, e);
}
//7. 如果队头元素等于新的元素e,说明e执行时间比队列中其他元素早,唤醒消费线程,消费线程判断元素e是否达到执行时间。
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}

数组queue是一个二叉堆,新加入的元素通过堆排序找到合适的位置插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 private void siftUp(int k, RunnableScheduledFuture<?> key) {
while (k > 0) {
//父节点
int parent = (k - 1) >>> 1;
RunnableScheduledFuture<?> e = queue[parent];
//如果比父节点大,确定位置
if (key.compareTo(e) >= 0)
break;
//如果比父节点小,和父节点交换位置,再和父节点的父节点比较。
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}

出队

queue数组表示二叉堆,queue[0]元素是根节点。判断根节点是否到了执行时间。

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
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
//1. 获取队头节点
RunnableScheduledFuture<?> first = queue[0];
//2. 队头为空,没元素。进行等待。
if (first == null)
available.await();
else {

long delay = first.getDelay(NANOSECONDS);
//3.判断队头节点是否到了执行时间
if (delay <= 0)
return finishPoll(first); //4. 返回队头节点,queue堆取掉头节点,进行调整

//4.队头节点没到执行时间,进入等待
first = null;

//这里是leader-follower模式的变种。为了减少不必要的等待。
//不是leader的线程会进行永久的等待直到被唤醒。leader线程只会等待到下个个延迟。
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}

ScheduledThreadPoolExecutor任务执行流程

任务提交

ScheduledThreadPoolExecutor的schedule方法很多,都差不多。以scheduleAtFiexedRate为例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
//1. 将任务封装成ScheduledFutureTask
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
//triggerTime方法会算出触发的具体时间,now()+initalDelay
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
//2.用outerTask保存当前任务。用于周期执行时,再次将任务加入队列。
sft.outerTask = t;
//3.延迟执行,将任务加入到DelayedWorkQueue队列中。
delayedExecute(t);
return t;
}

任务执行

执行任务时调用ThreadPoolExecutor的runWorker方法。

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
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//1.通过getTask方法从DelayedWorkQueue队列中拿出一个任务。
//没有达到执行时间的任务时,会阻塞
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//2. 调用任务的run方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}

ScheduledFutureTask的run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void run() {
//是否周期任务,period>0
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
//执行任务并重置任务状态
else if (ScheduledFutureTask.super.runAndReset()) {
//周期任务,设置下次执行的时间
setNextRunTime();
//再将outerTask(任务提交时将自身赋值给了outerTask)加入任务队列。
reExecutePeriodic(outerTask);
}
}

123…5

wujiazhen

42 日志
18 分类
27 标签
GitHub
© 2019 wujiazhen
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4