I'm just starting with DI and Spring. I have these two Components (pseudocode)
#Component
public class AuthHandlerImpl extends ChannelInboundHandlerAdapter implements AuthHandler {
#Autowired
AuthService authService;
#Override
channelRead(ChannelHandlerContext ctx, Object msg) {
authService.authenticate(msg); // want to pass ctx to constructor of authService
}
}
#Component
public class AuthServiceImpl implements AuthService {
private CustomerService customerService;
private ChannelHandlerContext ctx;
#Autowired
public AuthServiceImpl(CustomerService customerService, ChannelHandlerContext ctx) {
this.customerService = customerService;
this.ctx = ctx;
}
}
What I'm trying to achieve is have AuthService injected with the constructor parameters, where one of the constructor arguments is the ChannelHandlerContext from the ChannelInboundHandlerAdapter class. Not sure if that's possible or not.
Yes, it's possible, but CustomerService and ChannerHandlerContext must be defined as spring beans (like #Component, #Service, #Controller or #Bean annotations) in order to be autowired in the constructor. You can check this post to get more information about the topic.
Related
I have a question about spring bean injection in service tasks of Flowable, why only this kind of injection with a static modifier worked, and what is the logic of it?
I must inject a spring bean in a Flowable java service task, and I tested some different kind of injection Field, constructor, and setter injection, eventually setter injection with static modifier worked for me like this :
public class GetCurrentUserDlg implements JavaDelegate {
private static PersonService personService;
#Autowired
public void setPersonService(PersonService personService) {
this.personService = personService;
}
#Override
public void execute(DelegateExecution execution) {
personService.getCurrentUser();
}
}
While I can not answer your question, the following works fine for me:
public class SomeDelegate implements JavaDelegate {
#Autowired
private SomeBean bean;
#Override
public void execute(DelegateExecution execution) {
System.out.println(this.bean);
}
}
The class is then used in the process via flowable:class="packages.SomeDelegate"
But, be aware, that you may have problems with autowiring dependencies in the SomeBean bean. This dependencies are not injected when using the flowable:class attribute. In order for this to work you have to make the SomeDelegate a actual bean itself (e.g. via #Service) and use it in your process definition via flowable:delegateExpression="${someDelegate}"
Example:
#Service("someDelegate")
public class SomeDelegate implements JavaDelegate {
...
and
<serviceTask id="doSomething" name="Do Something" flowable:delegateExpression="${someDelegate}"/>
It should work like this:
#Component
public class GetCurrentUserDlg implements JavaDelegate {
#Autowired
private PersonService personService;
#Override
public void execute(DelegateExecution execution) {
personService.getCurrentUser();
}
}
#Component
public class PersonService {
// its methods
}
i want to call a method which is containing applicationContext which works fine when run independently but when i try calling it from a different class appContext.getBean(DataSource.class) returns null.
public class Action {
private static Logger log = LoggerFactory.getLogger(Action.class);
#Autowired
MessageConfigProperties messageProperties;
#Autowired
AutomatorApp automatorApp;
#Autowired
Apps gapps;
#Autowired
Deploy d;
#Autowired
Deploy depstatusChk;
#Autowired
private ApplicationContext appContext;
#Autowired
CreateSaltFileService createSaltFile;
#Autowired
DeployFactory depFactory;
#Autowired
IMoveAppsService moveAppsService;
#Autowired
IUserEnvironmentService userEnvService;
#Autowired
IEnvironmentService envService;
#Autowired
Session session;
private Logger logger = LoggerFactory.getLogger(Action.class);
private ServletContext context;
#Context
public void setServletContext(ServletContext context) {
System.out.println("servlet context set here");
this.context = context;
}
#POST
#Path("/register/")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#RequiresRoles("admin")
#RequiresPermissions("automator:register")
public Session register(Session credentials, #BeanParam Session springContext) throws AppException {
System.out.println("into action class");
System.out.println("-->>>" +appContext.getBean(DataSource.class));
appContext.getBean(DataSource.class);
logger.info(messageProperties.getGreetings());
// logger.trace("Inside Session");
System.out.println("Inside Session");
credentials.setDatasource(springContext.getDatasource());
when this Action method is called from
this method agentGroup in different class
#POST
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get List of agent group", response =
AgentGroup.class, responseContainer = "List")
public ArrayList<AgentGroup> agentGroup(Session credentials, #BeanParam Session springContext) throws AppException, ConfigException, InterruptedException {
Session r=objA.register(credentials, springContext);
int sessionId=r.getSessionId();
}
You #Autowire ApplicationContext appContext just to get datasource (appContext.getBean(DataSource.class)).
Why do not #Autowire DataSource datasource directly?
It will make make business logic of your Action class independend from Spring.
If you really want to access ApplicationContext you may try another approach
make Action implements ApplicationContextAware
private ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
ctx = applicationContext;
}
See Circular dependency in Spring for details.
edit:
your Action class has no annotation. no org.springframework.web.bind.annotation. RestController, no #Component or #Service. are you sure it is managed by Spring? if it is just created using new then Autowire will not work and fields will be null.
I have my repository interface as
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface MyRepository extends JpaRepository<Tokens, Long>{
}
and the service impl file in where I want to use above repository is not a
#Component or #Service because its object is created using new. So it is not allowing autowiring or injecting the repository in my service impl class.
Is there any other way around to implement in such case?
There are a couple of options available.
First of all, you can use it as a normal dependency and use a factory bean to instantiate your object.
#Component
public class MyFactoryBean {
private Repository repository;
#Autowired
public MyFactoryBean(Repository repository) {
this.repository = repository;
}
public Instance getInstance(Parameter parameter) {
return new Instance(repository, parameter);
}
}
If your parameter does not change at runtime, you can provide a bean in your configuration:
#Configuration
public class MyConfiguration {
private Repository repository;
#Autowired
public MyConfiguration(Repository repository) {
this.repository = repository;
}
#Bean
public Instance instance(){
Parameter parameter = ... // value in a config file or static value
return new Instance(repository, parameter);
}
}
Also, you can use the AutowireCapableBeanFactory to inject dependencies as normal for objects not managed by Spring. Remember that you need to use setter injection tough.
private AutowireCapableBeanFactory beanFactory;
#Autowired
public MyFactoryBean(AutowireCapableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void doStuff() {
MyService service = new MyService();
beanFactory.autowireBean(service); // dependencies will be autowired
}
I want to test a class using Spring + JUnit + Mockito but I don't manage to make it work properly.
Let's say my class references a Service:
#Controller
public class MyController
{
#Autowired
private MyService service;
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
And this Service references a Repository:
#Service
public class MyService {
#Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
When testing the MyController class, I want the service to be mocked. The problem is: even when the service is mocked, Spring tries to inject the repository in the mock.
Here is what I did. Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
#Autowired
private MyController myController;
#Test
public void testDoSomething() {
myController.doSomething();
}
}
Configuration class:
#Configuration
public class MyControllerTestConfiguration {
#Bean
public MyController myController() {
return new MyController();
}
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
And the error I get: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
I tried to initialize the mock using Mockito's #InjectMocks annotation but this fails because the #PostConstruct method is called before the mocks injection, generating a NullPointerException.
And I cannot simply mock the repository because in real life that would make me mock A LOT of classes...
Can anyone help me on this?
Use constructor instead of field injection. That makes testing a lot easier.
#Service
public class MyService {
private final MyRepository repository;
#Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
#Controller
public class MyController {
private final MyService service;
#Autowired
public MyController(MyService service) {
this.service = service;
}
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
This has several advantages:
You don't need Spring in your tests. This allows you to do proper unit tests. It also makes the test incredibly fast (from seconds to milliseconds).
You cannot accidentally create an instance of a class without its dependencies which would result in a NullPointerException.
As #NamshubWriter pointed out:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
Discard the #Configuration class and write a test like this:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
#Mock
private MyRepository repository;
#InjectMocks
private MyService service;
#Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
Use interfaces, especially if you use some kind of AOP (transactions, security, etc), i.e. you'll have interface MyService and class MyServiceImpl.
In configuration you'll have:
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
you should put the #InjectMocks annotation in your controller and #Mock in your service, look:
#Autowired
#InjectMocks
private MyController myController;
#Autowired
#Mock
private MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDoSomething() {
myController.doSomething();
}
I want to inject an ApplicationContext itself to a bean.
Something like
public void setApplicationContext(ApplicationContect context) {
this.context = context;
}
Is that possible in spring?
Previous comments are ok, but I usually prefer:
#Autowired private ApplicationContext applicationContext;
Easy, using the ApplicationContextAware interface.
public class A implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
}
Then in your actual applicationContext you only need to reference your bean.
<bean id="a" class="com.company.A" />
Yes, just implement the ApplicationContextAware -interface.
I saw some comments above about #Autowired not working still. The following may help.
This will not work:
#Route(value = "content", layout = MainView.class)
public class MyLayout extends VerticalLayout implements RouterLayout {
#Autowired private ApplicationContext context;
public MyLayout() {
comps.add(context.getBean(MyComponentA.class)); // context always null :(
}
You must do this:
#Autowired
public MyLayout(ApplicationContext context) {
comps.add(context.getBean(MyComponentA.class)); //context is set :)
}
or this:
#PostConstruct
private void init() {
comps.add(context.getBean(MyComponentA.class)); // context is set :)
}
Also note that Upload is another component that must be set within the scope of #PostConstruct. This was a nightmare for me to figure out. Hope this helps!
I almost forgot to mention that the #Scope annotation may be necessary for your Bean, as seen below. This was the case when using Upload within a Bean because the UI is not instantiate/attached prior to the Bean being created and will cause a Null Reference Exception to be thrown. It won't do so when using #Route, but will when using #Component - so the latter is not an option and if #Route is not viable, then I would recommend using #Configuration class to create the bean with the prototype scope.
#Configuration
public class MyConfig {
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyComponentA getMyBean() {
return new MyComponentA();
}
}
Special solution: get Spring beans from any (non Spring) classes
#Component
public class SpringContext {
private static ApplicationContext applicationContext;
#Autowired
private void setApplicationContext(ApplicationContext ctx) {
applicationContext = ctx;
}
public static <T> T getBean(Class<T> componentClass) {
return applicationContext.getBean(componentClass);
}
}