0

I know similar questions have been asked so many times here before, but I am still confused by the mechanisms.

Here is my problem. There is a null pointer exception coming from the CategoryDAO object in the CategoryService.

@Service
public class CategoryService {
    @Autowired
    private CategoryDAO categoryDAO;

    public List<Category> list(){
        List<Category> categories = categoryDAO.list();
        for (Category category : categories){
            List<Record> rs = recordDAO.list(category.getID());
            category.setRecordNumber(rs.size());
        }
        return categories;
    }

    public void add(String name){
        Category newCategory = new Category();
        newCategory.setName(name);
        categoryDAO.add(newCategory);
    }
}
@Repository
public class CategoryDAO {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public int getTotal(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<Category> categories = sqlSession.selectList("category.selectAll");

        return categories.size();
    }
}

In this top rated post and this one, both of the top answers mentioned that The most preferable option is to let Spring autowire all of your beans. Does it mean I have to also autowire the CategoryService in other classes once I need it? Which means I cannot use new operator to initialise a class if it contains autowired object? If yes, could you please explain the reason behind it?

Thanks

UPDATE

Here is an example about using the autowired class CategoryService:

public class RecordListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        RecordPanel panel = RecordPanel.getInstance();
        if (new CategoryService().list().size() == 0){
            JOptionPane.showMessageDialog(panel, "NO category is recorded, set category first");
            MainPanel.getInstance().getPanel().display(CategoryPanel.getInstance());
            return;
        }
}

The CategoryService is used in new CategoryService().list().size() == 0. If I autowire it as a property of this class here then this class will also need to be injected once I need it. I would like to avoid that so things could be easier. How can I achieve that?

chaos
  • 338
  • 4
  • 16
  • Can you please provide the stack trace of the NullPointer Exception occurred as you mentioned above – Rahal Kanishka Sep 30 '20 at 04:20
  • 1
    @RahalKanishka Sorry for not providing it in the first hand. But I have solved it as what the accepted answer said and not able to get the stack trace now. It was just a basic NullPointer Exception and raised by the `List categories = categoryDAO.list()` in `CategoryService` class. – chaos Sep 30 '20 at 08:52

1 Answers1

1

Does it mean I have to also autowire the CategoryService in other classes once I need it? Yes.

Which means I cannot use new operator to initialise a class if it contains autowired object? Yes.

The @Autowire annotation enables you to use Dependency Injection. A technique (or good practice, actually) that makes it easy to change the implementations you use for your interfaces in your application. You define beans/component/services that will get injected whenever you use the @Autowire annotation over an attribute or a constructor parameter. Instead of using new all over your code you just declare which concrete class should be used for an interface (or maybe the class itself) annotated with @Autowire. Imagine you create an interface RemoteAccess and an implementation FtpRemoteAccess and then every time you need it you write RemoteAccess remoteAccess = new FtpRemoteAccess(); After a while you might end up with that line over several places. Now, if you need to change this to HttpRemoteAccess because you have this new, better alternative, you have to review all your code base. Instead, if you used dependency injection you would just change the bean (there is more than one way to do that using Spring). For all this to work, Spring must be able to inject all the dependencies of a bean. If you create a bean, all its attributes must be injected too because Spring will create that object for you.

Clarification:

Inside your bean (namely, you classes that will be injected) you can create objects using new provided that makes sense and those object are not injected types. You are already doing that in CategoryService::add() and it is ok. Dependency injection doesn't mean you will not ever write new again. You will just avoid it for objects that will be managed by Spring dependency injection. Then, there are other good practices that disencourage using new like the static factory method that recommend putting a static method in your class to build complete objects and letting the constructor to be private. But you don't need to apply all the patterns all the time.

UPDATE:

For your RecordListener class you have to add a CategoryService attribute and then be sure it is initialized. There are two options: you can convert RecordListener in a bean itself and have it autowired where you need that. This way Spring will construct a RecordListener object for injecting it and will also add any other bean that is needed (like CategoryService)

@Component
public class RecordListener implements ActionListener {

    @Autowire
    private CategoryService categoryService;

    @Override
    public void actionPerformed(ActionEvent e) {
        RecordPanel panel = RecordPanel.getInstance();
        if (categoryService.list().size() == 0) {
            JOptionPane.showMessageDialog(panel, "NO category is recorded, set category first");
            MainPanel.getInstance().getPanel().display(CategoryPanel.getInstance());
            return;
        }
    }
}

The other option is you inject CategoryService in the class that is currently creating the RecordListener and then pass it as constructor argument. In that case RecordListener will not be a bean.

drkblog
  • 388
  • 2
  • 8
  • Thanks for the explanation. Could you tell me how I should handle local objects? Since @Autowire cannot be applied to local variables, and they are not able to be created via `new` operator. Do I have to always use `ApplicationContext`? – chaos Sep 20 '20 at 05:34
  • Added a clarification up there because it was too long for commenting. – drkblog Sep 20 '20 at 14:32
  • Thanks for the extra clarification. I have tried using different patterns to handle local objects but `CategoryService` was still assigned with null. Could you have a look on my updated content and give me some advices in detail? – chaos Sep 21 '20 at 11:40
  • @chaos Your class `CategoryService` is managed by Spring injection. Remember you don't use `new` on managed classes. You don't want to write `new CategoryService().list().size()`. Instead you have to add a `private CategoryService categoryService;` attribute annotated with `@Autowire` in the class you need to use it. Provided that class is also being created by Spring – drkblog Sep 21 '20 at 12:37