I'm learning about spring data and because I'm also learning about kotlin so I decided to work with kotlin during spring learning. So I want to ask how we can Implement setter dependency injection in kotlin? as in Java we can that as below.
#Component
public class StudentDaoImp {
public DataSource dataSource;
public JdbcTemplate jdbcTemplate;
public DataSource getDataSource() {
return dataSource;
}
#Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
And here is my spring.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/test" />
</bean>
<context:component-scan base-package="com.package.*" />
</beans>
Then I tried it in kotlin.
#Component
class StudentDao {
#Autowired
lateinit var dataSource: DataSource
var jt = JdbcTemplate(dataSource)
}
Then I'm getting the exception.
Caused by: kotlin.UninitializedPropertyAccessException: lateinit
property dataSource has not been initialized
I know about this exception because I'm using dataSource before autowired happen. So I have also tried this.
#Autowired
fun setDataSource(dataSource: DataSource) {
this.jt = JdbcTemplate(dataSource)
}
This is also an error, because JVM already have that signature behind the scene.
So how I can Initialize JdbcTemplate with the dataSourceparameter?
Note: I only want Code side example/solution. I know about the XML solution.
like in Java you can't use properties that are injected after instantiation at instantiation time. In Java you'd get a NullPointerException when you would do the equivalent, e.g.
#Autowired
private Datasource datasource;
private JdbcTemplate jt = new JdbcTemplace(dataSource);
But you can let Spring call a method of your choice after it has injected everything:
lateinit var jt: JdbcTemplate
#PostConstruct
fun initAfterAutowireIsDone() {
jt = JdbcTemplate(dataSource)
}
There is also the InitializingBean interface that can be used instead of the annotation. The same works in Java.
The other option you have in Kotlin is to use a lazy property when you ensure you don't access it at instantiation time.
val jt: JdbcTemplate by lazy { JdbcTemplate(dataSource) }
While I consider zapl answer correct, I would also suggest you to consider constructor arguments injection:
#Component
class StudentDao #Autowired constructor(
private val dataSource: DataSource
) {
private val jt = JdbcTemplate(dataSource)
}
In this way you can obtain non modifiable variables without warning about late initialization.
Also, if you need dataSource variable only to initialize jt, you can remove val key from constructor.
Related
I have tennisCoach object created by Spring framework:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml") ;
Coach theCoach = context.getBean("tennisCoach", Coach.class);
Can't understand why I need #Autowired annotation in TennisCoach constructor in code below. It works fine with and without #Autowired annotation.
#Component
public class TennisCoach implements Coach {
private FortuneService fortuneService;
#Autowired
public TennisCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
#Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}
#Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
UPD
Content of applicationContext.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.luv2code.springdemo"></context:component-scan>
</beans>
From #Autowired Javadoc:
If a class only declares a single constructor to begin with, it will always be used, even if not annotated.
Since Spring 4.3 you don’t need the #Autowired annotation as soon as you have the only constructor in your class.
Here #Autowired is used for constructor injection. TennisCoach has a dependency on FortuneService and it is injected through constructor. I'm not sure how you have configured beans in applicationContext.xml
I use spring 4.3.9.RELEASE
Some configs and code:
#Configuration
#EnableTransactionManagement
public class PersistenceContext {
#Autowired
private DbConfiguration dbConfiguration;
#Bean(destroyMethod = "close")
public DataSource dataSource() {
final BoneCPDataSource dataSource = new BoneCPDataSource();
dataSource.setDriverClass(dbConfiguration.getDriverClassName());
dataSource.setJdbcUrl(dbConfiguration.getUrl());
dataSource.setUsername(dbConfiguration.getUsername());
dataSource.setPassword(dbConfiguration.getPassword());
dataSource.setIdleConnectionTestPeriodInMinutes(dbConfiguration.getIdleConnectionTestPeriod());
dataSource.setIdleMaxAgeInMinutes(dbConfiguration.getIdleMaxAgeInMinutes());
dataSource.setMaxConnectionsPerPartition(dbConfiguration.getMaxConnectionsPerPartition());
dataSource.setMinConnectionsPerPartition(dbConfiguration.getMinConnectionsPerPartition());
dataSource.setPartitionCount(dbConfiguration.getPartitionCount());
dataSource.setAcquireIncrement(dbConfiguration.getAcquireIncrement());
dataSource.setStatementsCacheSize(dbConfiguration.getStatementsCacheSize());
return dataSource;
}
#Bean(name = "txManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
servlet:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:annotation-config/>
<context:component-scan base-package="ru.org.*"/>
<tx:annotation-driven transaction-manager="txManager" />
<mvc:annotation-driven />
</beans>
WebConfig:
#Configuration
#EnableScheduling
#EnableWebMvc
#ComponentScan({"ru.org.*"})
#EnableTransactionManagement
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public MainHandler mainHandler() {
return new MainHandler();
}
}
handler:
public class MainHandler extends AbstractUrlHandlerMapping {
#Autowired
private DataSource dataSource;
protected Object getHandlerInternal(final HttpServletRequest request) throws Exception {
transactionalTest();
}
#Transactional(transactionManager = "txManager")
private void transactionalTest() {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute("INSERT INTO marks(score) VALUES (1);");
jdbcTemplate.execute("IN3ERT INTO marks(score) VALUES (1);");
}
}
It throws an exception but no rollback happened.
Also I tried to use rollbackFor-param and throws exactly that exception.
Any ideas?
There are two major rules of #Transaction annotation you must keep in mind
#Transaction will work if the annotated method is not declared in
the same class that invokes it (True for default Spring Proxy AOP).
#Transaction will work if annotated on a public method. It will always
be ignored if the method is private, protected or package-visible.
So what you should do
Delcare a public transactionalTest() method in a #Service annotated class and then call it in MainHandler class
I have a small Java application which connects to a MySQL database. For database connectivity ONLY, I'd like to use Spring to manage a JNDI based connection pool.
I have a working implementation for the above but this requires manually loading the JNDI connection bean, whereas I'd like to use #Autowired.
How can I convert my working code to one that uses #Autowired to get the JNDI connection ?
This is my beans.xml file (inside src/main/resources folder):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns= ....>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/Database"/>
</bean>
<bean id="databaseMapper2Impl"
class="com.dataaccess.DatabaseMapper2Impl">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
The is a section of my DatabaseMapper2Impl class:
public class DatabaseMapper2Impl {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public OrderDTO getOrder(String orderNumber, String envToUse) {
String getOrderSql = "SELECT * FROM REPORTING.ORDER where ORDER_NUMBER = ? limit 1";
List<OrderDTO> orders = jdbcTemplateObject.query(getOrderSql, new Object[] { orderNumber }, new OrderMapper());
if (orders == null || orders.isEmpty()) {
return null;
} else {
return orders.get(0);
}
}
}
This is the class where the JNDI connection bean is manually instantiated:
public class DataDelegateImpl {
public OrderDTO getOrder(String orderNumber, String envToUse) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
DatabaseMapper2Impl x = (DatabaseMapper2Impl) context.getBean("databaseMapper2Impl");
return x.getOrder(orderNumber, envToUse);
}
}
In order for Spring to manage the instantiation and injection of your DatabaseMapper2Implbean, you will have to create an ApplicationContext, and declare the bean in that context, exactly as you have done.
If the question is just how to avoid using XML for that declaration, you could annotate DatabaseMapper2Impl with #Component and instead use
ApplicationContext context = new AnnotationConfigApplicationContext(DatabaseMapper2Impl.class);
to create the context.
If you really need to have the DatabaseMapper2Impl #Autowired into an instance of DataDelegateImpl, then that instance would also have to be controlled by Spring, so you'd have to create the context at a higher level and make the DataDelegateImpl a bean as well.
I'm experimenting with spring-security for authentication and want to be able to add users at runtime. I figured using UserDetailsManager would be minimally intrusive. How do I make it available as a bean, so that I can access it in controllers and other objects?
The Code I was starting with is as follows:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource, PasswordEncoder enc) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).withDefaultSchema().passwordEncoder(enc)
.withUser("user").password(enc.encode("password")).roles("USER").and()
.withUser("admin").password(enc.encode("password")).roles("USER", "ADMIN");
}
jdbcAuthentication() creates a JdbcUserDetailsManager, everything works fine. But I don't know to how to access that after the web app's initialization. I tried two variants that didn't work:
#Bean
public UserDetailsManager userDetailsManager(DataSource dataSource,PasswordEncoder enc) throws Exception {
return new JdbcUserDetailsManagerConfigurer<>().dataSource(dataSource).withDefaultSchema().passwordEncoder(enc)
.withUser("user").password(enc.encode("password")).roles("USER").and()
.withUser("admin").password(enc.encode("password")).roles("USER", "ADMIN").and()
.getUserDetailsService();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsManager userDetailsManager) throws Exception {
auth.userDetailsService(userDetailsManager);
}
When filling out the login form, I get the following:
Table "USERS" not found; SQL statement:
select username,password,enabled from users where username = ? [42102-185]
So it seems that this does not initialize the bean properly. Second try:
#Bean
public UserDetailsManager userDetailsManager(AuthenticationManagerBuilder auth, DataSource dataSource, PasswordEncoder enc) throws Exception {
return auth.jdbcAuthentication().dataSource(dataSource).withDefaultSchema().passwordEncoder(enc)
.withUser("user").password(enc.encode("password")).roles("USER").and()
.withUser("admin").password(enc.encode("password")).roles("USER", "ADMIN").and()
.getUserDetailsService();
}
During initialization, I get:
java.lang.IllegalStateException: Cannot apply ...JdbcUserDetailsManagerConfigurer#3bd97b0d to already built object
So using the builder in an #Bean method does not work either.
jdbcAuthentication() does more than just creating a JdbcUserDetailsManagerConfigurer; most importantly, it registers the configurer using apply(), so that all configurations can later be executed in the right order.
Luckily, apply() is public, so we can call it ourselves. Knowing that, we can move the main configuration load back into configureGlobal, using the configurer's alternate constructor. What worked for me in the end is the following:
#Bean
public JdbcUserDetailsManager userDetailsManager(DataSource dataSource) {
JdbcUserDetailsManager mgr = new JdbcUserDetailsManager();
mgr.setDataSource(dataSource); // (1)
return mgr;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, JdbcUserDetailsManager userDetailsManager, DataSource dataSource, PasswordEncoder enc) throws Exception {
//set user detail service manually
auth.userDetailsService(userDetailsManager);
JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> conf =
new JdbcUserDetailsManagerConfigurer<>(userDetailsManager);
//apply the configurer
auth.apply(conf);
conf.dataSource(dataSource) // (2)
.withDefaultSchema().passwordEncoder(enc)
.withUser("user").password(enc.encode("password")).roles("USER").and()
.withUser("admin").password(enc.encode("password")).roles("USER", "ADMIN");
}
It seems that setting the data source in (1) and (2) is redundant, but leaving either one out results in an exception during initialization:
//without (1)
java.lang.IllegalArgumentException: 'dataSource' or 'jdbcTemplate' is required
//without (2)
java.lang.IllegalStateException: DataSource must be set
The first error is the validation of the bean returned by userDetailsManager(), the second that of the configurer before it is executed by the AuthenticationManagerBuilder.
Configured like this, I can write, e.g. in a controller:
#Autowired
private UserDetailsManager users;
#Autowired
private PasswordEncoder enc;
#RequestMapping(...)
public String handle(Model model) {
users.createUser(new User(username, enc.encode(password), authorities);
return "view";
}
If i got you right, you want to create a bean that handles users, i would suggest declaring it in the spring.xml and using functions that keep adding to the bean.
try using an applicationcontext, this will be on main page
ApplicationContext ac;
ac=new ClassPathXmlApplicationContext("Spring.xml");
prisontest par=(prisontest)ac.getBean("prison");
here, applicationcontext is like a container holding beans and prisontest is class that defines the datasource, u can understand seeing the spring.xml,
the spring.xml will look in this way:
<?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-2.5.xsd">
<bean id="prison" class="prisontest">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/tgmc" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
</beans>
and the bean that refers to the datasource will be like this:
public class prisontest {
DataSource ds;
public void setDataSource(DataSource ds)
{
this.ds=ds;
}
add your code here for managing users on runtime
}
you can also put your other code in the same class, hope this helps.
My application uses struts and spring frameworks. I have a class FormA which has an autowired property in it. When I try to instantiate it while writing unit tests I get a Null Pointer Exception. Here is my code.
My ClassA:
public class FormA{
private String propertyOne;
#Autowired
private ServiceClass service;
public FormA(){
}
}
My unit test method:
#Test
public void testFormA(){
FormA classObj = new FormA();
}
#Autowired only works when object life cycle is managed by Spring.
You'll need to run your tests with #RunWith(SpringJUnit4ClassRunner.class), and instead of instantiating FormA manually, inject it in the test class as well, using #Autowired.
When you create an object by new, autowire\inject don't work...
as workaround you can try this:
create your template bean of NotesPanel
<bean id="notesPanel" class="..." scope="prototype">
<!-- collaborators and configuration for this bean go here -->
</bean>
and create an istance in this way
applicationContext.getBean("notesPanel");
PROTOTYPE : This scopes a single bean definition to have any number of object instances.
anyway a unit test should be
Test class
#RunWith( SpringJUnit4ClassRunner.class )
#ContextConfiguration(locations = { "classpath:META-INF/your-spring-context.xml" })
public class UserServiceTest extends AbstractJUnit4SpringContextTests {
#Autowired
private UserService userService;
#Test
public void testName() throws Exception {
List<UserEntity> userEntities = userService.getAllUsers();
Assert.assertNotNull(userEntities);
}
}
your-spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="userService" class="java.package.UserServiceImpl"/>
</beans>