1

My question is - Can I autowire an instance of a class in multiple classes?

My application uses Spring MVC to communicate between the JSP front end pages and my MongoDB back end. I am using MongoDB for my repository. I have created a service which performs the CRUD methods for MongoDB. This can be seen below with one of the CRUD methods (not all are shown as they are not needed). It uses the Mongo template.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class SensorReadingService {

@Autowired
private MongoTemplate mongoTemplate;

/**
 * Create a unique mongo id and insert the document into
 * the collection specified. If collection doesn't exist, 
 * create it.
 * @param sensor
 */ 
public void addSensorReadingIntoCollection(SensorReading sensor, String collection)  {
    if (!mongoTemplate.collectionExists(SensorReading.class)) {
        mongoTemplate.createCollection(SensorReading.class);
    }

    String id = UUID.randomUUID().toString();

    String[] ids = id.split("-");

    String noHyphens = "";

    for(int i = 0; i<ids.length; i++) {
        noHyphens = noHyphens.concat(ids[i]);
    }

    sensor.setId(noHyphens);
    mongoTemplate.insert(sensor, collection);
}

As can be seen, the MongoTemplate is autowired. In my Dispatcher-Servlet I have the following code for MongoDB:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:p="http://www.springframework.org/schema/p"
   xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.2.xsd
   http://www.springframework.org/schema/mvc 
   http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

<context:component-scan base-package="usm" />

<!-- Factory bean that creates the Mongo instance -->
<bean id="mongo" class="org.springframework.data.mongodb.core.MongoFactoryBean">
    <property name="host" value="localhost" />
</bean>

<!-- MongoTemplate for connecting and querying the documents in the database -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongo" ref="mongo" />
    <constructor-arg name="databaseName" value="USMdb" />
</bean>

<!-- Use this post processor to translate any MongoExceptions thrown in @Repository annotated classes -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />   

<bean id="jspViewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver"
      p:prefix="/WEB-INF/jsp/"
      p:suffix=".jsp" />    

<mvc:resources mapping="/resources/**" location="/resources/" />

<mvc:annotation-driven />

I now also have a controller which has autowired the service so that requests from the JSP page can be passed to the controller, and the controller then writes these requests to the database via the autowired service. (One example method shown below as the class is massive). This autowired service doesn't have anything in the dispatcher servlet like the MongoDB autowired previously.

import gnu.io.SerialPortEventListener;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class SensorReadingController implements SerialPortEventListener {

//mongodb service
@Autowired
private SensorReadingService sensorReadingService;

    /**
 * When a post method is achieved via save, check if reading already
 * exists and update. Else, create new sensor. Redirect back to post race
 * page.
 * @param sensorReading
 * @param model
 * @return post race page
 */
@RequestMapping(value = "/postRace-save", method = RequestMethod.POST)
public View createSensorReading(@ModelAttribute SensorReading sensor, ModelMap model,@RequestParam(value="colName", required=true)String name) {
    if (StringUtils.hasText(sensor.getId())) {
        sensorReadingService.updateSensorReading(sensor, name);
    }
    else {
        sensorReadingService.addSensorReading(sensor);
    }
    return new RedirectView("/USM-Analysis-Application/postRace");
}

The sensorReadingService method runs perfectly, performing CRUD methods on the database. Now my question is, how do I use this sensorReadingService in another class? When I add

//mongodb service
@Autowired
private SensorReadingService sensorReadingService;

into another class, the service doesn't work. No errors are flung, the service just does not add anything to the database. Is it because @Autowired only allows the class to be autowired in one class i.e. there can't be multiple instances? Or is it because I haven't specified anything in my dispatcher-servlet like with the MongoDB?

The reason I need to have it work in another class is that in my class, I am listening for Serial events. When there is data available, I create a new thread to deal with this serial event so that the rest of my program can still function. In the thread, I parse the string I received from Serial, create a new SensorReading object, and then I want to write this SensorReading object to the database. However, as I cannot get sensorReadingService to work in any other classes, I can't perform this write to database.

First I used a class that implements Runnable to perform the parsing and saving. Having @Autowired in this class did not work, so I tried passing the sensorReadingService to the thread from the controller (shown in code below). This did not work either. I then changed my thread to implement Callable so I could return the SensorReading and save it to the database in my original controller class that has the working autowired service. However, this defeats the purpose of creating the thread in the first place as it is the writing to the database I wish to perform in the thread, as it slows down the whole program if not.

 @Override
public void serialEvent(SerialPortEvent oEvent) {
    if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {          
        //use a thread in the thread pool
       //tried passing sensorReadingService to thread but did not work
        //threadPool.execute(new RealTimeThread(input, sensorReadingService, getRealTimeIdsAndNames()));

        //tried return sensor reading. This works but slows down the program.
        Callable<List<SensorReading>> callable = new RealTimeThread(input, getRealTimeIdsAndNames(), offset, sensitivity);
        Future<List<SensorReading>> future = threadPool.submit(callable);

So, I was just wondering if anyone knew what I was doing wrong with @Autowired? Can I have multiple instances of @Autowired? Do I need to add something in my dispatcher servlet? Or should I just not use @Autowired and try to call my service another way?

Any suggestions would be much appreciated and if you need me to post anymore code let me know! Thanks in advance!

EDIT:

For my RealTimeThread class I have the following code

import org.springframework.beans.factory.annotation.Autowired;
import usm.service.SensorReadingService;

 public class RealTimeThread implements Callable<List<SensorReading>> {

//mongodb service
@Autowired
private SensorReadingService sensorReadingService;

//fed by an InputStreamReader to convert bytes to strings
BufferedReader input;

//variables for getting data from real time
ArrayList<Byte> values = new ArrayList<Byte>();
//mappings for sensors
double[] offset;
double[] sensitivity;

Map<String, String> realTimeIDs;

public RealTimeThread(BufferedReader input, Map<String, String> rt, double[] offset, double[] sens) {
    this.input = input;
    realTimeIDs = rt;
    this.offset = offset;
    this.sensitivity = sens;
}

//Split up the line, parse it and add to database
@Override
public List<SensorReading> call() throws Exception {
    List<SensorReading> toReturn = new ArrayList<SensorReading>();

        String inputLine;
        if((inputLine = input.readLine()) != null) {

            //pass to the scanner
            Scanner scan = new Scanner(inputLine); 

            //get everything after the starting pattern 
            Pattern pStart = Pattern.compile("\\x02\\x02\\x02\\x02\\x02(.*)"); 
            Matcher mStart = pStart.matcher(inputLine);
            if ( mStart.find() ) {
               inputLine = mStart.group(1);

               //get everything before ending pattern
               Pattern pEnd = Pattern.compile("(.*)\\x04\\x04\\x04\\x04\\x04"); 
               Matcher mEnd = pEnd.matcher(inputLine);
               if ( mEnd.find() ) {
                   inputLine = mEnd.group(1); // " that is awesome"

                   //split up this string
                   scan = new Scanner(inputLine);

                   //set the delimiter to unit separator
                   Pattern delim = Pattern.compile("\\x1F"); 
                   scan.useDelimiter(delim); 

                   while(scan.hasNext()) {
                       //get the next string
                       String s = scan.next();

                       //change it to an integer and make it a byte
                       int val = Integer.parseInt(s);
                       byte b = (byte) val;

                       //add to the arraylist
                       values.add(b);
                   } 

                   //parse the values
                   toReturn = parser(values);

                  // System.out.println("RETURN 0 " + toReturn.get(1).getRawValue());

                   //reset the arraylist
                   values = new ArrayList<Byte>();
                }                  
            }
        }

    return toReturn;
}

//Parser to split up line, create a new sensor reading and add to database
private List<SensorReading> parser(ArrayList<Byte> data) {

    //arraylist for data after transformation
    ArrayList<Short> convertedData = new ArrayList<Short>();

    //get all data in big endian 
    for (int i = 0; i<46; i=i+2) {

    ...
...
        convertedData.add(dataChunk);
    }

    //get the time now
    double myTime = System.currentTimeMillis();

    ArrayList<SensorReading> toReturn = new ArrayList<SensorReading>();

//add to the database
    for(int i = 0; i<convertedData.size(); i++) {
        //create new sensor reading
        SensorReading sr = new SensorReading();
        sr.setSensorId(keys[i].toString());
        sr.setName(sens.get(keys[i]));
        sr.setRawValue(convertedData.get(i));
        sr.setTime(myTime);

        //add to database - this is not working 
        sensorReadingService.addSensorReadingIntoCollection(sr, "realTime");
        System.out.println("added");

        toReturn.add(sr);
    }

    return toReturn;
}
}

When trying to create a bean either in my XML or using @Component I get a BeanCreationException saying that there is no default empty constructor. I have a constructor but it has inputs into it. How do I make Spring use this constructor?

I tried using @Autowired on the constructor I have but I got an error saying that it could not autowire field BufferedInput. Any suggestions? Thanks!

EDIT:

I have used @Component on my RealTimeThread class and @Autowired on my constructor. Now the errors I am getting are as follows:

[localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader - Context
initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'realTimeThread' defined in file [C:\Users\Lauren\Dropbox\MEng Project\1. Off-board Software\Lauren\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\USM-Analysis-Application\WEB-INF\classes\usm\model\RealTimeThread.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.io.BufferedReader]: : No qualifying bean of type [java.io.BufferedReader] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.io.BufferedReader] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

So what I gather from this is that my BufferedReader is not handled by Spring? I am passing my BufferedReader in from my Controller class as follows:

@Controller
public class SensorReadingController implements SerialPortEventListener {

//mongodb service
@Autowired
private SensorReadingService sensorReadingService;

private BufferedReader input;
double[] offset;
double[] sensitivity;

...

@Override
public void serialEvent(SerialPortEvent oEvent) {
    if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {          
        //use a thread in the thread pool
        //threadPool.execute(new RealTimeThread(input, sensorReadingService, getRealTimeIdsAndNames()));

        //input.readLine();

        Callable<List<SensorReading>> callable = new RealTimeThread(input, getRealTimeIdsAndNames(), offset, sensitivity);
        Future<List<SensorReading>> future = threadPool.submit(callable);

I thought that because I was passing these variables from the controller, Spring already dealt with them. But I guess not. How do I make Spring manage these variables? Do I just use @Autowired above them like I have done with my service?

Lauren
  • 147
  • 1
  • 3
  • 10
  • You are missing `` which will enable support for auto wiring through annotations. – Bart Apr 06 '14 at 19:13
  • What kind of class are you trying to @Autowire your service into? – geoand Apr 06 '14 at 19:18
  • @Bart isn't that what is doing? All my classes are in a usm package e.g. usm.controller, usm.model so I thought that would be enabling the autowiring. Looking at http://stackoverflow.com/questions/7414794/difference-between-contextannotation-config-vs-contextcomponent-scan it sounds like the two do basically the same job. But I will give it a try with – Lauren Apr 06 '14 at 22:22
  • @geoand public class RealTimeThread implements Callable> is the class I am trying to autowire my service into, which will be called on a serial event. Currently, the service is autowired into (AT symbol)Controller public class SensorReadingController implements SerialPortEventListener and works fine. Not sure if that answers your question. – Lauren Apr 06 '14 at 22:32
  • @Lauren Thanks for the answer! Is RealTimeThread a Spring bean? – geoand Apr 07 '14 at 06:12
  • @geoand Erm I think so? My servlet xml code is displayed above with all the beans that I have. I think the bean jspViewResolver is making all my .jsp classes (which RealTimeThread is) into a Spring bean? I'm not really sure - this is the first time I've made a Web Application using Spring, MongoDB, etc. so I'm still a bit fuzzy on all the details sorry! – Lauren Apr 07 '14 at 08:24
  • kudos to all of those who read this question, I for one couldn't dare to scroll this much also – Sikorski Apr 07 '14 at 11:59

1 Answers1

1

The problem seems to be that the class in which SensorReadingService is being autowired into, is not a class managed by Spring. For autowiring to work, the class that requires the wiring needs to have it's lifecycle managed by Spring (meaning that you need to have an entry for that class in your Spring XML of Spring Java Config)

You could refactor your code like this:

1) Add another constructor argument to RealTimeThread which is going to be of type SensorReadingService.

2) Create a class RealTimeThreadFactory like so:

public class RealTimeThreadFactory {

    private final SensorReadingService sensorReadingService;

    public RealTimeThreadFactory(SensorReadingService sensorReadingService) {
        this.sensorReadingService = sensorReadingService;
    }

    public RealTimeThread getObject(BufferedReader input, Map<String, String> rt, double[] offset, double[] sens) {
          return new RealTimeThread(input, rt, offset, sens, sensorReadingService);
    }
}

3) Add a Java Config class somewhere under the package that is being included in the component scan

@Configuration
public class RealTimeThreadConfig {

    @Autowired
    private SensorReadingService sensorReadingService;

    @Bean
    public RealTimeThreadFactory realTimeThreadFactory() {
        RealTimeThreadFactory realTimeThreadFactory = new RealTimeThreadFactory(sensorReadingService);
        return realTimeThreadFactory;
    }

}

4) The class that with your current code creating RealTimeThread needs to now be a Spring bean (using any way you prefer) and be injected with RealTimeThreadFactory. In order to create the RealTimeThread objects , simple call the getObject() method on the factory with the appropriate arguments

geoand
  • 60,071
  • 24
  • 172
  • 190
  • So I would need to have something like in my dispatcher servlet xml file? Or is there a way I can use annotations in that class to make it be managed by Spring? I tried using @Component but it was flinging a Bean Creation Exception as I have no default constructor. I will edit my question to include my RealTimeThread class to show you the constructor I am using – Lauren Apr 07 '14 at 09:42
  • 1
    Yes you can use either xml or @Component. However, your classed must have a default constructor or else you must use Autowired on the constructor as well and have all arguments of the constructor managed by Spring as well. Can you please post the code of RealTimeThread? – geoand Apr 07 '14 at 09:51
  • I have posted the code of my RealTimeThread. I have used Component and Autowired but like you said the arguments of my constructor need to be managed by Spring as well. I thought they were because they are in a Controller class but apparently not. How would I make these variables be managed by Spring? Thanks very much for all the help you have given me, it has really helped! – Lauren Apr 07 '14 at 10:13
  • @Lauren I will update my answer in a while. Stay tuned – geoand Apr 07 '14 at 10:30
  • I think in the RealTimeThreadFactory you meant to return new RealTimeThread. Thanks this solution works - I can now add stuff to my database in this other class! :) However it does not seem to be working quite multi-threaded, it is hanging in the serial event listener. But that is another problem now haha Thanks very much for your help and patience! – Lauren Apr 07 '14 at 16:19
  • @Lauren Your welcome and you're right about the typo, I updated the my answer :) – geoand Apr 07 '14 at 16:39