I've read many articles about the DI principle in spring framework, but I still don't understand how to resolve circular dependency in below case:
Entity class
class Thread {
...
String status;
}
class Reply {
Thread thread;
String status;
...
}
Service layer to handle CRUD of these 2 object
class ThreadService {
#Autowired private ReplyService replySvc;
public void suspend (Thread t) {
t.setStatus("suspended");
replySvc.getReplies(t).forEach(r -> {
r.setStatus("suspended");
replySvc.update(r);});
dao.save(t);
}
public void update(Thread t) {
...
dao.save(t);
}
...
}
class ReplyService {
#Autowired private ThreadService threadSvc;
public Reply create(...) {
thread.setStatus("replied");
threadSvc.update(thread);
...
return reply;
}
public List<Reply> getReplies(Thread t) {
...
return replies;
}
public void update(Reply r) {
...
dao.save(r);
}
}
Both services involve an update call of another service to update the corresponding entity.
If I'd like to apply DI principle, what should I change?
Based on your design, the most dangerous dependency is
ThreadService (this should be isolated) -> ReplyService.
You can decouple your components using event based design:
ThreadService
method call : suspend
sent event : ThreadSuspendedEvent
ReplyService should be a listener for event ThreadSuspendedEvent and when the event is received, this code should be executed:
getReplies(t).forEach(r -> {
r.setStatus("suspended");
update(r);});
Related
I have an application scoped bean as follows
#ApplicationScoped
public class Worker {
public void process(Long id) {
final Runnable runnable = () -> {
doATransaction(id);
};
executor.execute(runnable);
}
#Transactional
public void doATransaction(Long id) {
User user = User.findById(id);
}
}
I am getting a javax.enterprise.context.ContextNotActiveException. I also tried adding the doATransaction() to another Bean, which was injected in this Worker, as suggested here. Still had the same problem.
Does anyone have an idea of what I could do next?
The Exception I am getting is
Exception in thread "pool-13-thread-3" javax.enterprise.context.ContextNotActiveException
at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:40)
at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder_ClientProxy.arc$delegate(RequestScopedSessionHolder_ClientProxy.zig:42)
at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder_ClientProxy.getOrCreateSession(RequestScopedSessionHolder_ClientProxy.zig:160)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.acquireSession(TransactionScopedSession.java:103)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.find(TransactionScopedSession.java:168)
at io.quarkus.hibernate.orm.runtime.session.ForwardingSession.find(ForwardingSession.java:68)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.findById(AbstractJpaOperations.java:173)
at com.example.User.findById(User.java)
Thanks for reading ahead of time. In my main method I have a PublishSubscribeChannel
#Bean(name = "feeSchedule")
public SubscribableChannel getMessageChannel() {
return new PublishSubscribeChannel();
}
In a service that does a long running process it creates a fee schedule that I inject the channel into
#Service
public class FeeScheduleCompareServiceImpl implements FeeScheduleCompareService {
#Autowired
MessageChannel outChannel;
public List<FeeScheduleUpdate> compareFeeSchedules(String oldStudyId) {
List<FeeScheduleUpdate> sortedResultList = longMethod(oldStudyId);
outChannel.send(MessageBuilder.withPayload(sortedResultList).build());
return sortedResultList;
}
}
Now this is the part I'm struggling with. I want to use completable future and get the payload of the event in the future A in another spring bean. I need future A to return the payload from the message. I think want to create a ServiceActivator to be the message end point but like I said, I need it to return the payload for future A.
#org.springframework.stereotype.Service
public class SFCCCompareServiceImpl implements SFCCCompareService {
#Autowired
private SubscribableChannel outChannel;
#Override
public List<SFCCCompareDTO> compareSFCC(String state, int service){
ArrayList<SFCCCompareDTO> returnList = new ArrayList<SFCCCompareDTO>();
CompletableFuture<List<FeeScheduleUpdate>> fa = CompletableFuture.supplyAsync( () ->
{ //block A WHAT GOES HERE?!?!
outChannel.subscribe()
}
);
CompletableFuture<List<StateFeeCodeClassification>> fb = CompletableFuture.supplyAsync( () ->
{
return this.stateFeeCodeClassificationRepository.findAll();
}
);
CompletableFuture<List<SFCCCompareDTO>> fc = fa.thenCombine(fb,(a,b) ->{
//block C
//get in this block when both A & B are complete
Object theList = b.stream().forEach(new Consumer<StateFeeCodeClassification>() {
#Override
public void accept(StateFeeCodeClassification stateFeeCodeClassification) {
a.stream().forEach(new Consumer<FeeScheduleUpdate>() {
#Override
public void accept(FeeScheduleUpdate feeScheduleUpdate) {
returnList new SFCCCompareDTO();
}
});
}
}).collect(Collectors.toList());
return theList;
});
fc.join();
return returnList;
}
}
Was thinking there would be a service activator like:
#MessageEndpoint
public class UpdatesHandler implements MessageHandler{
#ServiceActivator(requiresReply = "true")
public List<FeeScheduleUpdate> getUpdates(Message m){
return (List<FeeScheduleUpdate>) m.getPayload();
}
}
Your question isn't clear, but I'll try to help you with some info.
Spring Integration doesn't provide CompletableFuture support, but it does provide an async handling and replies.
See Asynchronous Gateway for more information. And also see Asynchronous Service Activator.
outChannel.subscribe() should come with the MessageHandler callback, by the way.
I have a simple scenario in which am trying to verify some behavior when a method is called (i.e. that a certain method was called with given parameter, a function pointer in this scenario). Below are my classes:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
AppBootStrapper bootStrapper = context.getBean(AppBootStrapper.class);
bootStrapper.start();
}
}
#Component
public class AppBootStrapper {
private NetworkScanner networkScanner;
private PacketConsumer packetConsumer;
public AppBootStrapper(NetworkScanner networkScanner, PacketConsumer packetConsumer) {
this.networkScanner = networkScanner;
this.packetConsumer = packetConsumer;
}
public void start() {
networkScanner.addConsumer(packetConsumer::consumePacket);
networkScanner.startScan();
}
}
#Component
public class NetworkScanner {
private List<Consumer<String>> consumers = new ArrayList<>();
public void startScan(){
Executors.newSingleThreadExecutor().submit(() -> {
while(true) {
// do some scanning and get/parse packets
consumers.forEach(consumer -> consumer.accept("Package Data"));
}
});
}
public void addConsumer(Consumer<String> consumer) {
this.consumers.add(consumer);
}
}
#Component
public class PacketConsumer {
public void consumePacket(String packet) {
System.out.println("Packet received: " + packet);
}
}
#RunWith(JUnit4.class)
public class AppBootStrapperTest {
#Test
public void start() throws Exception {
NetworkScanner networkScanner = mock(NetworkScanner.class);
PacketConsumer packetConsumer = mock(PacketConsumer.class);
AppBootStrapper appBootStrapper = new AppBootStrapper(networkScanner, packetConsumer);
appBootStrapper.start();
verify(networkScanner).addConsumer(packetConsumer::consumePacket);
verify(networkScanner, times(1)).startScan();
}
}
I want to verify that bootStrapper did in fact do proper setup by registering the packet consumer(there might be other consumers registered later on, but this one is mandatory) and then called startScan. I get the following error message when I execute the test case:
Argument(s) are different! Wanted:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapperTest$$Lambda$8/438123546#282308c3
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapper$$Lambda$7/920446957#5dda14d0
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)
From the exception, clearly the function pointers aren't the same.
Am I approaching this the right way? Is there something basic I am missing? I played around and had a consumer injected into PacketConsumer just to see if it made a different and that was OK, but I know that's certainly not the right way to go.
Any help, perspectives on this would be greatly appreciated.
Java doesn't have any concept of "function pointers"; when you see:
networkScanner.addConsumer(packetConsumer::consumePacket);
What Java actually compiles is (the equivalent of):
networkScanner.addConsumer(new Consumer<String>() {
#Override void accept(String packet) {
packetConsumer.consumePacket(packet);
}
});
This anonymous inner class happens to be called AppBootStrapper$$Lambda$7. Because it doesn't (and shouldn't) define an equals method, it will never be equal to the anonymous inner class that the compiler generates in your test, which happens to be called AppBootStrapperTest$$Lambda$8. This is regardless of the fact that the method bodies are the same, and are built in the same way from the same method reference.
If you generate the Consumer explicitly in your test and save it as a static final Consumer<String> field, then you can pass that reference in the test and compare it; at that point, reference equality should hold. This should work with a lambda expression or method reference just fine.
A more apt test would probably verify(packetConsumer, atLeastOnce()).consumePacket(...), as the contents of the lambda are an implementation detail and you're really more concerned about how your component collaborates with other components. The abstraction here should be at the consumePacket level, not at the addConsumer level.
See the comments and answer on this SO question.
My current setup is JBoss Seam 2.2 on JBoss 4.2.3.GA.
I have two Beans like so:
#Name("mailingManager")
#Scope(ScopeType.PAGE)
public class MailingMgr {
private Mailing selectedMailing;
#Observer("mailing.letter.success")
public void recordSuccess(final Object arg) {
if (null != selectedMailing) { // store arg }
}
public void send() {
selectedMailing = new Mailing();
if ('EMAIL' == determineType()) {
EmailSender mailer = (EmailSender) Component.getInstance(EmailSender.class);
mailer.send(getAddresses());
}
// ... more options
}
}
#Name("emailSender")
#Scope(ScopeType.PAGE)
public class EmailSender {
public void send(final Set<String> addresses) {
for (String addr : addresses) {
// ... create a mail
Events.instance().raiseEvent("mailing.letter.success", getGeneratedMail());
}
}
}
The problem is that when recordSuccess() is called selectedMailing is always null.
As a workaround I'm setting selectedMailing in the conversation context manually before calling any code that could potentially trigger my events, and then annotate my field with #In(required=false) to inject it again before recordSuccess is called. But is there a more elegant solution (keeping the decoupling intact)? And why isn't the calling bean reused to handle the event?
In order to use events only listened if a transaction succeeds or fails, I'm following the given doc about transactional observers :
http://docs.jboss.org/weld/reference/1.1.0.Final/en-US/html_single/#d0e4075
... but cannot manage to make my code work on JBoss AS7.
Here's my EJB:
#LocalBean
#Stateful
#TransactionAttribute(TransactionAttributeType.NEVER)
public class MyController
{
#Inject
private transient Event<MyEvent> myEventLauncher;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void save()
{
myEventLauncher.fire(new MyEvent());
}
#AfterCompletion
protected void afterSave(boolean isCommitted)
{
// do stuff
}
}
And here my basic listener:
public class MyHandler
{
protected void listenMyEvent(#Observes(during=TransactionPhase.AFTER_SUCCESS) MyEvent event)
{
// do stuff
}
protected void listenMyEvent2(#Observes(during=TransactionPhase.AFTER_FAILURE) MyEvent event)
{
// do stuff
}
}
I can say I'm in a transaction when the event is fired, because the afterSave method of the EJB is called. Alas, the methods listenMyEvent and listenMyEvent2 are always called both, like if I was not in a transactional context.
I tried the same code on GlassFish 3 and it perfectly works, so I guess there is a problem with JBoss AS 7, but I cannot find any bug report about it.
Well, as my current tests made me think that transactional observers are not working in JBoss AS 7, I managed to do a workaround I gave here for people who are interested.
First, we need qualifier annotations: Immediate, AfterFailure and AfterSuccess.
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface AfterFailure
{}
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface AfterSuccess
{}
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface Immediate
{}
Also, three basic AnnotationLiteral to create in runtime instances of this three annotations.
Then, we need a encapsulator for our true events, that I named SpecialEvent.
public class SpecialEvent
{
private Object event; // the real event you want
public SpecialEvent(Object event)
{
super();
this.event = event;
}
public Object getEvent()
{
return event;
}
}
And at last, an observer for this special event and an interceptor for classes where you want to fire this kind of events (full explanation below).
#RequestScoped
public class SpecialEventObserver
{
#Inject
private Event<Object> anyEventFirer; // firer for real events
private List<Object> events; // queued events
public SpecialEventObserver()
{
events = new ArrayList<Object>();
}
// remove all queued events
public void reset()
{
this.events.clear();
}
public void fireAfterFailureEvents() throws Exception
{
this.fireAllEventsOnce(new AfterFailureLiteral());
}
public void fireAfterSuccessEvents() throws Exception
{
this.fireAllEventsOnce(new AfterSuccessLiteral());
}
protected void listenSpecialEvent(#Observes SpecialEvent specialEvent)
{
Object event = specialEvent.getEvent();
this.events.add(event);
this.fireEvent(event, new ImmediateLiteral());
}
protected void fireAllEventsOnce(Annotation qualifier) throws Exception
{
try
{
for (Object event : this.events)
{
this.fireEvent(event, qualifier);
}
}
catch (Exception e)
{
throw e;
}
finally
{
this.events.clear();
}
}
protected void fireEvent(Object event, Annotation qualifier)
{
Event eventFirer = anyEventFirer.select(event.getClass(), qualifier);
eventFirer.fire(event);
}
}
#Interceptor
#LocalInterception
public class MyInterceptor implements Serializable
{
#Inject
private SpecialEventObserver specialEventObserver;
#AroundInvoke
public Object intercept(InvocationContext ic) throws Exception
{
specialEventObserver.reset();
try
{
// call the real method
Object proceedResult = ic.proceed();
// real method succeeded, fire successful events
specialEventObserver.fireAfterSuccessEvents();
return proceedResult;
}
catch (Exception e)
{
// real method failed, fire failed events
specialEventObserver.fireAfterFailureEvents();
throw e;
}
}
}
The mechanism is quite simple:
When you want to fire an event, fire a SpecialEvent that hold the true event.
The SpecialEventObserver will catch any SpecialEvent and will immediately fire your own event with an Immediate qualifier. It will also queue the events for the after completion part.
At the end of your own method call (ic.proceed in the interceptor), MyInterceptor will ask the SpecialEventObserver either to fire again all events with a AfterFailure qualifier or a AfterSuccess qualifier, depending of the success of your method.
In place of #Observes(during=...), your own observers have to observe events with the right qualifier, like #Observes #Immediate, #Observes #AfterFailure or #Observes #AfterSuccess.
The behavior is not exactly the one that provides the native #Observes(during=...). The after completion part is not based on the transaction state, but on your own method call success:
In JaveEE6, transactional observers on after success or after failure phases must be immediately called if you're not in a transaction, like a IN_PROGRESS would do.
In this workaround, observers on after success or after failure phases will always be called at the end of the method, and only if it succeeded or failed.
This works with version 7.1.0.Final which is supposedly (-> with Jboss you never know) fully Java EE compliant. Also your bean is not thread-safe as it uses list instead of a concurrent queue.
Your observer methods need REQUIRES_NEW, as stated here :
http://www.seamframework.org/Documentation/WhyIsThereNoActiveTransactionInMySFSBTransactionalObserver