Spring's dependency injection mechanism looks convenient because I don't have to create classes manually from root class and pass them to deeper classes. But what if I have 3 separate threads which do the same job and have some thread-specific data which should be spread through the class hierarchy?
Consider the following App:
#SpringBootApplication
public class App {
public static void main(String[] args) throws IOException {
Properties props = new Properties();
props.load(App.class.getResourceAsStream("/application.properties"));
String[] domains = props.getProperty("domains").split(",");
ConfigurableApplicationContext context = SpringApplication.run(App.class);
ConfigurableListableBeanFactory factory = context.getBeanFactory();
for(String domain : domains) {
DomainListener listener = factory.getBean(DomainListener.class);
listener.setDomain(domain);
listener.start();
}
}
}
#Service
#Scope("prototype")
public class DomainListener extends Thread {
#Autowired
EventPublisher publisher;
String domain;
public void setDomain(String domain) {
this.domain = domain;
}
#Override
public void run() {
System.out.println("starting '" + domain + "' domain thread...");
publisher.setDomain(domain); // <-- is this the best way to initialize bean?
// some processing
publisher.publish("Some event");
}
}
#Component
#Scope("prototype")
public class EventPublisher {
String domain;
public void setDomain(String domain) {
this.domain = domain;
}
public void publish(String msg) {
System.out.println("publishing '" + msg + "' to '" + domain + "' domain");
}
}
application.properties file content
domains = domain1.example.com,domain2.example.com,domain3.example.com
DomainListener and EventPublisher classes can't be initialized with domain value automatically by Spring, thus I have to call setDomain method and pass the domain value manually, first during DomainListener thread construction, then in DomainListener.run() to initialize EventPublisher with domain value. Would I need to pass the value even deeper, for example from EventPublisher to some other class, say MessageBuilder (let's assume it requires domain value), then I would have to call MessageBuilder.setDomain() instance method from EventPublisher constructor (as it is the same domain thread).
Can this be improved somehow?
Related
I'm new in Spring, and want to figure out how to inject value from properties file.
I have component with Value annotation:
#Component
public class ConfigProp {
private final String login;
public ConfigProp(#Value("${login}") String login) {
this.login = login;
}
#Override
public String toString() {
return "ConfigProp{" +
"login='" + login + '\'' +
'}';
}
}
then, I have main class:
public class App {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ConfigProp.class);
context.addBeanFactoryPostProcessor(new PropertySourcesPlaceholderConfigurer());
var cp = context.getBean(ConfigProp.class);
System.out.println(cp);
}
}
Additionally I have application.properties file
with content:
login=some_login
And when I launching my app I'm getting ConfigProp{login='${login}'}
Yes, I know, I may annotate ConfigProp with #PropertySource(path-to-properties) annotation, but it's not a good solution, because in the Prod I may want to use another properties file,
How can I resolve this issue?
Designing a new application, I have two sets of domain objects. One set mirrors the other and domain objects basically are paired up with similar attributes. When a domain object in the set A is created, updated, or deleted, a corresponding domain object in the set B will also be created, updated, or deleted. To reduce any coupling, I would like to separate those operations between a pair of domain objects. What will be a good mechanism to achieve this approach? I am thinking of using a messaging system. Will it work well for this case? I use Spring for this project.
Yes, using application events is a common solution for decreasing coupling between objects.
Actually spring already has builtin mechanism for that.
You might come up with something like:
#SpringBootApplication
public class So44490189Application {
public static void main(String[] args) {
SpringApplication.run(So44490189Application.class, args);
}
public static class UserCreatedEvent {
private final String email;
public UserCreatedEvent(String email) {
this.email = email;
}
}
#Service
public static class UserService {
#EventListener
public void handleUserCreatedEvent(UserCreatedEvent userCreatedEvent) {
System.out.println("Creating user " + userCreatedEvent.email);
}
}
#Service
public static class MemberService {
#EventListener
public void handleUserCreatedEvent(UserCreatedEvent userCreatedEvent) {
System.out.println("Creating member " + userCreatedEvent.email);
}
}
#Service
public static class OperationService {
private final ApplicationEventPublisher eventPublisher;
#Autowired
public OperationService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createUser(String email) {
eventPublisher.publishEvent(new UserCreatedEvent(email));
}
}
#RestController
public static class OperationController {
private final OperationService operationService;
#Autowired
public OperationController(OperationService operationService) {
this.operationService = operationService;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void createUser(String email) {
operationService.createUser(email);
}
}
}
Usage
curl -XPOST 'localhost:8080?email=admin#localhost.localdomain'
Output
Creating member admin#localhost.localdomain
Creating user admin#localhost.localdomain
In this case creation user and members are mirrored.
One possible problem is transaction support and there is a couple of ways to deal with that. Spring has tools for it as well.
I can't make method level validation right. Or I don't understand how it works.
My application class is below. Very simple. It contains MethodValidationPostProcessor bean definition. It also runs Greeter service.
#SpringBootApplication
public class App implements CommandLineRunner {
private final Greeter greeter;
public App(Greeter greeter) {
this.greeter = greeter;
}
public static void main(String[] args) {
new SpringApplicationBuilder().main(App.class).sources(App.class).web(false).run(args).close();
}
#Bean
public org.springframework.validation.beanvalidation.MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
#Override
public void run(String... args) throws Exception {
final Input input = new Input();
input.setName("j");
final String messageFromInput = greeter.getMessageFromInput(input);
final String messageFromString = greeter.getMessageFromString("j");
}
}
Greeter service below. Here I do expect to validate input and output.
#Service
#Validated
public class Greeter {
String getMessageFromInput(#Valid #NotNull Input name) {
return "[From Input] Greetings! Oh mighty " + name + "!";
}
String getMessageFromString(#Size(min = 4) String name) {
return "[From String] Greetings! Oh mighty " + name + "!";
}
}
Input DTO is very simple as well.
public class Input {
#NotEmpty
#Size(min = 3)
private String name;
// Getters, setters and toString ommited.
}
Since the name in both cases, direct String and DTO, is only one letter I expect this setup to throw exception. Unfortunately, nothing happens and application completes successfully. It works with controller's methods. But I would like it to work with any bean's methods.
You are injecting your Greeter bean as a constructor argument into the class annotated with #SpringBootApplication which is a #Configuration class. To satisfy that dependency the Greeter is created very early on in the startup process of the ApplicationContext and as such will remove it as a candidate for proxy creation.
Instead of injecting it as a constructor argument move your CommandLineRunner logic to a #Bean annotated method and simply inject the Greeter as a dependency. This will delay the creation of the bean and as such make it available for proxying.
#Bean
public CommandLineRunner runner(Greeter greeter) {
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
final Input input = new Input();
input.setName("j");
final String messageFromInput = greeter.getMessageFromInput(input);
final String messageFromString = greeter.getMessageFromString("j");
}
};
}
Another thing is that your methods of the Greeter should be, due the the nature of proxies, public.
I have Spring service, which is actually actor, it is received info, but I cant pass it to another Spring service, because injection fails.
#Service("mailContainer")
#Scope("prototype")
#Component
public class MailContainer extends UntypedActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private Mail value;
private List<Mail> mailList = new ArrayList<Mail>();
private Integer size;
#Autowired
#Qualifier("springService")
private SpringService springService;
//#Autowired
public void setSpringService(SpringService springService) {
this.springService = springService;
}
public MailContainer(Mail value) {
this.value = value;
}
#Override
public void onReceive(Object message) throws Exception {
// LOG.debug("+ MailContainer message: {} ", message);
if (message instanceof Mail) {
value = (Mail) message;
System.out.println("MailContainer get message with id " + value.getId());
System.out.println("With time " + value.getDateSend());
//getSender().tell(value, getSelf()); //heta uxarkum
//this.saveIt(value);
springService.add(value);
}
}
and second service
#Service("springService")
//#Component
#Scope("session")
public class SpringService {
private List<Mail> mailList = new ArrayList<Mail>();
public void add(Mail mail) {
System.out.println("Saving mail from Spring " +mail.getId());
mailList.add(mail);
}
public List<Mail> getMailList() {
return mailList;
}
}
Spring config, this is from akka spring example
#Configuration
//#EnableScheduling
//EnableAsync
#ComponentScan(basePackages = {"com"}, excludeFilters = {
#ComponentScan.Filter(Configuration.class)})
//#ImportResource("classpath:META-INF/spring/spring-data-context.xml")
//#EnableTransactionManagement
//#EnableMBeanExport
//#EnableWebMvc
public class CommonCoreConfig {
// the application context is needed to initialize the Akka Spring Extension
#Autowired
private ApplicationContext applicationContext;
/**
* Actor system singleton for this application.
*/
#Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("AkkaJavaSpring");
// initialize the application context in the Akka Spring Extension
SpringExtProvider.get(system).initialize(applicationContext);
return system;
}
}
So, how I can inject just another Spring service?????????
Based on our discussions, I think it is due to the way you create the MailContainer actor. You aren't using the SpringExtProvider and instead are using Props.create directly. This means that Spring doesn't get the opportunity to perform dependency injection on your new actor.
Try changing this code:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(Props.create(MailContainer.class, result), "one");
}
to use the the SpringExtProvider like this:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("mailContainer"), "one");
}
This way you are asking the Spring extension to create the new actor and inject any required dependecnies.
I have an issue/problem with CDI in the next scenario:
Initializator is injected in the ServletContextListener. But after some other "steps" the method startup is invoked:
#WebListener
public class ContextListener implements ServletContextListener {
#Inject
private Initializator initializator;
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = (ServletContext) event.getSource();
String contextPath = ((ServletContext) event.getSource()).getContextPath();
String serverName = servletContext.getInitParameter("SERVER_NAME");
initializator.startup(serverName);
System.out.println("ServletContext " + contextPath + " stated.");
}
public void contextDestroyed(ServletContextEvent event) {
String contextPath = ((ServletContext) event.getSource()).getContextPath();
System.out.println("ServletContext " + contextPath + " stopped.");
}
}
The repository is successful injected in the initializator:
public class Initializator {
#Inject
private ChannelRepository repo;
public String serverName;
public void startup(String aServerName) {
this.serverName = aServerName;
initAll();
}
private void initAll() {
List<Channel> channels = repo.getChannels();
for (Channel channel : channels) {
channel.start();
}
}
}
The repository retrieves the data and instantiates channels:
public class ChannelRepository {
public List<Channel> getChannels() {
List<Channel> channels = new ArrayList<Channel>();
// ...some db access via jdbc (or jpa)
channels.add(new Channel("dummy", 8080));
return channels;
}
}
The channel needs a Logger:
public class Channel extends Thread {
#Inject
private Logger logger;
public String name;
public int port;
public Channel(String aName, int aPort) {
this.name = aName;
this.port = aPort;
}
#Override
public void run() {
logger.log("Channel " + name + " is running in port " + port);
// ...some other things to do
}
}
How to avoid the manual creation of Channel instances?
The problem is done because the startup method in Initializator is invoked after the instance construction.
How to manage this type of "deferred" injections?
Avoiding the manual creation of instances of Channel with
new Channel()
is fairly easy.
First we need a default constructor in class Channel and setters for channels attributes.
Then you must add this to your Channel Repo
#Inject
Instances<Channel> channelInstances;
and in your repo method change from
channels.add(new Channel("dummy", 8080));
to
Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(channel);
A small hint:
If it is possible to do, don't let Channel extend Thread, but do the following
final Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(new Thread() {
#Override
public void run() {
channel.doTheChannelStuff();
channelInstances.destroy(channel);
}
}
Why should you do this:
Under some circumstances a memory leak will be introduced when doing it in the way you are trying to use it. (Had a similiar issue at work) This has something to do with dependent scoped (default) dependencies and "manual" creation of new instances.