在之前的文章中,我们分析过SpringBoot内部的自动化配置原理和自动化配置注解开关原理。
我们先简单分析一下mybatis starter的编写,然后再编写自定义的starter。
mybatis中的autoconfigure模块中使用了一个叫做MybatisAutoConfiguration的自动化配置类。
这个MybatisAutoConfiguration需要在这些Condition条件下才会执行:
- @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })。需要SqlSessionFactory和SqlSessionFactoryBean在classpath中都存在
- @ConditionalOnBean(DataSource.class)。 spring factory中需要存在一个DataSource的bean
- @AutoConfigureAfter(DataSourceAutoConfiguration.class)。需要在DataSourceAutoConfiguration自动化配置之后进行配置,因为mybatis需要数据源的支持
同时在META-INF目录下有个spring.factories这个properties文件,而且它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这样才会被springboot加载:
1
2
3
|
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
|
有了这些东西之后,mybatis相关的配置会被自动加入到spring container中,只要在maven中加入starter即可:
1
2
3
4
5
|
<
dependency
>
<
groupId
>org.mybatis.spring.boot</
groupId
>
<
artifactId
>mybatis-spring-boot-starter</
artifactId
>
<
version
>1.1.1</
version
>
</
dependency
>
|
编写自定义的starter
接下来,我们来编写自定义的starter:log-starter。
这个starter内部定义了一个注解,使用这个注解修饰方法之后,该方法的调用会在日志中被打印并且还会打印出方法的耗时。starter支持exclude配置,在exclude中出现的方法不会进行计算。
pom文件:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
parent
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starters</
artifactId
>
<
version
>1.3.5.RELEASE</
version
>
</
parent
>
<
dependencies
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter</
artifactId
>
</
dependency
>
</
dependencies
>
|
定义修饰方法的注解@Log:
1
2
3
4
5
6
7
8
9
10
|
package
me.format.springboot.log.annotation;
import
java.lang.annotation.ElementType;
import
java.lang.annotation.Retention;
import
java.lang.annotation.RetentionPolicy;
import
java.lang.annotation.Target;
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.METHOD)
public
@interface
Log { }
|
然后是配置类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package
me.format.springboot.log.autoconfigure;
import
org.springframework.boot.context.properties.ConfigurationProperties;
import
org.springframework.util.StringUtils;
import
javax.annotation.PostConstruct;
@ConfigurationProperties
(prefix =
"mylog"
)
public
class
LogProperties {
private
String exclude;
private
String[] excludeArr;
@PostConstruct
public
void
init() {
this
.excludeArr = StringUtils.split(exclude,
","
);
}
public
String getExclude() {
return
exclude;
}
public
void
setExclude(String exclude) {
this
.exclude = exclude;
}
public
String[] getExcludeArr() {
return
excludeArr;
}
}
|
接下来是AutoConfiguration:
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
|
package
me.format.springboot.log.autoconfigure;
import
me.format.springboot.log.annotation.Log;
import
me.format.springboot.log.aop.LogMethodInterceptor;
import
org.aopalliance.aop.Advice;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
org.springframework.aop.Pointcut;
import
org.springframework.aop.support.AbstractPointcutAdvisor;
import
org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.context.properties.EnableConfigurationProperties;
import
org.springframework.context.annotation.Configuration;
import
javax.annotation.PostConstruct;
@Configuration
@EnableConfigurationProperties
(LogProperties.
class
)
public
class
LogAutoConfiguration
extends
AbstractPointcutAdvisor {
private
Logger logger = LoggerFactory.getLogger(LogAutoConfiguration.
class
);
private
Pointcut pointcut;
private
Advice advice;
@Autowired
private
LogProperties logProperties;
@PostConstruct
public
void
init() {
logger.info(
"init LogAutoConfiguration start"
);
this
.pointcut =
new
AnnotationMatchingPointcut(
null
, Log.
class
);
this
.advice =
new
LogMethodInterceptor(logProperties.getExcludeArr());
logger.info(
"init LogAutoConfiguration end"
);
}
@Override
public
Pointcut getPointcut() {
return
this
.pointcut;
}
@Override
public
Advice getAdvice() {
return
this
.advice;
}
}
|
由于计算方法调用的时候需要使用aop相关的lib,所以我们的AutoConfiguration继承了AbstractPointcutAdvisor。这样就有了Pointcut和Advice。Pointcut是一个支持注解的修饰方法的Pointcut,Advice则自己实现:
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
|
package
me.format.springboot.log.aop;
import
org.aopalliance.intercept.MethodInterceptor;
import
org.aopalliance.intercept.MethodInvocation;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
java.util.Arrays;
import
java.util.List;
public
class
LogMethodInterceptor
implements
MethodInterceptor {
private
Logger logger = LoggerFactory.getLogger(LogMethodInterceptor.
class
);
private
List<String> exclude;
public
LogMethodInterceptor(String[] exclude) {
this
.exclude = Arrays.asList(exclude);
}
@Override
public
Object invoke(MethodInvocation invocation)
throws
Throwable {
String methodName = invocation.getMethod().getName();
if
(exclude.contains(methodName)) {
return
invocation.proceed();
}
long
start = System.currentTimeMillis();
Object result = invocation.proceed();
long
end = System.currentTimeMillis();
logger.info(
"====method({}), cost({}) "
, methodName, (end - start));
return
result;
}
}
|
最后resources/META-INF/spring.factories中加入这个AutoConfiguration:
1
2
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
me.format.springboot.log.autoconfigure.LogAutoConfiguration
|
我们在项目中使用这个log-starter:
1
2
3
4
5
|
<
dependency
>
<
groupId
>me.format.springboot</
groupId
>
<
artifactId
>log-starter</
artifactId
>
<
version
>1.0-SNAPSHOT</
version
>
</
dependency
>
|
使用配置:
1
|
mylog.exclude=core,log
|
然后编写一个简单的Service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Service
public
class
SimpleService {
@Log
public
void
test(
int
num) {
System.out.println(
"----test---- "
+ num);
}
@Log
public
void
core(
int
num) {
System.out.println(
"----core---- "
+ num);
}
public
void
work(
int
num) {
System.out.println(
"----work---- "
+ num);
}
}
|
使用单元测试分别调用这3个方法,由于work方法没有加上@Log注解,core方法虽然加上了@Log注解,但是在配置中被exclude了,只有test方法可以正常计算耗时:
1
2
3
4
|
----test----
666
2016
-
11
-
16
01
:
29
:
32.255
INFO
41010
--- [ main] m.f.s.log.aop.LogMethodInterceptor : ====method(test), cost(
36
)
----work----
666
----core----
666
|
总结:
自定义springboot的starter,注意这两点。
- 如果自动化配置类需要在程序启动的时候就加载,可以在META-INF/spring.factories文件中定义。如果本次加载还需要其他一些lib的话,可以使用ConditionalOnClass注解协助
- 如果自动化配置类要在使用自定义注解后才加载,可以使用自定义注解+@Import注解或@ImportSelector注解完成
参考: