SlideShare une entreprise Scribd logo
1  sur  39
www.luxoft.com
Конфигурирование
Spring-приложений
Дворжецкий Юрий
25 июня 2016
www.luxoft.com
О чём доклад?
 XML-based конфигурация:
- Почему так плохо получается?
- Что делать?
 Annotation-based:
- Почему так плохо получается?
- Что делать?
 Java-based конфигурация:
- Почему так плохо получается?
- Что делать?
 Конфигурировние вместе со
Spring Boot.
 Properties – куда их класть и
откуда брать?
 Немного Spring Cloud.
2
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..
www.luxoft.com
XML-based конфигурация (+)
 Всем знакома.
 Большая часть документации
и примеров с XML.
 Централизованная
конфигурация.
 Естественно уменьшает
связАнность Java-кода.
4
www.luxoft.com
XML-based конфигурация (–)
 Огромные XML.
 XML.
 Необходимость изменять в
нескольких местах.
 Тяжело рефакторить.
 Не всегда работают Smart *
 Ошибки в run-time.
5
www.luxoft.com
Почему так получается? Что делать?
 Огромные XML:
- <import /> – позволяет из одной
большой XML сделать несколько
маленьких.
- Делить приложение на модули.
<import resource=
"classpath*:context.xml"
/>
6
www.luxoft.com
Почему так получается? Что делать?
 Огромные XML - используйте
расширения:
- util
- mvc
- tx
- security
- context
<util:properties
location="my.properties"
/>
7
www.luxoft.com
Почему так получается? Что делать?
 Огромные XML - используйте
Spring Expression Language:
- для выражений, касающихся
конфигурации;
- не для бизнес-логики.
<bean id="bean1"></bean>
<bean>
<property ref="bean1"/>
</bean>
<!-- SpEL -->
<property
value="#{new My()}"/>
8
www.luxoft.com
Почему так получается? Что делать?
 Огромные XML?
 Много связей?
 Хочется auto-wiring?
<bean id="bean1">
...
</bean>
<bean autowire="byType">
</bean>
9
www.luxoft.com
Почему так получается? Что делать?
 Ошибки в run-time (run-time бывает разный)
- Используйте, по возможности,
<constructor-arg> вместо <property>.
- Поднимается ли контекст || часть контекста, можно протестировать
в Unit-тестах.
10
www.luxoft.com
XML
Жизнь с XML Жизнь без XML
11
www.luxoft.com
Annotation-based конфигурация
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
 Активно используется
@Autowired
 Стереотипы:
- @Controller / @RestController
- @Service, ...
 @Value("${my.property}")
12
www.luxoft.com
Annotation-based конфигурация (+)
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
 Никакого XML (или почти).
 Изменения в одном месте.
 Меньше Java-кода.
 Рефакторинг стал проще.
 Некоторые run-time ошибки
просто исчезли.
 spring-mvc, spring-* так и
используют.
13
www.luxoft.com
Annotation-based конфигурация (–)
14
 Иногда нужно несколько
бинов одного класса.
 С @Autowired полями
тяжелее пишутся Unit-тесты.
 Добавить зависимость –
элементарно.
public class MovieRecommender {
@Autowired
MovieCatalog movieCatalog;
// отсутствующий конструктор
// отсутствующие сеттеры
// ...
}
@Service
public MovieCatalog {
// ...
}
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
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;
www.luxoft.com
Annotation-based конфигурация (–)
17
View
Domain
Data Source
DB
Infrastructure
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
www.luxoft.com
Как не использовать Spring в Domain?
19
DB
Domain
Infrastructure
www.luxoft.com
А где файл c бинами контекста?
 Непонятно куда размещать
чисто конфигурационный код.
 С аннотациями @Component,
@Service проблематично
создать несколько бинов одного
класса.
 Сторонние классы без этих
аннотаций.
 Уменьшить связАннось.
20
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
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
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
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
www.luxoft.com
Почему так получается? Что делать?
 Смешение бизнес- и конфигурационной логики?
- Конфигурацию вынести в отдельный пакет/модуль.
- Договорённости, ревью.
 Большие классы конфигурации?
- Используйте @Import: классы делятся проще и естественнее, чем XML.
- Начинайте со Spring Boot.
25
www.luxoft.com
Spring Boot
26
www.luxoft.com
Spring Boot
Spring Framework Spring Boot
27
www.luxoft.com
Spring Boot
 Предназначен для мексимально
быстрой разработки.
 Сразу доступны общие features.
 Автоматически конфигурирует
компоненты.
 По-умолчанию использует Java-
based конфигурацию.
 Возможности для тетсирования
всего микросервиса.
28
www.luxoft.com
Spring Boot
29
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
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
www.luxoft.com
Properties
#application.properties
my.app.title=Пример
// UTF-8
#application.properties
my.app.title=
// ISO-8859-1
32
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
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
www.luxoft.com
Properties
35
 Куда их класть:
- jar/war
- /var/lib/tomcat/conf
- /etc/luxoft/sampleapp/app.yaml
- в git-репозиторий?
- в системные свойства (можно туда
класть JSON)
- параметры запуска
- git!
 Откуда брать?
- Из системых свойств
- Из пареметров запуска
- Из файла в широком сымсле
- С сервера в широком смысле
 git
 файл на сервере
 Ваш собственный config server
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
www.luxoft.com
Properties
37
Жизнь с config-server
 правим настройки
 собираем новую версию
 останавливаем каждую ноду
 обновляем каждую ноды
 запускаем заново
Жизнь с config-server
 правим настройки (git)
 перезапускаем config-server
 перезапускаем ноды по одной
www.luxoft.com
spring-cloud config server
http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html
38
www.luxoft.com
Спасибо за внимание!
Вопросы?

Contenu connexe

En vedette

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

En vedette (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

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

  • 2. www.luxoft.com О чём доклад?  XML-based конфигурация: - Почему так плохо получается? - Что делать?  Annotation-based: - Почему так плохо получается? - Что делать?  Java-based конфигурация: - Почему так плохо получается? - Что делать?  Конфигурировние вместе со Spring Boot.  Properties – куда их класть и откуда брать?  Немного Spring Cloud. 2
  • 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. 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
  • 11. www.luxoft.com XML Жизнь с XML Жизнь без XML 11
  • 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
  • 19. www.luxoft.com Как не использовать Spring в Domain? 19 DB Domain Infrastructure
  • 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
  • 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
  • 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. 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. www.luxoft.com Properties 35  Куда их класть: - jar/war - /var/lib/tomcat/conf - /etc/luxoft/sampleapp/app.yaml - в git-репозиторий? - в системные свойства (можно туда класть JSON) - параметры запуска - git!  Откуда брать? - Из системых свойств - Из пареметров запуска - Из файла в широком сымсле - С сервера в широком смысле  git  файл на сервере  Ваш собственный config server
  • 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. www.luxoft.com Properties 37 Жизнь с config-server  правим настройки  собираем новую версию  останавливаем каждую ноду  обновляем каждую ноды  запускаем заново Жизнь с config-server  правим настройки (git)  перезапускаем config-server  перезапускаем ноды по одной