Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

DEV Labs 2016. Конфигурирование spring-приложений

232 views

Published on

В докладе будут рассмотрены XML based, Annotation based и Java based подходы к конфигурированию Spring-приложения, их плюсы и минусы, а также различные возможности конфигурирования приложения, предоставляемые Spring Boot, Spring Cloud и, разумеется, самим Spring. Видео https://youtu.be/5RGM4LskJtA

Published in: Technology
  • Be the first to comment

  • Be the first to like this

DEV Labs 2016. Конфигурирование spring-приложений

  1. 1. www.luxoft.com Конфигурирование Spring-приложений Дворжецкий Юрий 25 июня 2016
  2. 2. www.luxoft.com О чём доклад?  XML-based конфигурация: - Почему так плохо получается? - Что делать?  Annotation-based: - Почему так плохо получается? - Что делать?  Java-based конфигурация: - Почему так плохо получается? - Что делать?  Конфигурировние вместе со Spring Boot.  Properties – куда их класть и откуда брать?  Немного Spring Cloud. 2
  3. 3. www.luxoft.com Конфигурации Spring-приложений 3 XML-based <bean id="..."> ... </bean> <mvc:view-.../> Annotations @Service class MyService @Autowired Java-based @Bean MyService my() { return new My... } XML + Annotations <bean .../> @Autowired Annotations + Java @Autowired @Bean MyService..
  4. 4. www.luxoft.com XML-based конфигурация (+)  Всем знакома.  Большая часть документации и примеров с XML.  Централизованная конфигурация.  Естественно уменьшает связАнность Java-кода. 4
  5. 5. www.luxoft.com XML-based конфигурация (–)  Огромные XML.  XML.  Необходимость изменять в нескольких местах.  Тяжело рефакторить.  Не всегда работают Smart *  Ошибки в run-time. 5
  6. 6. www.luxoft.com Почему так получается? Что делать?  Огромные XML: - <import /> – позволяет из одной большой XML сделать несколько маленьких. - Делить приложение на модули. <import resource= "classpath*:context.xml" /> 6
  7. 7. www.luxoft.com Почему так получается? Что делать?  Огромные XML - используйте расширения: - util - mvc - tx - security - context <util:properties location="my.properties" /> 7
  8. 8. www.luxoft.com Почему так получается? Что делать?  Огромные XML - используйте Spring Expression Language: - для выражений, касающихся конфигурации; - не для бизнес-логики. <bean id="bean1"></bean> <bean> <property ref="bean1"/> </bean> <!-- SpEL --> <property value="#{new My()}"/> 8
  9. 9. www.luxoft.com Почему так получается? Что делать?  Огромные XML?  Много связей?  Хочется auto-wiring? <bean id="bean1"> ... </bean> <bean autowire="byType"> </bean> 9
  10. 10. www.luxoft.com Почему так получается? Что делать?  Ошибки в run-time (run-time бывает разный) - Используйте, по возможности, <constructor-arg> вместо <property>. - Поднимается ли контекст || часть контекста, можно протестировать в Unit-тестах. 10
  11. 11. www.luxoft.com XML Жизнь с XML Жизнь без XML 11
  12. 12. www.luxoft.com Annotation-based конфигурация public class MovieRecommender { @Autowired MovieCatalog movieCatalog; // отсутствующий конструктор // отсутствующие сеттеры // ... } @Service public MovieCatalog { // ... }  Активно используется @Autowired  Стереотипы: - @Controller / @RestController - @Service, ...  @Value("${my.property}") 12
  13. 13. www.luxoft.com Annotation-based конфигурация (+) public class MovieRecommender { @Autowired MovieCatalog movieCatalog; // отсутствующий конструктор // отсутствующие сеттеры // ... } @Service public MovieCatalog { // ... }  Никакого XML (или почти).  Изменения в одном месте.  Меньше Java-кода.  Рефакторинг стал проще.  Некоторые run-time ошибки просто исчезли.  spring-mvc, spring-* так и используют. 13
  14. 14. www.luxoft.com Annotation-based конфигурация (–) 14  Иногда нужно несколько бинов одного класса.  С @Autowired полями тяжелее пишутся Unit-тесты.  Добавить зависимость – элементарно. public class MovieRecommender { @Autowired MovieCatalog movieCatalog; // отсутствующий конструктор // отсутствующие сеттеры // ... } @Service public MovieCatalog { // ... }
  15. 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. 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;
  17. 17. www.luxoft.com Annotation-based конфигурация (–) 17 View Domain Data Source DB Infrastructure
  18. 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
  19. 19. www.luxoft.com Как не использовать Spring в Domain? 19 DB Domain Infrastructure
  20. 20. www.luxoft.com А где файл c бинами контекста?  Непонятно куда размещать чисто конфигурационный код.  С аннотациями @Component, @Service проблематично создать несколько бинов одного класса.  Сторонние классы без этих аннотаций.  Уменьшить связАннось. 20
  21. 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. 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. 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. 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. 25. www.luxoft.com Почему так получается? Что делать?  Смешение бизнес- и конфигурационной логики? - Конфигурацию вынести в отдельный пакет/модуль. - Договорённости, ревью.  Большие классы конфигурации? - Используйте @Import: классы делятся проще и естественнее, чем XML. - Начинайте со Spring Boot. 25
  26. 26. www.luxoft.com Spring Boot 26
  27. 27. www.luxoft.com Spring Boot Spring Framework Spring Boot 27
  28. 28. www.luxoft.com Spring Boot  Предназначен для мексимально быстрой разработки.  Сразу доступны общие features.  Автоматически конфигурирует компоненты.  По-умолчанию использует Java- based конфигурацию.  Возможности для тетсирования всего микросервиса. 28
  29. 29. www.luxoft.com Spring Boot 29
  30. 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
  31. 31. www.luxoft.com Auto configurations  spring-boot-starter-* - spring-boot-starter-aop - spring-boot-starter-jdbc - spring-boot-starter-mvc - spring-boot-strater-tx - ... spring.datasource.driverClassName =org.postgresql.Driver spring.datasource.url =jdbc:postgresql://srv0/test spring.datasource.username =test spring.datasource.password =test 31
  32. 32. www.luxoft.com Properties #application.properties my.app.title=Пример // UTF-8 #application.properties my.app.title= // ISO-8859-1 32
  33. 33. www.luxoft.com Properties properties.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>Hi</comment> <entry key="foo">bar</entry> <entry key="fu">baz</entry> </properties> 33
  34. 34. www.luxoft.com Properties #app.properties environments.dev.url =http://dev.bar.com environments.dev.name =Developer Setup environments.prod.url =http://foo.bar.com environments.prod.name =My Cool App #app.yaml environments: dev: url: http://dev.bar.com name: Developer Setup prod: url: http://foo.bar.com name: My Cool App 34
  35. 35. www.luxoft.com Properties 35  Куда их класть: - jar/war - /var/lib/tomcat/conf - /etc/luxoft/sampleapp/app.yaml - в git-репозиторий? - в системные свойства (можно туда класть JSON) - параметры запуска - git!  Откуда брать? - Из системых свойств - Из пареметров запуска - Из файла в широком сымсле - С сервера в широком смысле  git  файл на сервере  Ваш собственный config server
  36. 36. www.luxoft.com Config server Config server Client @EnableConfigServer spring.platform.config.server.uri: https://github.com/config-repo <dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-config-server </artifactId> </dependency> spring.cloud.config.uri: http://myconfigserver.com <dependency> <groupId> org.springframework.cloud </groupId> <artifactId> spring-cloud-config-client </artifactId> </dependency> <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-actuator </artifactId> </dependency> 36
  37. 37. www.luxoft.com Properties 37 Жизнь с config-server  правим настройки  собираем новую версию  останавливаем каждую ноду  обновляем каждую ноды  запускаем заново Жизнь с config-server  правим настройки (git)  перезапускаем config-server  перезапускаем ноды по одной
  38. 38. www.luxoft.com spring-cloud config server http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html 38
  39. 39. www.luxoft.com Спасибо за внимание! Вопросы?

×