Dependency Injection In Spring
DI Basics
• Reduces coupling between classes
• Classes do not obtain their dependencies (collaborators), Spring container provides proper dependency based on configuration provided
• Enables easy switching of dependencies based en current needs - local development vs production environment, easy mocking for unit tests, in memory database vs production one, etc.
• Promotes programming to interfaces
• Spring-managed objects are called "beans"
• Beans are written as POJOs, no need to inherit Spring base classes or implement spring interfaces
• Classes can be completely independent of Spring framework (although for convenience Spring annotations are often used)
• Lifecycle of beans is managed centrally by Spring container (eg. enforces singleton state of bean/ one instance pers session. one instance per HTTP request/...)
• Spring configuration (including DI) is possible either via XML or Java configuration files (newer, more popular approach)
Spring Application Context
• Spring beans are managed by Application Context (container)
• The application context is initialized with one or more configuration files (java or XML), which contains setting for the framework, DI, persistence, transactions,...
• Application context can be also instantiated in unit tests
• Ensures beans are always created in right order based on their dependencies
• Ensures all beans are fully initialized before the first use
• Each bean has its unique identifier, by which it can be later referenced (should not contain specific implementation details, based on circumstances different implementations can be provided)
• No need for full fledged java EE application server
Creating app context
//From single configuration class
ApplicationContext context = SpringApplication.run(ApplicationConfiguration.class);
//With string args
ApplicationContext context = SpringApplication.run(ApplicationConfiguration.class, args);
//With additional configuration
SpringApplication app = new SpringApplication(ApplicationConfiguration.class);
// ... customize app settings here
context = app.run(args);
[A] Additional ways to create app context
• new AnnotationConfigApplicationContext(AppConfig.class);
• new ClassPathXmlApplicationContext(“com/example/app-config.xml”);
• new FileSystemXmlApplicationContext(“C:/Users/vojtech/app-config.xml”);
• new FileSystemXmlApplicationContext(“./app-config.xml”);
Obtaining beans from Application Context
//Obtain bean by type
applicationContext.getBean(MyBean.class);
Bean Scopes
• Spring manages lifecycle of beans, each bean has its scope
• Default scope is singleton - one instance per application context
• If none of the Spring scopes is appropriate, custom scopes can be defined
• Scope can be defined by @Scope (eg. @Scope(BeanDefinition.SCOPE_SINGLETON)) annotation on the class-level of bean class
Available Scopes
• Singleton - One instance per application context, default if bean scope is not defined
• Prototype - New instance is created every time bean is requested
• Session - One instance per user session - Web Environment only
• [A]Global-session - One global session shared among all portlets - Only in Web Portlet Environment
• Request - One instance per each HTTP Request - Web Environment only
• Custom - Can define multiple custom scopes with different names and lifecycle
• Additional scopes available for Spring Web Flow applications (not needed for the certification)
Spring Configuration
• can be XML or java based
• Externalized from the bean class → separation of concerns
Externalizing Configuration Properties
• Configuration values (DB connection, external endpoints, ...) should not be hard-coded in the configuration files
• Better to externalize to, eg. to .properties files
• Can then be easily changed without a need to rebuild application
• Can have different values on different environments (eg. different DB instance on each environment)
• Spring provides abstraction on top of many property sources
o JNDI
o Java .properties files
o JVM System properties
o System environment variables
o Servlet context params
Obtaining properties using Environment object
• Environment can be injected using @Autowired annotation
• properties are obtained using environment.getProperty("propertyName")
Obtaining properties using @Value annotation
• @Value("${propertyName}") can be used as an alternative to Environment object
• can be used on fields on method parameters
Property Sources
• Spring loads system property sources automatically (JNDI, System variables, ...)
• Additional property sources can be specified in configuration using @PropertySource annotation on @Configuration class
• If custom property sources are used, you need to declare PropertySourcesPlaceholderConfigurer as a static bean
@Configuration
@PropertySource("classpath:/com/example/myapp/config/application.properties")
public class ApplicationConfiguration {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
//Additional config here
}
• Can use property placeholders in @PropertySource names (eg. different property based on environment - dev, staging, prod)
• placeholders are resolved against know properties
@PropertySource("classpath:/com/example/myapp/config/application-{$ENV}.properties")
Spring Expression language
• Acronym SpEL
• can be used in @Value annotation values
• enclosed in #{}
• ${} is for properties, #{} is for SpEL
• Access property of bean #{beanName.property}
• Can reference systemProperties and systemEnvironment
• Used in other spring projects such as Security, Integration, Batch, WebFlow,...
Spring profiles
• Beans can have associated profile (eg. "local", "staging", "mock")
• Using @Profile("profileName") annotation - either on bean level or on @Configuration class level - then it applies for all the beans inside
• Beans with no profile are always active
• Beans with a specific profile are loaded only when given profile is active
• Beans can have multiple profiles assigned
• Multiple profiles can be active at the same time
• Active profiles can be set in several different ways
o @ActiveProfiles in spring integration tests
o in web.xml deployment descriptor as context param ("spring.profiles.active")
o By setting "spring.profiles.active" system property
o Active profiles are comma-separated
• Profiles can be used to load different property files under different profiles
@Configuration
@Profile("local")
@PropertySource("classpath:/com/example/myapp/config/application-local.properties")
public class LocalConfig {
//@PropertySource is loaded only when "local" profile is active
//Config here
}
Composite configuration
• Preferred way is to divide configuration to multiple files and then import them when used
• Usually based on application layers - web layer, service layer, DAO layer,...
• Enables better configuration organization
• Enables to separate layers, which often change based on environment, etc. - eg. switching services for mocks in unit tests, having an in-memory database, ...
• Can import other config files in java config using @Import annotation
@Configuration
@Import({WebConfiguration.class, InfrastructureConfiguration})
public class ApplicationConfiguration {
//Config here
}
• Beans defined in another @Configuration file can be injected using @Autowired annotation on field or setter level, cannot use constructors here
• Another way of referencing beans from another @Configuration is to declare them as method parameters of defined beans in current config file
@Configuration
@Import({WebConfiguration.class, InfrastructureConfiguration})
public class ApplicationConfiguration {
@Bean //Object returned by this method will be spring managed bean
public MyBean myBean(BeanFromOtherConfig beanFromOtherConfig) {
//beanFromOtherConfig bean is automatically injected by Spring
...
return myBean;
}
• In case multiple beans of the same type are found in configuration, spring injects the one, which is discovered as the last
Java Configuration
• In Java classes annotated with @Configuration on class level
• Beans can be either specifically declared in @Configuration using @Bean annotation or automatically discovered using component scan
Component Scan
• On @Configuration class, @ComponentScan annotation can be present with packages, which should be scanned
• All classes with @Component annotation on class level in scanned packages are automatically considered spring managed beans
• Applies also for annotations annotated with @Component (@Service, @Controller, @Repository, @Configuration)
• Beans declare their dependencies using @Autowired annotation - automatically injected by spring
o Field level - even private fields
o Method Level
o Constructor level
• Autowired dependencies are required by default and result in exception when no matching bean found
• Can be changed to optional using @Autowired(required=false)
• Dependencies of type Optional(T) are automatically considered optional
• Can be also used for scanning jar dependencies
• Recommended to declared as specific scanned packages as possible to speed up scan time
@Configuration
@ComponentScan( { "org.foo.myapp", "com.bar.anotherapp" })
public class ApplicationConfiguration {
//Configuration here
}
@Value annotation
• Is used to inject value either from system properties (using ${}) or SpEL (using #{})
• Can be on fields, constructor parameters or setter parameters
• On constructors and setters must be combined with @Autowired on method level, for fields @Value alone is enough
• Can specify default values
o ${minAmount:100}"
o #{environment['minAmount'] ?: 100}
Setter vs Constructor vs Field injection
• All three types can be used and combined
• Usage should be consistent
• Constructors are preferred for mandatory and immutable dependencies
• Setters are preferred for optional and changeable dependencies
• Only way to resolve circular dependencies is using setter injection, constructors cannot be used
• Field injection can be used, but mostly discouraged
Autowired conflicts
• If no bean of given type for @Autowired dependency is found and it is not optional, exception is thrown
• If more beans satisfying the dependency is found, NoSuchBeanDefinitionException is thrown as well
• Can use @Qualifier annotation to inject bean by name, the name can be defined inside component annotation - @Component("myName")
@Component("myRegularDependency")
public class MyRegularDependency implements MyDependency {...}
@Component("myOtherDependency")
public class MyOtherDependency implements MyDependency {...}
@Component
public class MyService {
@Autowired
public MyService(@Qualifier("myOtherDependency") MyDependency myDependency) {...}
}
Autowired resolution sequence
1. Try to inject bean by type, if there is just one
2. Try to inject by @Qualifier if present
3. Try to inject by bean name matching name of the property being set
• Bean 'foo' for 'foo' field in field injection
• Bean 'foo' for 'setFoo' setter in setter injection
• Bean 'foo' for constructor param named 'foo' in constructor injection
When a bean name is not specified, one is auto-generated: De-capitalized non-qualified class name
Explicit bean declaration
• Class is explicitly marked as spring managed bean in @Configuration class (similar concept is in XML config)
• All settings of the bean are present in the @Configuration class in the bean declaration
• Spring config is completely in the @Configuration, bean is just POJO with no spring dependency
• Cleaner separation of concerns
• Only option for third party dependencies, where you cannot change source code
@Configuration
public class ApplicationConfiguration {
/***
* - Object returned by this method will be spring managed bean
* - Return type is the type of the bean
* - Method name is the name of the bean
*/
@Bean
public MyBean myBean() {
MyBean myBean = new MyBean();
//Configure myBean here
return myBean;
}
Component scan vs Explicit bean declaration
• Same settings can be achieved either way
• Both approaches can be mixed
o Can use component scan for your code, @Configuration for third party and legacy code
o Use component scan only for Spring classes, Explicit configuration for others
• Explicit bean declaration
o Is more verbose
o Achieves cleaner separation of concerns
o Config is centralized in on or several places
o Configuration can contain complex logic
• Component scan
o Config is scattered across many classes
o Cannot be used for third party classes
o Code and configuration violate single responsibility principle, bad separations of concerns
o Good for rapid prototyping and frequently changing code
@PostConstruct, @PreDestroy
• Can be used on @Component's methods
• Methods can have any access modifier, but no arguments and return void
• Applies to both beans discovered by component scan and declared by @Bean in @Configuration
• Defined by JSR-250, not Spring (javax.annotation package)
• Also used by EJB3
• Alternative in @Configuration is @Bean(initMethod="init”, destroyMethod="destroy") - can be used for third party beans
@PostConstruct
• Method is invoked by Spring after DI
@PreDestroy
• Method is invoked before bean is destroyed
• Is not called for prototype scoped beans!
• After application context is closed
• Only if JVM exits normally
Stereotypes and Meta-annotations
Stereotypes
• Spring has several annotations, which are themselves annotated by @Component
• @Service, @Controller, @Repository, @Configuration, ... more in other Spring Projects
• Are discoverable using component scan
Meta-annotations
• Used to annotate other annotations
• Eg. can create custom annotation, which combines @Service and @Transactional
[A] @Resource annotation
• From JSR-250, supported by EJB3
• Identifies dependencies for injection by name, not by type like @Autowired
• Name is spring bean name
• Can be used for field and setter DI, not for constructors
• Can be used with name @Resource("beanName") or without
o If not provided, name is inferred from field name or tries injection by type if name fails
o setAmount() → "amount" name
[A] JSR-330 - Dependency Injection for Java
• Alternative DI annotations
• Spring is valid JSR-330 implementation
• @ComponentScan also scans for JSR-330 annotations
• @Inject for injecting dependencies
o Provides subset of @Autowired functionality, but often is enough
o Always required
• @Named - Alternative to @Component
o With or without bean name - @Named / @Named("beanName")
o Default scope is "prototype"
• @Singleton - instead of @Named for singleton scoped beans
XML Configuration
• Original form of configuration in Spring
• External configuration in XML files
• Beans are declared inside (beans) tag
• Can be combined with java config
o Xml config files can be imported to @Configuration using @ImportResource
o @ImportResource({"classpath:com/example/foo/config.xml","classpath:com/example/foo/other-config.xml"})
o For importing java config files, @Import is used
o In xml, (import resource="config.xml" /) can be used to import other xml config files, path is relative to current xml config file
(beans)
(import resource="other-config.xml" /)
(bean id="fooBeanName" class="com.example.foo.Foo" /)
(bean id="barBeanName" class="com.example.bar.Bar" /)
(/beans)
Conponent scan can be also used in XML
(context:component-scan base-package="com.example.foo, com.example.bar"/)
Constructor injection
• using (constructor-arg) tag
• ref attribute is used for injecting beans
• value attribute is used for setting primitive values, primitives are auto-converted to proper type
o supports numeric types, BigDecimal, boolean, Date, Resource, Locale
• Parameters can be in any order, are matched by type
• If necessary, order can be defined - (constructor-arg ref="someBean" index="0"/)
• Or using named constructor parameters - (constructor-arg ref="someBean" name="paramName"/)
o Java 8+
o OR compiled with debug-symbols enabled to preserve param names
o OR @ConstructorProperties( { "firstParam", "secondParam" } ) on constructor - order of items matches order of constructor params
(bean id="fooBeanName" class="com.example.foo.Foo")
(constructor-arg ref="someBean"/)
(constructor-arg val="42"/)
(/bean)
Setter injection
• Using (property\) tag
• ref or value like with constructor injection
• Setter and constructor injection can be combined
• Can use (value\) tag inside of (property\)
(bean id="fooBeanName" class="com.example.foo")
(constructor-arg ref="someBean"/)
(property ref="someOtherBean"/)
(property name="someProperty" val="42"/)
(property name="someOtherProperty")
(value)42(/value)
(/property)
(/bean)
Additional bean config
• @PostConstruct is equivalent to init-method attribute
• @PreDestroy is init to destroy-method attribute
• @Scope is equivalent to scope attribute, singleton if not specified
• @Lazy is equivalent to lazy-init attribute
• @Profile is equivalent to profile attribute on (beans) tag, (beans) tags can be nested
(beans profile="barProfile")
(beans profile="fooProfile")
(bean id="fooBeanName" class="com.example.foo.Foo" scope="prototype" /)
(/beans)
(bean id="barBeanName" class="com.example.bar.Bar" lazy-init="true" init-method="setup"
destroy-method="teardown" /)
(/beans)
XML Namespaces
• Default namespace is usually beans
(?xml version="1.0" encoding="UTF-8"?)
(beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd)
(!--Config here--)
(/beans)
• Many other namespaces available
o context
o aop (Aspect Oriented Programming)
o tx (transactions)
o util
o jms
o ...
• Provides tags to simplify configuration and hide detailed bean configuration
o (context:property-placeholder location="config.properties" /) instead of declaring PropertySourcesPlaceholderConfigurer bean manually
o (aop:aspectj-autoproxy /) Enables AOP (5+ beans)
o (tx:annotation-driven /) Enables Transactions (15+ beans)
• XML Schema files can have version specified (spring-beans-4.2.xsd) or not (spring-beans.xsd)
o If version not specified, most recent is used
o Usually, no version number is preferred as it means easier upgrade to newer framework version
Accessing properties in XML
• Equivalent of @Value in XML
• Still need to declare PropertySourcesPlaceholderConfigurer
• (context:property-placeholder location="config.properties" /) from context namespace
(beans ...beans and context namespaces here...)
(context:property-placeholder location="config.properties" /)
(bean id="beanName" class="com.example.MyBean")
(property name="foo" value="${foo}" /)
(property name="bar" value="${bar}" /)
(/bean)
(/beans)
Can load different property files based on spring profiles
(context:property-placeholder properties-ref="config"/)
(beans profile="foo")
(util:properties id="config" location="foo-config.properties"/)
(/beans)
(beans profile="bar")
(util:properties id="config" location="bar-config.properties"/)
(/beans)
Spring Application Lifecycle
• Three main phases - Initialization, Use, Destruction
• Lifecycle independent on configuration style used (java / XML)
Initialization phase
• Application is prepared for usage
• Application not usable until init phase is complete
• Beans are created and configured
• System resources allocated
• Phase is complete when application context is fully initialized
Bean initialization process
1. Bean definitions loaded
2. Bean definitions post-processed
3. For each bean definition
i. Instantiate bean
ii. Inject dependencies
iii. Run Bean Post Processors
a. Pre-Initialize
b. Initialize
c. Post-Initialize
Loading and post-processing bean definitions
• Explicit bean definitions are loaded from java @Configuration files or XML config files
• Beans are discovered using component scan
• Bean definitions are added to BeanFactory with its id
• Bean Factory Post Processor beans are invoked - can alter bean definitions
• Bean definitions are altered before beans are instantiated based on them
• Spring has already many BFPPs - replacing property placeholders with actual values, registering custom scope,...
• Custom BFPPs can be created by implementing BeanFactoryPostProcessor interface
Instatiating beans
• Spring creates beans eagerly by default unless declared as @Lazy (or lazy-init in XML)
• Beans are created in right order to satisfy dependencies
• After a bean is instantiated, it may be post-processed (similar to bean definitions post-processing)
• One kind of post-processors are Initializers - @PostConstruct/init-method
• Other post processors can be invoked either before initialization or after initialization
• To create custom Bean Post Processor, implement interface BeanPostProcessor
public interface BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName);
public Object postProcessBeforeInitialization(Object bean,String beanName);
}
Note that return value is the post-processed bean. It may be the bean with altered state, however, it may be completely new object. It is very important - post processor can return proxy of bean instead of original one - used a lot in spring - AOP, Transactions, ... A proxy can add dynamic behavior such as security, logging or transactions without its consumers knowing.
Spring Proxies
• Two types of proxies
• JDK dynamic proxies
o Proxied bean must implement java interface
o Part of JDK
o All interfaces implemented by the class are proxied
o Based on proxy implementing interfaces
• CGLib proxies
o Is not part of JDK, included in spring
o Used when class implements no interface
o Cannot be applied to final classes or methods
o Based on proxy inheriting the base class
Use phase
• App is in this phase for the vast majority of time
• Beans are being used, business logic performed
Destruction phase
• Application shuts down
• Resources are being released
• Happens when application context closes
• @PreDestroy and destroyMethod methods are called
• Garbage Collector still responsible for collecting objects
Testing Spring Applications
Test Types
Unit Tests
• Without spring
• Isolated tests for single unit of functionality, method level
• Dependencies interactions are not tested - replaced by either mocks or stubs
• Controllers; Services; ...
Integration Tests
• With spring
• Tests interactions between components
• Dependencies are not mocked/stubbed
• Controllers + Services + DAO + DB
Spring Test
• Distributed as separate artifact - spring-test.jar
• Allows testing application without the need to deploy to external container
• Allows to reuse most of the spring config also in tests
• Consist of several JUnit support classes
• SpringJUnit4ClassRunner - application context is cached among test methods - only one instance of app context is created per test class
• Test class needs to be annotated with @RunWith(SpringJUnit4ClassRunner.class)
• Spring configuration classes to be loaded are specified in @ContextConfiguration annotation
o If no value is provided (@ContextConfiguration), config file ${classname}-context.xml in the same package is imported
o XML config files are loaded by providing string value to annotation - @ContextConfiguration("classpath:com/example/test-config.xml")
o Java @Configuration files are loaded from classes attribute - @ContextConfiguration(classes={TestConfig.class, OtherConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfig.class, OtherConfig.class})
public final class FooTest {
//Autowire dependencies here as usual
@Autowired
private MyService myService;
//...
}
@Configuration inner classes (must be static) are automatically detected and loaded in tests
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(...)
public final class FooTest {
@Configuration
public static class TestConfiguration {...}
}
Testing with spring profiles
• @ActiveProfiles annotation of test class activates profiles listed
• @ActiveProfiles( { "foo", "bar" } )
Test Property Sources
• @TestPropertySource overrides any existing property of the same name.
• Default location is "[classname].propeties"
• @TestPropertySource(properties= { "username=foo" } )
Testing with in-memory DB
• Real DB usually replaced with in-memory DB for integration tests
• No need to install DB server
• In-memory DB initialized with scripts using @Sql annotation
• Can be either on class level or method level
• If on class level, SQL is executed before every test method in the class
• If on method level, SQL is executed before invoking particular method
• Can be declared with or without value
• When value is present, specified SQL script is executed - @Sql({ "/sql/foo.sql", "/sql/bar.sql" } )
• When no value is present, defaults to ClassName.methodName.sql
• Can be specified whether SQL is run before (by default) or after the test method
• @Sql(scripts=“/sql/foo.sql”, executionPhase=Sql.ExecutionPhase.AFTER_TEST_METHOD)
• Can provide further configuration using config param - error mode, comment prefix, separator, ...
Aspect Oriented Programming
• AOP solves modularization of cross-cutting concerns
• Functionality, which would be scattered all over the app, but is not core functionality of the class
o Logging
o Security
o Performance monitoring
o Caching
o Error handling
o ...
• Keeps good separation of concerns, does not violate single responsibility principle
• Solves two main problems
o Code Tangling - Coupling of different concerns in one class - eg. business logic coupled with security and logging
o Code scattering - Same code scattered among multiple classes, code duplication - eg. same security logic in each class
Writing AOP Apps
1. Write main application logic
2. Write Aspects to address cross-cutting concerns
3. Weave aspects into the application to be applied in right places
AOP Technologies
• AspectJ
o Original AOP Technology
o Uses bytecode modification for aspect weaving
o Complex, lot of features
• Spring AOP
o AspectJ integration
o Subset of AspectJ functionality
o Uses dynamic proxies instead of bytecode manipulation for aspect weaving
o Limitations
Can advise non-private methods only
Only applicable to Spring Managed Beans
AOP is not applied when one method of the class calls method on the same class (will bypass the proxy)
AOP Concepts
• Join Point - A point in the execution of the app, where AOP code will be applied - method call, exception thrown
• Pointcut - Expression that matches one or multiple Join Points
• Advice - Code to be executed at each matched Join Point
• Aspect - Module encapsulating Pointcut and Advice
• Weaving - Technique, by which aspects are integrated into main code
Enabling AOP
• In XML - (aop:aspectj-autoproxy /)
• In Java Config - @EnableAspectJAutoProxy on @Configuration class
• Create @Aspect classes, which encapsulate AOP behavior
o Annotate with @Aspect
o Must be spring managed bean - either explicitly declare in configuration or discover via component-scan
o Class methods are annotated with annotation defining advice (eg. Before method call, After, if exception), the annotation value defines pointcut
@Aspect
public class MyAspect {
@Before("execution(void set*(*))")
public void beforeSet() {
//Do something before each setter call
}
}
Pointcuts
• Spring AOP uses subset of AspectJ's expression language for defining pointcuts
• Can create composite conditions using ||, && and !
• execution((method pattern)) - method must match the pattern provided
• [Modifiers] ReturnType [ClassType] MethodName ([Arguments]) [throws ExceptionType]
o execution(void com.example.*Service.set*(..))
o Means method call of Services in com.example package starting with "set" and any number of method parameters
• More examples
o execution(void find*(String)) - Any methods returning void, starting with "find" and having single String argument
o execution(* find(*)) - Any method named "find" having exactly one argument of any type
o execution(* find(int, ..)) - Any method named "find", where its first argument is int (.. means 0 or more)
o execution(@javax.annotation.security.RolesAllowed void find(..)) - Any method returning void named "find" annotated with specified annotation
o execution(* com.*.example.*.*(..)) - Exactly one directory between com and example
o execution(* com..example.*.*(..)) - Zero or more directoryies between com and example
o execution(* *..example.*.*(..)) - Any sub-package called "Example"
Advice Types
Before
• Executes before target method invocation
• If advice throws an exception, target is not called
• @Before("expression")
After returning
• Executes after successful target method invocation
• If target throws an exception, advice is not called
• Return value of target method can be injected to the annotated method using returning param
• @AfterReturning(value="expression", returning="paramName")
@AfterReturning(value="execution(* service..*.*(..))", returning="retVal")
public void doAccessCheck(Object retVal) {
//...
}
After throwing
• Called after target method invocation throws an exception
• Exception object being thrown from target method can be injected to the annotated method using throwing param
• It will not stop exception from propagating but can throw another exception
• Stopping exception from propagating can be achieved using @Around advice
• @AfterThrowing(value="expression", throwing="paramName")
@AfterThrowing(value="execution(* service..*.*(..))", throwing="exception")
public void doAccessCheck(Exception exception) {
//...
}
After
• Called after target method invocation, no matter whether it was successful or there was an exception
• @After("expression")
Around
• ProceedingJoinPoint parameter can be injected - same as regular JoinPoint, but has proceed() method
• By calling proceed() method, target method will be invoked, otherwise it will not be
[A] XML AOP Configuration
• Instead of AOP annotations (@Aspect, @Before, @After, ...), pure XML onfig can be used
• Aspects are just POJOs with no spring annotations or dependencies whatsoever
• aop namespace
(aop:config)
(aop:aspect ref="myAspect")
(aop:before pointcut="execution(void set*(*))" method="logChange"/)
(/aop:aspect)
(bean id="myAspect" class="com.example.MyAspect" /)
(/aop:config)
[A]Named Pointcuts
• Pointcut expressions can be named and then referenced
• Makes pointcut expressions reusable
• Pointcuts can be externalized to a separate file
• Composite pointcuts can be split into several named pointcuts
XML
(aop:config)
(aop:pointcut id="pointcut" expression="execution(void set*(*))"/)
(aop:aspect ref="myAspect")
(aop:after-returning pointcut-ref="pointcut" method="logChange"/)
(/aop:aspect)
(bean id="myAspect" class="com.example.MyAspect" /)
(/aop:config)
Java
//Pointcut is referenced here by its ID
@Before("setters()")
public void logChange() {
//...
}
//Method name is pointcut id, it is not executed
@Pointcut("execution(void set*(*))")
public void setters() {
//...
}
[A] Context Selecting Pointcuts
• Data from JoinPoint can be injected as method parameters with type safety
• Otherwise they would need to be obtained from JoinPoint object with no type safety guaranteed
• @Pointcut("execution(void example.Server.start(java.util.Map)) && target(instance) && args(input)")
o target(instance) injects instance on which is call performed to "instance" parameter of the method
o args(input) injects target method parameters to "input" parameter of the method
REST
• Representational State Transfer
• Architectural style
• Stateless (clients maintains state, not server), scalable → do not use HTTP session
• Usually over HTTP, but not necessarily
• Entities (e.g. Person) are resources represented by URIs
• HTTP methods (GET, POST, PUT, DELETE) are actions performed on resource (like CRUD)
• Resource can have multiple representations (different content type)
• Request specifies desired representation using HTTP Accept header, extension in URL (.json) or parameter in URL (format=json)
• Response states delivered representation type using Content-Type HTTP header
H@EOAS
• Hypermedia As The Engine of Application State
• Response contains links to other items and actions → can change behavior without changing client
• Decoupling of client and server
JAX-RS
• Java API for RESTful web services
• Part of Java EE6
• Jersey is reference implementation
@Path("/persons/{id}")
public class PersonService {
@GET
public Person getPerson(@PathParam("id") String id) {
return findPerson(id);
}
}
RestTemplate
• Can be used to simplify HTTP calls to RESTful api
• Message converters supported - Jackson, GSON
• Automatic input/output conversion - using HttpMessageConverters
o StringHttpMessageConvertor
o MarshallingHttpMessageConvertor
o MappingJackson2XmlHttpMessageConverter
o GsonHttpMessageConverter
o RssChannelHttpMessageConverter
• AsyncRestTemplate - similar to regular one, allows asynchronous calls, returns ListenableFuture
Spring Rest Support
• In MVC Controllers, HTTP method consumed can be specified
o @RequestMapping(value="/foo", method=RequestMethod.POST)
• @ResponseStatus can set HTTP response status code
o If used, void return type means no View (empty response body) and not default view!
o 2** - success (201 Created, 204 No Content,...)
o 3** - redirect
o 4** - client error (404 Not found, 405 Method Not Allowed, 409 Conflict,...)
o 5** - server error
• @ResponseBody before controller method return type means that the response should be directly rendered to client and not evaluated as a logical view name
o Uses converters to convert return type of controller method to requested content type
• @RequestHeader can inject value from HTTP request header as a method parameter
• @RequestBody - Injects body of HTTP request, uses converters to convert request data based on content type
o public void updatePerson(@RequestBody Person person, @PathVariable("id") int personId)
o Person can be converted from JSON, XML, ...
• HttpEntity()
o Controller can return HttpEntity instead of view name - and directly set response body, HTTP response headers
o return new HttpEntity(ReturnType)(responseBody, responseHeaders);
• HttpMessageConverter
o Converts HTTP response (annotated by @ResponseBody) and request body data (annotated by @RequestBody)
o @EnableWebMvc or (mvc:annotation-driven/) enables it
o XML, Forms, RSS, JSON,...
• @RequestMapping can have attributes produces and consumes to specify input and output content type
o @RequestMapping (value= "/person/{id}", method=RequestMethod. GET, produces = {"application/json"})
o @RequestMapping (value= "/person/{id}", method=RequestMethod. POST, consumes = { "application/json" })
• Content Type Negotiation can be used both for views and REST endpoints
• @RestController - Equivalent to @Controller, where each method is annotated by @ResponseBody
• @ResponseStatus(HttpStatus.NOT_FOUND) - can be used on exception to define HTTP code to be returned when given exception is thrown
• @ExceptionHandler({MyException.class}) - Controller methods annotated with it are called when declared exceptions are thrown
o Method can be annotated also with @ResponseStatus to define which HTTP result code should be sent to the client in the case of the exception
• When determining address of child resources, UriTemplate should be used so absolute urls are not hardcoded
StringBuffer currentUrl = request.getRequestURL();
String childIdentifier = ...;//Get Child Id from url requested
UriTemplate template = new UriTemplate(currentUrl.append("/{childId}").toString());
String childUrl = template.expand(childIdentifier).toASCIIString();
Microservices in Spring
Microservices
• Classic monolithic architecture
o Single big app
o Single persistence (usually relational DB)
o Easier to build
o Harder to scale up
o Complex and large deployment process
• Microservices architecture
o Each microservice has its own storage best suited for it (Relational DB, Non-Relation DB, Document Store, ...)
o Each microservice can be in different language with different frameworks used, best suited for its purpose
o Each microservice has separate development, testing and deployment
o Each microservice can be developed by separate team
o Harder to build
o Easier to extend and maintain
o Easier to scale up
o Easy to deploy or update just one microservice instead of the whole monolith
• One microservice usually consists of several services in service layer and corresponding data store
• Microservices are well suited to run in the cloud
o Easily manage scaling - number of microservice instances
o Scaling can be done dynamically
o Provided load balancing across all the instances
o Underlying infrastructure agnostic
o Isolated containers
• Apps deployed to cloud don't necessary have to be microservices
o Enables to gradually change existing apps to microservice architecture
o New features of monolith can be added as microservices
o Existing features of monolith can be one by one refactored to microservices
Spring Cloud
• Provides building blocks for building Cloud and microservice apps
o Intelligent routing among multiple instances
o Service registration and discovery
o Circuit Breakers
o Distributed configuration
o Distributed messaging
• Independent on specific cloud environment - Supports Heroku, AWS, Cloud foundry, but can be used without any cloud
• Built on Spring Boot
• Consist of many sub-projects
o Spring Cloud Config
o Spring Cloud Security
o Spring Cloud Connectors
o Spring Cloud Data Flow
o Spring Cloud Bus
o Spring Cloud for Amazon Web Services
o Spring Cloud Netflix
o Spring Cloud Consul
o ...
Building Microservices
1. Create Discovery Service
2. Create a Microservice and register it with Discovery Service
3. Clients use Microservice using "smart" RestTemplate, which manages load balancing and automatic service lookup
Discovery Service
• Spring Cloud supports two Discovery Services
o Eureka - by Netflix
o Consul.io by Hashicorp (authors of Vagrant)
o Both can be set up and used for service discovery easily in Spring Cloud
o Can be later easily switched
• On top of regular Spring Cloud and Boot dependencies (spring-cloud-starter, spring-boot-starter-web, spring-cloud-starter-parent for parent pom), dependency for specific Discovery Service server needs to be provided (eg. spring-cloud-starter-eureka-server)
• Annotate @SpringBootApplication with @EnableEurekaServer
• Provide Eureka config to application.properties (or application.yml)
server.port=8761
#Not a client
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF
Microservice Registration
• Annotate @SpringBootApplication with @EnableDiscoveryClient
• Each microservice is a spring boot app
• Fill in application.properties with app name and eureka server URL
o spring.application.name is name of the microservice for discovery
o eureka.client.serviceUrl.defaultZone is URL of the Eureka server
Microservice Usage
• Annotate client app using microservice (@SpringBootApplication) with @EnableEurekaServer
• Clients call Microservice using "smart" RestTemplate, which manages load balancing and automatic service lookup
• Inject RestTemplate using @Autowired with additional @LoadBalanced
• "Ribbon" service by Netflix provides load balancing
• RestService automatically looks up microservice by logical name and provides load-balancing
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
Then call specific Microservice with the restTemplate
restTemplate.getForObject("http://persons-microservice-name/persons/{id}", Person.class, id);
Spring Security
• Independent on container - does not require EE container, configured in application and not in container
• Separated security from business logic
• Decoupled authorization from authentication
• Principal - User, device, or system that is performing and action
• Authentication
o Process of checking identity of a principal (credentials are valid)
o basic, digest, form, X.509, ...
o Credentials need to be stored securely
• Authorization
o Process of checking a principal has privileges to perform requested action
o Depends on authentication
o Often based on roles - privileges not assigned to specific users, but to groups
• Secured item - Resource being secured
Configuring Spring Security
• Annotate your @Configuration with @EnableWebSecurity
• Your @Configuration should extend WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configureGlobal(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
//Global security config here
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//Web security specific config here
}
}
web.xml - Declare spring security filter chain as a servlet filter
(filter)
(filter-name)springSecurityFilterChain(/filter-name)
(filter-class) org.springframework.web.filter.DelegatingFilterProxy(/filter-class)
(/filter)
(filter-mapping)
(filter-name)springSecurityFilterChain(/filter-name)
(url-pattern)/*(/url-pattern)
(/filter-mapping)
Authorization
• Process of checking a principal has privileges to perform requested action
• Specific urls can have specific Role or authentication requirements
• Can be configured using HttpSecurity.authorizeRequests().*
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/css/**","/img/**","/js/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/profile").hasAnyRole("USER","ADMIN")
.antMatchers("/user/**").authenticated()
.antMatchers("/user/private/**").fullyAuthenticated()
.antMatchers("/public/**").anonymous();
}
}
• Rules are evaluated in the order listed
• Should be from the most specific to the least specific
• Options
o hasRole() - has specific role
o hasAnyRole() - multiple roles with OR
o hasRole(FOO) AND hasRole(BAR) - having multiple roles
o isAnonymous() - unauthenticated
o isAuthenticated() - not anonymous
Authentication
Authentication provider
• Processes authentication requests and returns Authentication object
• Default is DaoAuthenticationProvider
o UserDetailsService implementation is needed to provide credentials and authorities
o Built-in implementations: LDAP, in-memory, JDBC
o Possible to build custom implementation
• Can provide different auth configuration based on spring @Profiles - in-memory for development, JDBC for production etc.
In-memory Authentication provider
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configureGlobal(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.inMemoryAuthentication().withUser("alice").password("letmein").roles("USER").and()
.withUser("bob").password("12345").roles("ADMIN").and();
}
}
JDBC Authentication provider
• Authenticates against DB
• Can customize queries using .usersByUsernameQuery(), .authoritiesByUsernameQuery(), groupAuthoritiesByUsername()
• Otherwise default queries will be used
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
public void configureGlobal(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(...)
.authoritiesByUsernameQuery(...)
.groupAuthoritiesByUsername(...);
}
}
Password Encoding
• Supports passwords hashing (md5, sha, ...)
• Supports password salting - adding string to password before hashing - prevents decoding passwords using rainbow tables
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
public void configureGlobal(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new StandardPasswordEncoder("this is salt"));
}
}
Login and Logout
@Configuration
@EnableWebSecurity
public class HelloWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().formLogin().loginPage("/login.jsp") .permitAll()
.and()
.logout().permitAll();
}
}
Login JSP Form
(c:url var="loginUrl" value="/login.jsp" /)
(form:form action="${loginUrl}" method="POST")
(input type="text" name="username"/)
(input type="password" name="password"/)
(input type="submit" name="submit" value="LOGIN"/)
(/form:form)
Spring Security Tag Libraries
• Add taglib to JSP
(%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %)
• Facelet fags for JSF also available
• Displaying properties of Authentication object - (security:authentication property=“principal.username”/)
• Display content only if principal has certain role
(security:authorize access="hasRole('ADMIN')")
(p)Admin only content(/p)
(/security:authorize)
• Or inherit the role required from specific url (roles required for specific urls are centralized in config and not across many JSPs)
(security:authorize url="/admin")
(p)Admin only content(/p)
(/security:authorize)
Method Security
• Methods (e.g. service layer) can be secured using AOP
• JSR-250 or Spring annotations or pre/post authorize
JSR-250
• Only supports role based security
• @EnableGlobalMethodSecurity(jsr250Enabled=true) on @Configuration to enable
• On method level @RolesAllowed("ROLE_ADMIN")
Spring @Secured Annotations
• @EnableGlobalMethodSecurity(securedEnabled=true) on @Configuration to enable
• @Secured("ROLE_ADMIN") on method level
• Supports not only roles - e.g. @Secured("IS_AUTHENTIC@ED_FULLY")
• SpEL not supported
Pre/Post authorize
• @EnableGlobalMethodSecurity(prePostEnabled=true) on @Configuration to enable
• Pre authorize - can use SpEL (@Secured cannot), checked before annotated method invocation
• Post authorize - can use SpEL, checked after annotated method invocation, can access return object of the method using returnObject variable in SPEL; If expression resolves to false, return value is not returned to caller
• @PreAuthorize("hasRole('ROLE_ADMIN')")
Spring Web
• Provides several web modules
o Spring MVC - Model View Controller framework
o Spring Web Flow - Navigation flows (wizard-like)
o Spring Social - Integration with facebook, twitter, ...
o Spring mobile - switching between regular and mobile versions of site
• Spring can be integrated with other web frameworks, some integration provided by Spring itself
o JSF
o Struts 2 (Struts 1 no longer supported as of Spring 4+)
o Wicket
o Tapestry 5
o ...
• Spring web layer on top of regular spring application layer
• Initialized using regular servlet listener
• Can be configured either in web.xml or using AbstractContextLoaderInitializer - implements WebApplicationInitializer, which is automatically recognized and processed by servlet container (Servlets 3.0+)
Basic configuration
AbstractContextLoaderInitializer
public class WebAppInitializer extends AbstractContextLoaderInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
//Configure app context here
return applicationContext;
}
}
• Servlet container looks for classes implementing ServletContainerInitializer (Spring provides SpringServletContainerInitializer)
• SpringServletContainerInitializer looks for classes implementing WebApplicationInitializer, which specify configuration instead of web.xml
• Spring provides two convenience implementations
o AbstractContextLoaderInitializer - only Registers ContextLoaderListener
o AbstractAnnotationConfigDispatcherServletInitializer - Registers ContextLoaderListener and defines Dispatcher Servlet, expects JavaConfig
web.xml
• Register spring-provided Servlet listener
• Provide spring config files as context param - contextConfigLocation
• Defaults to WEB-INF/applicationContext.xml if not provided
• can provide spring active profiles in spring.profiles.active
(context-param)
(param-name)contextConfigLocation(/param-name)
(param-value)
/WEB-INF/config.xml
/WEB-INF/other-config.xml
(/param-value)
(/context-param)
(listener)
(listener-class)
org.springframework.web.context.ContextLoaderListener
(/listener-class)
(/listener)
(context-param)
(param-name)spring.profiles.active(/param-name)
(param-value)profile1,profile2(/param-value)
(/context-param)
Dependency injection in servlets
• in init() of HttpServlet cannot access spring beans as they are not available yet
• need to use WebApplicationContextUtils - provides application Context through Servlet Context
• Only needed in servlets - other components such as @Controllers can use regular Dependency Injection
public class MyServlet extends HttpServlet {
private MyService service;
public void init() {
ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
service = (MyService) context.getBean("myService");
}
...
}
Spring Web flow
• Provides support of stateful navigation flows
• Handles browser back button
• Handles double submit problem
• Support custom scopes of beans - flow scope, flash scope, ...
• Configuration of flows defined in XML
o Defines flow states - view, action, end, ...
o Defines transition between states
Spring MVC
• Spring Web Framework based on Model-View-Controller pattern
• Alternative to JSF, Struts, Wicket, Tapestry, ...
• Components such as Controllers are Spring-managed beans
• Testable POJOs
• Uses Spring Configuration
• Supports a wide range of view technologies - JSP, Freemarker, Velocity, Thymeleaf, ...
Dispatcher Servlet
• Front controller pattern
• Handles all incoming requests and delegates them to appropriate beans
• All the web infrastructure beans are customizable and replaceable
• DS is defined as servlet in web.xml or through WebApplicationInitializer interface
• Separate Web Application context on top of regular app context
• Web app context can access beans from root context but not vice versa
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//@Configuration classes for root application context
@Override
protected Class(?)[] getRootConfigClasses() {
return new Class(?)[]{ RootConfig.class };
}
//@Configuration classes for web application contet
@Override
protected Class(?)[] getServletConfigClasses() {
return new Class(?)[]{ WebConfig.class };
}
//Urls handled by Dispatcher Servlet
@Override
protected String[] getServletMappings() {
return new String[]{"/app/*"};
}
...
}
Controllers
• Annotated with @Controller
• @RequestMapping annotation on controller's methods specifies which requests should the method handle
@RequestMapping
• Can specify URL, which annotated method should handle - @RequestMapping("/foo")
o =) server-url/app-context-root/servlet-mapping/request-mapping
o can use wildcards @RequestMapping("/foo/*")
• Can specify HTTP method, which annotated method should handle
@RequestParam
• Can specify parameter from http request to be injected as method parameter
@RequestMapping("/foo")
public String foo(@RequestParam("bar") String bar) {
...
}
• This will extract value "xxx" from requested url /foo?bar=xxx
@PathVariable
• Can extract value as a method parameter from the url requested
• eg. Extract "1" from /persons/1
@RequestMapping("/persons/{personId}")
public String foo(@PathVariable("personId") String personId) {
...
}
• Other method parameters can be injected as well - @RequestHeader(), @Cookie,...
• Some parameters injects spring by type - HttpServletRequest, HttpServletResponse, Principal, HttpSession, ...
Views
• Views render output to the client based on data in the model
• Many built-in supported view technologies - Freemarker, Velocity, JSP, ...
• ViewResolver selects specific View based on logical view name returned by the controller methods
View Resolver
• Translates logical view name to actual View
• This way controller is not coupled to specific view technology (returns only logical view name)
• Default View resolver already configured is InternalResourceViewResolver, which is used to render JSPs (JstlView). Configures prefix and suffix to logical view name which then results in a path to specific JSP.
• Can register custom resolvers
View Resolution Sequence
1. Controller returns logical view name to DispatcherServlet.
2. ViewResolvers are asked in sequence (based on their Order).
3. If ViewResolver matches the logical view name then returns which View should be used to render the output. If not, it returns null and the chain continues to the next ViewResolver.
4. Dispatcher Servlet passes the model to the Resolved View and it renders the output.
Spring MVC Quick Start
1. Register Dispatcher servlet (web.xml or in Java)
2. Implement Controllers
3. Register Controllers with Dispatcher Servlet
o Can be discovered using component-scan
4. Implement Views
o eg. write JSP pages
5. Register View resolver or use the default one
o Need to set prefix (eg. /WEB-INF/views/) and suffix (eg. .jsp)
6. Deploy
Spring Boot
Basics
• Convention over configuration - pre-configures Spring app by reasonable defaults, which can be overridden
• Maven and Gradle integration
• MVC enabled by having spring-boot-starter-web as a dependence
o Registers Dispatcher servlet
o Does same as @EnableWebMvc + more
o Automatically serves static resources from /static, /public, /resources or /META-INF/resources
o Templates are served from /templates (Velocity, FreeMarker, Thymeleaf)
o Registers BeanNameViewResolver if beans implementing View interface are found
o Registers ContentNegociatingViewResolver, InternalResourceViewResolver (prefix and suffix configurable in application.properties)
o Customisation of auto-configured beans should be done using WebMvcConfigurerAdapter as usual
@SpringBootApplication
• Main Class annotated with @SpringBootApplication, can be run as a jar with embedded application server (Tomcat by default, can be changed for example to Jetty or Undertow)
• Actually consists of three annotations @Configuration, @EnableAutoConfiguration and @ComponentScan
• @EnableAutoConfiguration configures modules based on presence of certain classes on classpath - based on @Conditional
• Manually declared beans usually override beans automatically created by AutoConfiguration (@ConditionalOnMissingBean is used), usually bean type and not name matters
• Can selectively exclude some AutoConfigutation classes @EnableAutoConfiguration(exclude=DataSourceAutoConfiguration.class)
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
//run in embedded container
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
//run as war, needs to have (packaging)war(/packaging) in pom.xml
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
Dependencies
• Need to add proper maven parent and dependencies
• Using "starter" module dependencies → using transitive dependencies bundles versions which are tested to work well together
o Can override version of specific artifacts in pom.xml
(properties)
(spring.version)4.2.0.RELEASE(/spring.version)
(/properties)
• spring-boot-starter, spring-boot-starter-web, spring-boot-starter-test, ...
• Parent pom defines all the dependencies using dependency management, specific versions are not defined in our pom.xml
• Only version which needs to be specifically declared is parent pom version
(dependency)
(groupId)org.springframework.boot(/groupId)
(artifactId)spring-boot-starter(/artifactId)
(/dependency)
(parent)
(groupId)org.springframework.boot(/groupId)
(artifactId)spring-boot-starter-parent(/artifactId)
(version)1.3.0.RELEASE(/version)
(/parent)
Application Configuration
• Application configuration is externalized by default to application.properties file
• Alternatively, can use YAML configuration - application.yml by default
• Located in workingdirectory/config or working directory or classpath/config or classpath
• PropertySource automatically created
Configuration resolution sequence
1. Check /config subdirectory of the working directory for application.properties file
2. Check working directory
3. Check config package in the classpath
4. Check classpath root
5. Create property source based on files found
Logging
• By default Logback over SLF4J
• By default logs to console, but can define log file
#Logging through SLF4J
logging.level.org.springframework=DEBUG
logging.level.com.example=INFO
logging.file=logfile.log
#OR spring.log file in to configured path
logging.path=/log
To change logging framework from default logback
1. Exclude logback dependency ch.qos.logback.logback-classic
2. Add desired logging framework dependency - eg. org.slf4j.slf4j-log4j12
DataSource
• either include spring-boot-starter-jdbc or spring-boot-starter- data-jpa
• JDBC driver required on classpath, datasource will be created automatically
• Tomcat JDBC as default pool, other connection pools can be used if present - eg.HikariCP
#Connection
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=
#Scripts to be executed
spring.datasource.schema=
spring.datasource.data=
#Connection pool
spring.datasource.initial-size=
spring.datasource.max-active=
spring.datasource.max-idle=
spring.datasource.min-idle=
Web Container
server.port=
server.address=
server.session-timeout=
server.context-path=
server.servlet-path=
[A]Web container can be configured in Java dynamically by implementing EmbeddedServletContainerCustomizer interface and registering resulting class as a @Component
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8081);
container.setContextPath("/foo");
}
Or if needed more fine-grained configuration - declare bean of type EmbeddedServletContainerFactory
YAML
• YAML - Yaml Ain't a Markup Language
• Alternative to configuration in .properties file
• Configuration can be hierarchical
server:
port:
address:
session-timeout:
context-path:
servlet-path:
• YAML can contain configuration based on spring profiles
o Divided by ---
---
spring.profiles: development
database:
host: localhost
user: dev
---
spring.profiles: production
database:
host: 198.18.200.9
user: admin
• Or each profile can have its own dedicated file
o application-profilename.yml
@ConfigurationProperties
• Class is annotated with @ConfigurationProperties(prefix= "com.example")
• Fields of the class are automatically injected with values from properties
• @ConfigurationProperties(prefix= "com.example") + com.example.foo → foo field injected
@ConfigurationProperties(prefix="com.example")
public class MyProperties {
private String foo; //com.example.foo
private int bar; //com.example.bar, type conversion applied
}
• Needs to be enabled on @Configuration class - @EnableConfigurationProperties(MyProperties.class)
Embedded container
• Spring boot can run embedded application server from a jar file
• spring-boot-starter-web includes embedded Tomcat, can change to Jetty
o spring-boot-starter-jetty as a dependency
o exclude spring-boot-starter-tomcat dependency from spring-boot-starter-web
• Embedded app server recommended for Cloud Native applications
• Spring boot can produce jar or war (change packaging to war, extend SpringBootServletInitializer, override configure method)
@ComponentScan
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
//Still executable through traditional main method
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
• war can still be executed java -jar myapp.war or deployed to app server
• when using spring-boot-maven-plugin, mvn package produces two jars
o traditional jar without dependencies
o executable jar with all dependencies included
• Both war and jar with embedded app server are valid options
@Conditional
• Enables bean instantiatiation only when specific condition is met
• Core concept of spring boot
• Only when specific bean found - @ConditionalOnBean(type={DataSource.class})
• Only when specific bean is not found - @ConditionalOnMissingBean
• Only when specific class is on classpath - @ConditionalOnClass
• Only When specific class is not present on classpath - @ConditionalOnMissingClass
• Only when system property has certain value - @ConditionalOnProperty(name="server.host", havingValue="localhost")
• @Profile is a special case of conditional
• org.springframework.boot.autoconfigure contains a lot of conditionals for auto-configuration
o eg. @ConditionalOnMissingBean(DataSource.class) → Created embedded data source
o Specifically declared beans usually disable automatically created ones
o If needed, specific autoconfiguration classes can be excluded explicitly
o @EnableAutoConfiguration(exclude=DataSourceAutoConfiguration.class)
Testing
• Can use same configuration as Spring Boot application in tests without invoking main method
• @SpringApplicationConfiguration(classes= MyApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MyApplication.class)
public class FooServiceTest {
...
}
• Or for testing web app - @WebAppConfiguration
• Initializes web app context
Data Management With Spring
• Spring supports many data-access technologies
• No matter what technology is used, consistent way of using
• JDBC, JPA, Hibernate, MyBatis, ...
• Spring manages most of the actions under the hood
o Accessing data source
o Establishing connection
o Transactions - begin, rollback, commit
o Close the connection
• Declarative transaction management (transactions through AOP proxies)
Exception handling
• Provides own set of exception so exceptions are not specific to underlying technology used
• Replaces checked exceptions with unchecked
o Checked exceptions provide a form of tight coupling between layers
o If exception is not caught must be declared in method signature
o Exceptions from specific data access technology leak to service layer of the app - layers no more loosely coupled
• Spring provides DataAccessException
o Does not reveal underlying specific technology (JPA, Hibernate, JDBC, ...)
o Is not single exception but hierarchy of exceptions
o DataAccessResource FailureException, DataIntegrityViolationException, BadSqlGrammarException, OptimisticLocking FailureException, ...
o Unchecked (Runtime)
In-Memory Database
• Spring can create embedded DB and run specified init scripts
• H2, HSQL, Derby supported
• Using EmbeddedDatabaseBuilder
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
builder.setName("inmemorydb")
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:/inmemorydb/schema.db")
.addScript("classpath:/inmemorydb/data.db");
return builder.build();
}
Or in XML
(jdbc:embedded-database id="dataSource" type="HSQL")
(jdbc:script location="classpath:schema.sql" /)
(jdbc:script location="classpath:data.sql" /)
(/jdbc:embedded-database)
Alternatively, existing DB can be initialized with DataSource provided
(jdbc:initialize-database data-source="dataSource")
(jdbc:script location="classpath:schema.sql" /)
(jdbc:script location="classpath:data.sql" /)
(/jdbc:initialize-database)
Or the same in Java
@Configuration
public class DatabaseInitializer {
@Value("classpath:schema.sql")
private Resource schemaScript;
@Value("classpath:data.sql")
private Resource dataScript;
private DatabasePopulator databasePopulator() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(schemaScript);
populator.addScript(dataScript);
return populator;
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator());
return initializer;
}
}
Caching
• Spring bean's method results can be cached
• Cache here is a map of cache-key, cached value
• Multiple caches supported
• Caching enabled using
o @EnableCaching on @Configuration class level
o or in xml (cache:annotation-driven /)
@Cacheable
• Marks method as cacheable
• Its result will be stored in the cache
• Subsequent calls of the method with the same arguments will result in data being fetched from the cache
• Defines value (name of the cache - multiple caches supported) and key (key in given cache)
• key can use SpEL expressions
• Can also use condition, which uses SpEL
@Cacheable(value="mycache", key="#personName.toUpperCase()", condition="#personName.length ( 50")
public Person getPerson(String personName) {
...
}
• @CacheEvict(value="cacheName") clears cache before annotated method is called
Or alternatively can define caching in XML
(bean id="myService" class="com.example.MyService"/)
(aop:config)
(aop:advisor advice-ref="cacheAdvice" pointcut="execution(* *..MyService.*(..))"/)
(/aop:config)
(cache:advice id="cacheAdvice" cache-manager="cacheManager")
(cache:caching cache="myCache")
(cache:cacheable method="myMethod" key="#id"/)
(cache:cache-evict method="fetchData" all-entries="true" /)
(/cache:caching)
(/cache:advice)
Caching Manager
• Cache manager must be specified
• SimpleCacheManager, EHCache, Gemfire, Custom, ...
SimpleCacheManager
@Bean
public CacheManager cacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
Set(Cache) caches = new HashSet(Cache)();
caches.add(new ConcurrentMapCache("fooCache"));
caches.add(new ConcurrentMapCache("barCache"));
manager.setCaches(caches);
return manager;
}
EHCache
@Bean
public CacheManager cacheManager(CacheManager ehCache) {
EhCacheCacheManager manager = new EhCacheCacheManager();
manager.setCacheManager(ehCache);
return manager;
}
@Bean EhCacheManagerFactoryBean ehCacheManagerFactoryBean(String configLocation) {
EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
factory.setConfigLocation(context.getResource(configLocation));
return factory;
}
Gemfire
• Distributed cache replicated across multiple nodes
• GemfireCacheManager
• Supports transaction management - GemfireTransactionManager
(gfe:cache-manager p:cache-ref="gemfire-cache"/)
(gfe:cache id="gemfire-cache"/)
(gfe:replicated-region id="foo" p:cache-ref="gemfire-cache"/)
(gfe:partitioned-region id="bar" p:cache-ref="gemfire-cache"/)
JDBC with Spring
• Using traditional JDBC connection has many disadvantages
o Lot of boilerplate code
o Poor exception handling (cannot really react to exceptions)
• Spring JDBC Template to the rescue
• Implementing template method pattern
• Takes care of all the boilerplate
o Establishing connection
o Closing connection
o Transactions
o Exception handling
• Handles SQLExceptions properly -
• Transforms SQLExceptions into DataAccessExceptions
• Great simplification - user just performs queries
• Create one instance and reuse it - thread safe
• Uses runtime exceptions - no try-catch needed
• Can query for
o primitives, String, Date, ...
o Generic maps
o Domain objects
JdbcTemplate template = new JdbcTemplate(dataSource);
Query for simple object
template.queryForObject(sqlStatement, Date.class);
• Can bind variables represented by ? in the query
• Values provided as varargs in the queryFor method
• Order of ? matches order of vararg arguments
String sqlStatement = "select count(*) from ACCOUNT where balance ) ? and type = ?";
accountsCount = jdbcTemplate.queryForObject(sqlStatement, Long.class, balance, type);
Query for generic collection
• Each result row returned as map of (Column name / Field value) pairs
• queryForList - when expecting multiple results
• queryForMap - when expecting single result
Query For single row
jdbcTemplate.queryForMap("select * from ACCOUNT where id=?", accountId);
Results in Map(String, Object) {ID=1, BALANCE=75000, NAME="Checking account", ...}
Query for multiple rows
return jdbcTemplate.queryForList("select * from ACCOUNT");
Results in List(Map(String,Object)) { Map(String, Object) {ID=1, BALANCE=75000, NAME="Checking account", ...} Map(String, Object) {ID=2, BALANCE=15000, NAME="Checking account", ...} ... }
Query for domain object
• May consider ORM for this
• Must implement RowMapper interface, which maps row of ResultSet to a domain object
public interface RowMapper(T) {
T mapRow(ResultSet resultSet, int rowNum) throws SQLException;
}
Query using query for object
//Single domain object
Account account = jdbcTemplate.queryForObject(sqlStatement, rowMapper, param1, param2);
//Multiple domain objects
List(Account) = jdbcTemplate.query(sqlStatement, rowMapper, param1, param2);
Alternatively, RowMapper can be replaced by lambda expression
RowCallbackHandler
• Should be used when no return object is needed
o Converting results to xml/json/...
o Streaming results
o ...
• Can be replaced by lambda expression
public interface RowCallbackHandler {
void processRow(ResultSet resultSet) throws SQLException;
}
jdbcTemplate.query(sqlStatement, rowCallbackHandler, param1, param2);
ResultSetExtractor
• Can map multiple result rows to single return object
• can be replaced by lambda
public interface ResultSetExtractor(T) {
T extractData(ResultSet resultSet) throws SQLException, DataAccessException;
}
jdbcTemplate.query(sqlStatement, resultExtractor, param1, param2);
Inserts and updates
• returns number of rows modified
• jdbcTemplate.update() used both for updates and inserts
numberOfAffectedRows = jdbcTemplate.update(sqlStatement, param1, param2);
Transactions
• Set of operations that take place as a single atomic unit - all or nothing
o E.g. Money transfer operation consists of deducting money from source account and adding them to the target account. If one part fails, other cannot be performed.
• ACID
o Atomic - All operations are performed or none of them
o Consistent - DB integrity constraints are not violated
o Isolated - Transactions are isolated from each other
o Durable - Committed changes are permanent
• When in transaction, one connection should be reused for the whole transaction
Java Transaction Management
• Local transaction - One resource is involved, transaction managed by resource itself (eg. JDBC)
• Global transaction - Multiple different resources involved in transaction, transaction managed by external Transaction Manager (e.g. JMS + two different databases)
• Multiple modules supporting transactions - JDBC, JPA, JMS, JTA, Hibernate
• Different API for global transactions and local transactions
• Different modules have different transaction API
• Usually programmatic transaction management - not declarative as in spring - need to call eg. begin transactions, commit, etc.
• Transaction management is cross-cutting concern and should be centralized instead scattered across all the classes
• Transaction demarcation should be independent on actual transaction implementation
• Should be done in service layer instead of data access layer
Spring Transaction Management
• Declarative transaction management instead of programmatic
• Transaction declaration independent on transaction implementation
• Same API for global and local transactions
• Hides implementation details
• Can easily switch transaction managers
• Can easily upgrade local transaction to global
• To enable transaction management in spring
o enable transaction management
Java - @EnableTransactionManagement
[A] (tx:annotation-driven/) in XML
o Declare a PlatformTransactionManager bean - "transactionManager" is default bean name
o Mark methods as transactional (java or XML)
Transaction Manager
• Spring provides many implementations of PlatformTransactionManager interface
o JtaTransactionManager
o JpaTransactionManager
o HibernateTransactionManager
o DataSourceTransactionManager
o ...
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Transactional
• Methods which require being run in transaction should be annotated with @Transactional
• Object is wrapped in a proxy
o Around advice is used
o Before entering method a transaction is started
o If everything goes smoothly, there is commit once the method is finished
o If method throws RuntimeException, rollback is performed, transaction is not committed
• Can be applied either to method level or class level. If on class level, applies to all the methods declared by implementing interfaces (!). Can be also applied to interface or parent class.
• Can combine method level and class level on the same class. Method level configuration then overrides class level.
• If a test method is annotated @Transactional, it is run in transaction and rolled back after finished
o Can be both on method and class level
o Can add @Commit annotation on method level to override @Transactional on test class level
Isolation Levels
• 4 isolation levels available (from least strict to the most strict)
i. READ_UNCOMMITTED
ii. READ_COMMITTED
iii. REPE@ABLE_READ
iv. SERIALIZABLE
• Not all isolation levels may be supported in all databases
• Different databases may implement isolation is slightly different ways
READ_UNCOMMITTED
• @Transactional (isolation=Isolation.READ_UNCOMMITTED)
• The lowest isolation level
• Dirty reads can occur - one transaction may be able to see uncommitted data of other transaction
• May be viable for large transactions or frequently changing data
READ_COMMITTED
• @Transactional (isolation=Isolation.READ_COMMITTED)
• Default isolation strategy for many databases
• Other transactions can see data only after it is properly committed
• Prevents dirty reads
REPE@ABLE_READ
• @Transactional (isolation=Isolation.REPE@ABLE_READ)
• Prevents non-repeatable reads - when a row is read multiple times in a single transaction, its value is guaranteed to be the same
SERIALIZABLE
• @Transactional (isolation=Isolation.SERIALIZABLE)
• Prevents phantom reads
Transaction Propagation
• Happens when code from one transaction calls another transaction
• Transaction propagation says whether everything should be run in single transaction or nested transactions should be used
• There are 7 levels of propagation
• 2 Basic ones - REQUIRED and REQUIRES_NEW
REQUIRED
• @Transactional(propagation=Propagation.REQUIRED)
• Default value if not specified otherwise
• If there is already transaction, new @Transactional code is run in existing transaction, otherwise a new transaction is created
REQUIRES_NEW
• @Transactional(propagation=Propagation.REQUIRES_NEW)
• If there is no transaction, a new one is created
• If there already is a transaction, it is suspended and a new transaction is created
Rollback
• By default rollback is performed if RuntimeException (or any subtype) is thrown in @Transactional method
• This can be overriden by rollbackFor and noRollbackFor attributes
@Transactional(rollbackFor=MyCheckedException.class, noRollbackFor={FooException.class, BarException.class})
JPA with Spring
• JPA - Java Persistence API
• ORM mapping
• Domain objects POJOs
• Common API for object relational mapping
• Several implementations of JPA
o Hibernate EntityManager
o EclipseLink - Glassfish
o Apache OpenJPA - WebLogic and WebSphere
o Data Nucleus - Google App Engine
• Can be used in spring without app server
Spring JPA Configuration
1. Define EntityManagerFactory bean
2. Define DataSource bean
3. Define TransactionManagement bean
4. Define DAOs
5. Define metadata Mappings on entity beans
EntityManager
• Manages unit of work and involved persistence objects (Persistence context)
• Usually in transaction
• Some API
o persist(Object object) - adds entity to persistence context - INSERT INTO ...
o remove(Object object) - removes entity from persistence context - DELETE FROM ...
o find(Class entity, Object primaryKey) - Find by primary key - SELECT * FROM ... WHERE id=primaryKey
o Query createQuery(String queryString) - Used to create JPQL query
o flush() - Current state of entity is written to DB immediately ...
Entity Manager Factory
• Provides access to new app managed Entity Managers
• Thread safe
• Represents single data source / persistence unit
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
adapter.setGenerateDdl(true);
adapter.setDatabase(Database.HSQL);
Properties properties = new Properties();
properties.setProperty("hibernate.format_sql", "true");
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource());
emfb.setPackagesToScan("com.example");
emfb.setJpaProperties(properties);
emfb.setJpaVendorAdapter(adapter);
return emfb;
}
@Bean
public DataSource dataSource() {
...
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
Or entityManagerFactory XML Equivalent
(bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean")
(property name="dataSource" ref="dataSource"/)
(property name="packagesToScan" value="com.example"/)
(property name="jpaVendorAdapter")
(bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter")
(property name="showSql" value="true"/)
(property name="generateDdl" value="true"/)
(property name="database" value="HSQL"/)
(/bean)
(/property)
(property name="jpaProperties")
(props)
(prop key="hibernate.format_sql")true(/prop)
(/props)
(/property)
(/bean)
Persistence Unit
• Group of persistent classes - entities
• Has persistence provider
• Has assigned transaction type
• App can have multiple persistence units
• In spring configuration of persistence unit can be either in spring configuration, persistence unit itself or combination
JPA Mapping
• Metadata is required to determine mapping of class fields to DB columns
• Can be done using either annotations or XML (orm.xml)
• Many defaults provided - configuration needed only when differs from the defaults
Can annotate
• Classes
o Configuration applies to entire class
o Class annotated @Entity
o Table name can be overridden using @Table(name="TABLE_NAME"), if not specified class name is used
• Fields
o Usually mapped to columns
o By default all considered persistent
o Can annotate @Transient if should not be persistent
o Accessed directly though reflection
o @Id can be used to mark primary key field, can be placed also on getter
o @Column(name="column_name") can be used to specify column name, if not specified, field name is used as default, can be also placed on getter
o @Entity and @Id are mandatory, rest is optional
o @JoinColumn (name="foreignKey")
o @OneToMany
• Getters
o Maps to columns like fields
o Alternative to annotating fields directly
@Embeddable
• One row can be mapped to multiple entities
• Eg. Person, which is one DB table contains personal info as well as address info
• Address can be embedded in Person entity
o Address as separate entity annotated with @Embeddable
o In Person class, Address field is annotated with @Embedded
o Can use @AttributeOverride annotation to specify mappings from columns to fields
JPA Queries
By Primary key
• entityManager.find(Person.class, personId)
• Returns null if no item found
• Uses generics - no cast needed
JPQL Queries
• JPQL - JPA Query Language
TypedQuery(Person) query = entityManager.createQuery("select p from Person p where p.firstname = :firstName", Person.class);
query.setParameter("firstName", "John");
List(Person) persons = query.getResultList();
List(Customer) persons = entityManager.createQuery("select p from Person p where p.firstname = :firstName", Person.class).setParameter("firstName", "John").getResultList();
Person person = query.getSingleResult();
• Criteria Queries
• Native Queries - JDBC template preferred instead
JPA DAOs
• JPA DAOs in Spring
o Transactions are handled in Service Layer - Services wrapped in AOP proxies - Spring TransactionInterceptor in invoked
o TransactionInterceptor delegates to TransactionManager (JpaTransactionManager, JtaTransactionManager)
o Services call DAOs with no spring dependency
o Services are injected with AOP-proxied EntityManager
o Proxied EntityManager makes sure queries are properly joined into active transaction
• DAO implementations have no spring dependencies
• Can use AOP for exception translation - JPA exceptions translated to Spring DataAccessExceptions
• Can use @PersistenceContext annotation to inject EntityManager proxy
public class JpaPersonRepository implements PersonRepository {
private EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
//Use EntityManager to perform queries
//If this repository is not annotated as spring component (@Repository), and explicitly declared in @Configuration
//file, there is no spring dependency in the DAO layer - @PersistenceContext is from javax.persistence
}
JPA With Spring Data
• Simplifies data access by reducing boilerplate code
• Consists of core project and many subprojects for various data access technologies - JPA, Gemfire, MongoDB, Hadoop, ...
1. Domain classes are annotated as usual
2. Repositories are defined just as interfaces (extending specific Spring interface), which will be dynamically implemented by spring
Spring data Domain Classes
• For JPA nothing changes, classes are annotated as usual - @Entity
• Specific data stores have their own specific annotations
o MongoDB - @Document
o Gemfire - @Region
o Neo4J - @NodeEntity, @GraphId
Spring data Repositories
• Spring searches for all interfaces extending Repository(DomainObjectType, DomainObjectIdType)
• Repository is just marker interface and has no method on its own
• Can annotate methods in the interface with @Query("Select p from person p where ...")
• Can extend CrudRepository instead of Repository - added methods for CRUD
o Method names generated automatically based on naming convention
o findBy + Field (+ Operation)
o FindByFirstName(String name), findByDateOfBirthGt(Date date), ...
o Operations - Gt, Lt, Ne, Like, Between, ...
• Can extend PagingAndSortingRepository - added sorting and paging
• Most Spring data sub-projects have their own variations of Repository
o JpaRepository for JPA
• Repositories can be injected by type of their interface
public interface PersonRepository extends Repository(Person, Long) {}
@Service
public class PersonService {
@Autowired
private PersonRepository personRepository;
...
}
Repository lookup
• Locations, where spring should look for Repository interfaces need to be explicitly defined
@Configuration
@EnableJpaRepositories(basePackages="com.example.**.repository")
public class JpaConfig {...}
@Configuration
@EnableGemfireRepositories(basePackages="com.example.**.repository")
public class GemfireConfig {...}
@Configuration
@EnableMongoRepositories(basePackages="com.example.**.repository")
public class MongoDbConfig {...}
(jpa:repositories base-package="com.example.**.repository" /)
(gfe:repositories base-package="com.example.**.repository" /)
(mongo:repositories base-package="com.example.**.repository" /)
No comments:
Post a Comment