В докладе будут рассмотрены XML based, Annotation based и Java based подходы к конфигурированию Spring-приложения, их плюсы и минусы, а также различные возможности конфигурирования приложения, предоставляемые Spring Boot, Spring Cloud и, разумеется, самим Spring. Видео https://youtu.be/5RGM4LskJtA
2. www.luxoft.com
О чём доклад?
XML-based конфигурация:
- Почему так плохо получается?
- Что делать?
Annotation-based:
- Почему так плохо получается?
- Что делать?
Java-based конфигурация:
- Почему так плохо получается?
- Что делать?
Конфигурировние вместе со
Spring Boot.
Properties – куда их класть и
откуда брать?
Немного Spring Cloud.
2
4. www.luxoft.com
XML-based конфигурация (+)
Всем знакома.
Большая часть документации
и примеров с XML.
Централизованная
конфигурация.
Естественно уменьшает
связАнность Java-кода.
4
5. www.luxoft.com
XML-based конфигурация (–)
Огромные XML.
XML.
Необходимость изменять в
нескольких местах.
Тяжело рефакторить.
Не всегда работают Smart *
Ошибки в run-time.
5
6. www.luxoft.com
Почему так получается? Что делать?
Огромные XML:
- <import /> – позволяет из одной
большой XML сделать несколько
маленьких.
- Делить приложение на модули.
<import resource=
"classpath*:context.xml"
/>
6
7. www.luxoft.com
Почему так получается? Что делать?
Огромные XML - используйте
расширения:
- util
- mvc
- tx
- security
- context
<util:properties
location="my.properties"
/>
7
8. www.luxoft.com
Почему так получается? Что делать?
Огромные XML - используйте
Spring Expression Language:
- для выражений, касающихся
конфигурации;
- не для бизнес-логики.
<bean id="bean1"></bean>
<bean>
<property ref="bean1"/>
</bean>
<!-- SpEL -->
<property
value="#{new My()}"/>
8
9. www.luxoft.com
Почему так получается? Что делать?
Огромные XML?
Много связей?
Хочется auto-wiring?
<bean id="bean1">
...
</bean>
<bean autowire="byType">
</bean>
9
10. www.luxoft.com
Почему так получается? Что делать?
Ошибки в run-time (run-time бывает разный)
- Используйте, по возможности,
<constructor-arg> вместо <property>.
- Поднимается ли контекст || часть контекста, можно протестировать
в Unit-тестах.
10
12. www.luxoft.com
Annotation-based конфигурация
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
Активно используется
@Autowired
Стереотипы:
- @Controller / @RestController
- @Service, ...
@Value("${my.property}")
12
13. www.luxoft.com
Annotation-based конфигурация (+)
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
Никакого XML (или почти).
Изменения в одном месте.
Меньше Java-кода.
Рефакторинг стал проще.
Некоторые run-time ошибки
просто исчезли.
spring-mvc, spring-* так и
используют.
13
14. www.luxoft.com
Annotation-based конфигурация (–)
14
Иногда нужно несколько
бинов одного класса.
С @Autowired полями
тяжелее пишутся Unit-тесты.
Добавить зависимость –
элементарно.
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
15. www.luxoft.com
Annotation-based конфигурация
Ожидание
@Service
class SmallService {
@Autowired
MyAwesomeDao dao;
public void update() {
dao.updateAll();
}
}
Реальность
public class ApprovedSiteAPI {
public static final String URL = RootController.API_URL + "/approved";
@Autowired
private ApprovedSiteService approvedSiteService;
@Autowired
private OAuth2TokenEntitySer vice tokenServices;
@Autowired
private OAuth2TokenEntitySer vice tokenServices;
@Autowired
private ClientDetailsEntitySer vice clientService;
@Autowired
private IntrospectionResultAssembler introspectionResultAssembler;
@Autowired
private UserInfoService userInfoService;
@Autowired
private ResourceSetService resourceSetService;
/**
* Delete an approved site
*
*/
@RequestMapping(value="/{id}", method = RequestMethod.DELETE)
public String deleteApprovedSite(@PathVari able("i d") Long id, ModelMap m, Principal p) {
ApprovedSite approvedSite = approvedSiteService.getById(id);
if (approvedSite == null) {
logger.error("deleteApprovedSite failed; no approved site found for id: " + id);
m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
m.put(JsonErrorView.ER ROR_MESSAGE, "Could not delete approved site. The requested approved site with id: " + id + " could not be found.");
return JsonErrorView.VIEWNAM E;
} else if (!approvedSite.getUserId().equals(p.getName())) {
logger.error("deleteApprovedSite failed; principal "
+ p.getName() + " does not own approved site" + id);
m.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
m.put(JsonErrorView.ER ROR_MESSAGE, "You do not have permission to delete this approved site. The approved site decision will not be deleted.");
return JsonErrorView.VIEWNAM E;
} else {
m.put(HttpCodeView.CODE, HttpStatus.OK);
approvedSiteService.remove(approvedSite);
}
return HttpCodeView.VIEWNAME;
}
@Override
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
if (authentication != null && authentication.getOAuth2Request() != null) {
// look up our client
OAuth2Request clientAuth = authentication.getOAuth2Request();
ClientDetailsEntity client = clientDetailsService.loadClientByCli entId(cl ientAuth.getClientId() );
if (client == null) {
throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
}
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();//accessTokenFactor y.createNewAccessToken();
// attach the client
token.setClient(client);
// inherit the scope from the auth, but make a new set so it is
//not unmodifiable. Unmodifiables don't play nicely with Eclipselink, which
//wants to use the clone operation.
Set<SystemScope> scopes = scopeService.fromStrings(clientAuth.getScope( ));
// remove any of the special system scopes
scopes = scopeService.removeReser vedScopes( scopes);
token.setScope(scopeSer vice.toStri ngs(scopes));
// make it expire if necessary
if (client.getAccessTokenValiditySeconds( ) != null && client.getAccessTokenValiditySeconds() > 0) {
Date expiration = new Date(System.currentTi meMillis() + (client.getAccessTokenValiditySeconds() * 1000L));
token.setExpiration(expiration);
}
// attach the authorization so that we can look it up later
AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity();
authHolder.setAuthentication(authentication);
authHolder = authenticationHolderRepositor y.save(authHolder);
token.setAuthenticationHolder(authHolder) ;
// attach a refresh token, if this client is allowed to request them and the user gets the offline scope
if (client.isAllowRefresh() && token.getScope().contains(SystemScopeSer vice.OFFLINE_ACCESS)) {
OAuth2RefreshTokenEntity savedRefreshToken = createRefreshToken(client, authHolder);
token.setRefreshToken(savedRefreshToken);
}
OAuth2AccessTokenEntity enhancedToken = (OAuth2AccessTokenEntity) tokenEnhancer.enhance(token, authentication);
OAuth2AccessTokenEntity savedToken = tokenRepository.saveAccessToken( enhancedToken);
//Add approved site reference, if any
OAuth2Request originalAuthRequest = authHolder.getAuthentication().getOAuth2Request();
if (originalAuthRequest.getExtensions() != null && originalAuthRequest.getExtensions().containsKey("approved_site")) {
Long apId = Long.parseLong((String) originalAuthRequest.getExtensions().get("approved_site"));
ApprovedSite ap = approvedSiteService.getById(apId);
Set<OAuth2AccessTokenEntity> apTokens = ap.getApprovedAccessTokens();
apTokens.add(savedToken);
ap.setApprovedAccessTokens(apTokens);
approvedSiteService.save(ap);
}
if (savedToken.getRefreshToken() != null) {
tokenRepository.saveRefr eshToken(savedToken.getRefreshToken()) ; // make sure we save any changes that might have been enhanced
}
return savedToken;
}
throw new AuthenticationCredentialsNotFoundException("No authentication credentials found");
}
private OAuth2RefreshTokenEntity createRefreshToken(ClientDetailsEntity client, AuthenticationHolderEntity authHolder) {
OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity(); //refreshTokenFactory.createNewRefreshToken();
JWTClaimsSet.Builder refreshClaims = new JWTClaimsSet.Builder();
// make it expire if necessary
if (client.getRefreshTokenValiditySeconds() != null) {
Date expiration = new Date(System.currentTi meMillis() + (client.getRefreshTokenValiditySeconds() * 1000L));
refreshToken.setExpi ration( expirati on);
refreshClaims.expirationTi me(expi ration);
}
// set a random identifier
refreshClaims.jwtID(U UID.randomU UID().toString());
// TODO: add issuer fields, signature to JWT
PlainJWT refreshJwt = new PlainJWT(refreshClai ms.buil d());
refreshToken.setJwt(r efreshJwt);
//Add the authentication
refreshToken.setAuthenticationHolder(authH older);
refreshToken.setClient(client) ;
// save the token first so that we can set it to a member of the access token (NOTE: is this step necessary?)
OAuth2RefreshTokenEntity savedRefreshToken = tokenRepository.saveRefreshToken(refreshToken);
return savedRefreshToken;
}
@Override
public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException {
OAuth2RefreshTokenEntity refreshToken = clearExpiredRefreshToken(tokenRepositor y.getRefreshTokenByValue( refreshTokenValue));
if (refreshToken == null) {
throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
}
ClientDetailsEntity client = refreshToken.getClient();
AuthenticationHolderEntity authHolder = refreshToken.getAuthenticationHolder() ;
// make sure that the client requesting the token is the one who owns the refresh token
ClientDetailsEntity requestingClient = clientDetailsService.loadClientByClientId(authR equest.getClientId());
if (!client.getClientId().equals(requestingClient.getClientId())) {
tokenRepository.removeRefreshToken(refreshToken);
throw new InvalidClientException("Client does not own the presented refresh token");
}
//Make sure this client allows access token refreshing
if (!client.isAllowRefresh()) {
throw new InvalidClientException("Client does not allow refreshing access token!");
}
// clear out any access tokens
if (client.isClearAccessTokensOnRefresh()) {
tokenRepository.clearAccessTokensForR efreshToken(r efreshToken);
}
if (refreshToken.isExpi red()) {
tokenRepository.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Expired refresh token: " + refreshTokenValue);
}
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
// get the stored scopes from the authentication holder's authorization request; these are the scopes associated with the refresh token
Set<String> refreshScopesRequested = new HashSet<>(refreshToken.getAuthenticati onHolder().getAuthentication( ).getOAuth2R equest().getScope());
Set<SystemScope> refreshScopes = scopeService.fromStrings(refreshScopesRequested);
// remove any of the special system scopes
refreshScopes = scopeService.removeReser vedScopes(r efreshScopes) ;
Set<String> scopeRequested = authRequest.getScope() == null ? new HashSet<String>() : new HashSet<>(authRequest.getScope());
Set<SystemScope> scope = scopeService.fromStr ings(scopeRequested);
// remove any of the special system scopes
scope = scopeService.removeReser vedScopes(scope);
if (scope != null && !scope.isEmpty()) {
// ensure a proper subset of scopes
if (refreshScopes != null && refreshScopes.containsAll(scope)) {
// set the scope of the new access token if requested
token.setScope(scopeService.toStrings(scope)) ;
} else {
String errorMsg = "Up-scoping is not allowed.";
logger.error(errorMsg);
throw new InvalidScopeException(error Msg);
}
} else {
// otherwise inherit the scope of the refresh token (if it's there -- this can return a null scope set)
token.setScope(scopeSer vice.toStri ngs(refreshScopes));
}
15
16. www.luxoft.com
Почему так получается? Что делать?
16
Сильная связАнность,
большие классы?
- Нужно лучше писать код.
- Unit-тесты помогут выявить
проблемы.
- Используйте @Autowired на
конструкторах, сеттерах,
методах.
public class ApprovedSiteAPI {
public static final String URL = RootController.API_URL + "/approved";
@Autowired
private ApprovedSiteService approvedSiteService;
@Autowired
private OAuth2TokenEntitySer vice tokenServices;
@Autowired
private OAuth2TokenEntitySer vice tokenServices;
@Autowired
private ClientDetailsEntitySer vice clientService;
@Autowired
private IntrospectionResultAssembler introspectionResultAssembler;
@Autowired
private UserInfoService userInfoService;
@Autowired
private ResourceSetService resourceSetService;
/**
* Delete an approved site
*
*/
@RequestMapping(value="/{id}", method = RequestMethod.DELETE)
public String deleteApprovedSite(@PathVari able("i d") Long id, ModelMap m, Principal p) {
ApprovedSite approvedSite = approvedSiteService.getById(id);
if (approvedSite == null) {
logger.error("deleteApprovedSite failed; no approved site found for id: " + id);
m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
m.put(JsonErrorView.ER ROR_MESSAGE, "Could not delete approved site. The requested approved site with id: " + id + " could not be found.");
return JsonErrorView.VIEWNAM E;
} else if (!approvedSite.getUserId().equals(p.getName())) {
logger.error("deleteApprovedSite failed; principal "
+ p.getName() + " does not own approved site" + id);
m.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
m.put(JsonErrorView.ER ROR_MESSAGE, "You do not have permission to delete this approved site. The approved site decision will not be deleted.");
return JsonErrorView.VIEWNAM E;
} else {
m.put(HttpCodeView.CODE, HttpStatus.OK);
approvedSiteService.remove(approvedSite);
}
return HttpCodeView.VIEWNAME;
}
@Override
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
if (authentication != null && authentication.getOAuth2Request() != null) {
// look up our client
OAuth2Request clientAuth = authentication.getOAuth2Request();
ClientDetailsEntity client = clientDetailsService.loadClientByCli entId(cl ientAuth.getClientId() );
if (client == null) {
throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
}
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();//accessTokenFactor y.createNewAccessToken();
// attach the client
token.setClient(client);
// inherit the scope from the auth, but make a new set so it is
//not unmodifiable. Unmodifiables don't play nicely with Eclipselink, which
//wants to use the clone operation.
Set<SystemScope> scopes = scopeService.fromStrings(clientAuth.getScope( ));
// remove any of the special system scopes
scopes = scopeService.removeReser vedScopes( scopes);
token.setScope(scopeSer vice.toStri ngs(scopes));
// make it expire if necessary
if (client.getAccessTokenValiditySeconds( ) != null && client.getAccessTokenValiditySeconds() > 0) {
Date expiration = new Date(System.currentTi meMillis() + (client.getAccessTokenValiditySeconds() * 1000L));
token.setExpiration(expiration);
}
// attach the authorization so that we can look it up later
AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity();
authHolder.setAuthentication(authentication);
authHolder = authenticationHolderRepositor y.save(authHolder);
token.setAuthenticationHolder(authHolder) ;
// attach a refresh token, if this client is allowed to request them and the user gets the offline scope
if (client.isAllowRefresh() && token.getScope().contains(SystemScopeSer vice.OFFLINE_ACCESS)) {
OAuth2RefreshTokenEntity savedRefreshToken = createRefreshToken(client, authHolder);
token.setRefreshToken(savedRefreshToken);
}
OAuth2AccessTokenEntity enhancedToken = (OAuth2AccessTokenEntity) tokenEnhancer.enhance(token, authentication);
OAuth2AccessTokenEntity savedToken = tokenRepository.saveAccessToken( enhancedToken);
//Add approved site reference, if any
OAuth2Request originalAuthRequest = authHolder.getAuthentication().getOAuth2Request();
if (originalAuthRequest.getExtensions() != null && originalAuthRequest.getExtensions().containsKey("approved_site")) {
Long apId = Long.parseLong((String) originalAuthRequest.getExtensions().get("approved_site"));
ApprovedSite ap = approvedSiteService.getById(apId);
Set<OAuth2AccessTokenEntity> apTokens = ap.getApprovedAccessTokens();
apTokens.add(savedToken);
ap.setApprovedAccessTokens(apTokens);
approvedSiteService.save(ap);
}
if (savedToken.getRefreshToken() != null) {
tokenRepository.saveRefr eshToken(savedToken.getRefreshToken()) ; // make sure we save any changes that might have been enhanced
}
return savedToken;
}
throw new AuthenticationCredentialsNotFoundException("No authentication credentials found");
}
private OAuth2RefreshTokenEntity createRefreshToken(ClientDetailsEntity client, AuthenticationHolderEntity authHolder) {
OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity(); //refreshTokenFactory.createNewRefreshToken();
JWTClaimsSet.Builder refreshClaims = new JWTClaimsSet.Builder();
// make it expire if necessary
if (client.getRefreshTokenValiditySeconds() != null) {
Date expiration = new Date(System.currentTi meMillis() + (client.getRefreshTokenValiditySeconds() * 1000L));
refreshToken.setExpi ration( expirati on);
refreshClaims.expirationTi me(expi ration);
}
// set a random identifier
refreshClaims.jwtID(U UID.randomU UID().toString());
// TODO: add issuer fields, signature to JWT
PlainJWT refreshJwt = new PlainJWT(refreshClai ms.buil d());
refreshToken.setJwt(r efreshJwt);
//Add the authentication
refreshToken.setAuthenticationHolder(authH older);
refreshToken.setClient(client) ;
// save the token first so that we can set it to a member of the access token (NOTE: is this step necessary?)
OAuth2RefreshTokenEntity savedRefreshToken = tokenRepository.saveRefreshToken(refreshToken);
return savedRefreshToken;
}
@Override
public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException {
OAuth2RefreshTokenEntity refreshToken = clearExpiredRefreshToken(tokenRepositor y.getRefreshTokenByValue( refreshTokenValue));
if (refreshToken == null) {
throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
}
ClientDetailsEntity client = refreshToken.getClient();
AuthenticationHolderEntity authHolder = refreshToken.getAuthenticationHolder() ;
// make sure that the client requesting the token is the one who owns the refresh token
ClientDetailsEntity requestingClient = clientDetailsService.loadClientByClientId(authR equest.getClientId());
if (!client.getClientId().equals(requestingClient.getClientId())) {
tokenRepository.removeRefreshToken(refreshToken);
throw new InvalidClientException("Client does not own the presented refresh token");
}
//Make sure this client allows access token refreshing
if (!client.isAllowRefresh()) {
throw new InvalidClientException("Client does not allow refreshing access token!");
}
// clear out any access tokens
if (client.isClearAccessTokensOnRefresh()) {
tokenRepository.clearAccessTokensForR efreshToken(r efreshToken);
}
if (refreshToken.isExpi red()) {
tokenRepository.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Expired refresh token: " + refreshTokenValue);
}
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
// get the stored scopes from the authentication holder's authorization request; these are the scopes associated with the refresh token
Set<String> refreshScopesRequested = new HashSet<>(refreshToken.getAuthenticati onHolder().getAuthentication( ).getOAuth2R equest().getScope());
Set<SystemScope> refreshScopes = scopeService.fromStrings(refreshScopesRequested);
// remove any of the special system scopes
refreshScopes = scopeService.removeReser vedScopes(r efreshScopes) ;
Set<String> scopeRequested = authRequest.getScope() == null ? new HashSet<String>() : new HashSet<>(authRequest.getScope());
Set<SystemScope> scope = scopeService.fromStr ings(scopeRequested);
// remove any of the special system scopes
scope = scopeService.removeReser vedScopes(scope);
if (scope != null && !scope.isEmpty()) {
// ensure a proper subset of scopes
if (refreshScopes != null && refreshScopes.containsAll(scope)) {
// set the scope of the new access token if requested
token.setScope(scopeService.toStrings(scope)) ;
} else {
String errorMsg = "Up-scoping is not allowed.";
logger.error(errorMsg);
throw new InvalidScopeException(error Msg);
}
} else {
// otherwise inherit the scope of the refresh token (if it's there -- this can return a null scope set)
token.setScope(scopeSer vice.toStri ngs(refreshScopes));
}
token.setClient(client);
if (client.getAccessTokenValiditySeconds() != null) {
Date expiration = new Date(System.currentTi meMillis() + (client.getAccessTokenValiditySeconds( ) * 1000L));
token.setExpiration(expiration);
}
if (client.isReuseRefreshToken()) {
// if the client re-uses refresh tokens, do that
token.setRefreshToken(refr eshToken);
} else {
// otherwise, make a new refresh token
OAuth2RefreshTokenEntity newRefresh = createRefreshToken(client, authHolder);
token.setRefreshToken(newRefresh);
// clean up the old refresh token
tokenRepository.removeRefreshToken(refreshToken);
}
token.setAuthenticationHolder (authH older);
tokenEnhancer.enhance(token, authHolder.getAuthentication());
tokenRepository.saveAccessToken(token);
return token;
18. www.luxoft.com
Annotation-based конфигурация (–)
18
Spring в Domain:
- невозможно отказаться от
Spring;
- сложности при reuse;
- нарушение архитектуры.
Что делать?
- JSR-330;
- не делать так.
@Inject @Autowired
@Named @Component
http://docs.spring.io/spring/docs/current/spring-
framework-reference/htmlsingle/#beans-standard-
annotations
20. www.luxoft.com
А где файл c бинами контекста?
Непонятно куда размещать
чисто конфигурационный код.
С аннотациями @Component,
@Service проблематично
создать несколько бинов одного
класса.
Сторонние классы без этих
аннотаций.
Уменьшить связАннось.
20
21. www.luxoft.com
Java-based конфигурация
@Configuration
public class ApplicationConfig {
@Bean
public PersonService personService(){
return new PersonService("arg");
}
}
public PersonService {
public PersonService(String arg) {
// обычный конструктор
}
// обычные методы
}
Есть класс с конфигурацией
@Configuration.
@Bean помечены методы (!)
которые возвращают бины.
Фактически бины создаются
обычным Java-кодом.
21
22. www.luxoft.com
Java-based конфигурация (+)
@Configuration
public class ApplicationConfig {
@Bean
public PersonService personService(){
return new PersonService("arg");
}
}
public PersonService {
public PersonService(String arg) {
// обычный конструктор
}
// обычные методы
}
Никакого XML, только Java.
Явное создание и настройка.
Type-safe.
Некоторые ошибки стали
compile-time.
Обычный рефакторинг.
22
23. www.luxoft.com
Java-based конфигурация (+)
@Configuration
public class ApplicationConfig {
@Bean
public PersonService personService(){
return new PersonService("arg");
}
}
public PersonService {
public PersonService(String arg) {
// обычный конструктор
}
// обычные методы
}
Smart-подсказки работают.
Позволяют отделить Domain
от Spring.
Современно.
23
24. www.luxoft.com
Java-based конфигурация (–)
@Configuration
public class ApplicationConfig {
@Bean
public PersonService personService(){
return new PersonService("arg");
}
}
public PersonService {
public PersonService(String arg) {
// обычный конструктор
}
// обычные методы
}
Возможно смешение бизнес-
и конфигурационной логики.
Java-кода иногда нужно больше.
Требует некоторой дисциплины
для разарботчиков.
С Java-based конфигурации
проще начинать, чем на неё
переходить.
24
25. www.luxoft.com
Почему так получается? Что делать?
Смешение бизнес- и конфигурационной логики?
- Конфигурацию вынести в отдельный пакет/модуль.
- Договорённости, ревью.
Большие классы конфигурации?
- Используйте @Import: классы делятся проще и естественнее, чем XML.
- Начинайте со Spring Boot.
25
28. www.luxoft.com
Spring Boot
Предназначен для мексимально
быстрой разработки.
Сразу доступны общие features.
Автоматически конфигурирует
компоненты.
По-умолчанию использует Java-
based конфигурацию.
Возможности для тетсирования
всего микросервиса.
28
30. www.luxoft.com
Conditional
Если класс присутсвует в
classpath – тогда создаётся
бин.
Включается
@EnableAutoConfiguration
@Configuration
public class AppConfig {
@Bean
@Conditional(
MySQLDatabaseTypeCondition.class
)
public UserDAO jdbcUserDAO() {
return new JdbcUserDAO();
}
@Bean
@Conditional(
MongoDBDatabaseTypeCondition.class
)
public UserDAO mongoUserDAO() {
return new MongoUserDAO();
}
}
30
35. www.luxoft.com
Properties
35
Куда их класть:
- jar/war
- /var/lib/tomcat/conf
- /etc/luxoft/sampleapp/app.yaml
- в git-репозиторий?
- в системные свойства (можно туда
класть JSON)
- параметры запуска
- git!
Откуда брать?
- Из системых свойств
- Из пареметров запуска
- Из файла в широком сымсле
- С сервера в широком смысле
git
файл на сервере
Ваш собственный config server
37. www.luxoft.com
Properties
37
Жизнь с config-server
правим настройки
собираем новую версию
останавливаем каждую ноду
обновляем каждую ноды
запускаем заново
Жизнь с config-server
правим настройки (git)
перезапускаем config-server
перезапускаем ноды по одной