简体   繁体   中英

Can I use Spring @Autowired in multiple classes?

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 ie 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?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM