在之前的文章中,我們分析過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加載:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
有了這些東西之後,mybatis相關的配置會被自動加入到spring container中,只要在maven中加入starter即可:
<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文件:
<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:
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 { }
然後是配置類:
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:
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則自己實現:
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;
}
}
/<string>
最後resources/META-INF/spring.factories中加入這個AutoConfiguration:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
me.format.springboot.log.autoconfigure.LogAutoConfiguration
我們在項目中使用這個log-starter:
<dependency>
<groupid>me.format.springboot/<groupid>
<artifactid>log-starter/<artifactid>
<version>1.0-SNAPSHOT/<version>
/<dependency>
使用配置:
mylog.exclude=core,log
然後編寫一個簡單的Service:
@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方法可以正常計算耗時:
----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註解完成
閱讀更多 Hello123world 的文章