How could I use abstract class on spring framework? - java

I'm the one leaning how to write a code using Spring Boot. Then when I tried to write a code that used abstract class, I got an error as below.
Description:
Parameter 0 of constructor in com.in28minutes.spring.practice.springmasterclasspractice.devicefactory.LaptopManufacturingProcess required a bean of type 'java.lang.String' that could not be found.
Action:
Consider defining a bean of type 'java.lang.String' in your configuration.
Could you guys give me an advise how I could solve the error?
Spring Boot: v2.1.4
Java: 10.0.2
Maven: 3.6.0
SpringMasterClassPracticeDeviceFactoryApplication class
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
#SpringBootApplication
public class SpringMasterClassPracticeDeviceFactoryApplication {
private static Logger LOGGER = LoggerFactory.getLogger(SpringMasterClassPracticeDeviceFactoryApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication
.run(SpringMasterClassPracticeDeviceFactoryApplication.class, args);
ManufacturingImpl manufacturingImpl = applicationContext.getBean(ManufacturingImpl.class);
System.out.println(manufacturingImpl);
// manufacturingImpl.manifactureProduct("Laptop Process");
LOGGER.info("{}", manufacturingImpl);
}
}
ManufacturingImpl class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
#Component
public class ManufacturingImpl {
#Autowired
#Qualifier("laptop")
private GeneralManufacturingProcess generalManufacturingProcess;
public void manifactureProduct(String processName) {
System.out.println(generalManufacturingProcess);
generalManufacturingProcess.launchProcess();
}
}
GeneralManufacturingProcess class
public abstract class GeneralManufacturingProcess {
private String processName;
public GeneralManufacturingProcess(String processName) {
this.processName = processName;
}
public String getProcessName() {
return processName;
}
public void launchProcess() {
if (processName != null && !processName.isEmpty()) {
assembleDevice();
testDevice();
packageDevice();
storeDevice();
} else {
System.out.println("No process name was specified");
}
}
protected abstract void assembleDevice();
protected abstract void testDevice();
protected abstract void packageDevice();
protected abstract void storeDevice();
}
LaptopManufacturingProcess class
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
#Component
#Qualifier("laptop")
public class LaptopManufacturingProcess extends GeneralManufacturingProcess {
public LaptopManufacturingProcess(String processName) {
super(processName);
}
#Override
protected void assembleDevice() {
System.out.println("Assembled laptop: " + getProcessName());
}
#Override
protected void testDevice() {
System.out.println("Tested laptop: " + getProcessName());
}
#Override
protected void packageDevice() {
System.out.println("Packaged laptop: " + getProcessName());
}
#Override
protected void storeDevice() {
System.out.println("Stored laptop: " + getProcessName());
}
}

There are Multiple ways to solve this. The problem is, that the Spring Framework is trying to create an instance of LaptopManufacturingProcess with the single constructor, which accepts a String. So the Framework is trying to autowire a Bean of type String into the constructor, which simply does not work.
Basically, what you can do is the following:
create a no-args constructor, and have it pass a hardcoded string to the parent constructor:
public LaptopManufacturingProcess() {
super("String");
}
Add an #Value-Annotation to read the String from a PropertySource:
public LaptopManufacturingProcess(#Value("${property.key.here}") String processName) {
super(processName);
}
Create a Factory Bean to create instances of GeneralManufacturingProcess on demand

Related

Why doesn't Spring's Qualifier annotation have the ability to resolve properties?

In Spring 5, I am able to use a properties file to inject values into fields using the Values annotation. I assumed that the same can be done using the Qualifier annotation, but it doesn't work, instead I have to do the following
import java.lang.annotation.Annotation;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
#Component
public class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
private static class EnvironmentAwareQualifierAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
private static class ResolvedQualifier implements Qualifier {
private final String value;
ResolvedQualifier(String value) { this.value = value; }
#Override
public String value() { return this.value; }
#Override
public Class<? extends Annotation> annotationType() { return Qualifier.class; }
}
#Override
protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
if (annotation instanceof Qualifier) {
Qualifier qualifier = (Qualifier) annotation;
if (qualifier.value().startsWith("${") && qualifier.value().endsWith("}")) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) this.getBeanFactory();
ResolvedQualifier resolvedQualifier = new ResolvedQualifier(bf.resolveEmbeddedValue(qualifier.value()));
return super.checkQualifier(bdHolder, resolvedQualifier, typeConverter);
}
}
return super.checkQualifier(bdHolder, annotation, typeConverter);
}
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) beanFactory;
bf.setAutowireCandidateResolver(new EnvironmentAwareQualifierAnnotationAutowireCandidateResolver());
}
}
This is something I found here: How to read Qualifier from property file in spring boot?
I am very new to Spring, so I might be missing something, but isn't this an issue?

Loading a random class using reflection and have it register as a component in springboot

I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.

java.lang.IllegalArgumentException: Not a managed type in spring boot app

I am getting following error in my code
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'locationServiceImpl': Unsatisfied
dependency expressed through method 'setLocationrepo' parameter 0;
nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'locationRepository': Invocation of init
method failed; nested exception is java.lang.IllegalArgumentException:
Not a managed type: class com.logan.location.entities.Location
This is my repository Interface
package com.logan.location.repos;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.logan.location.entities.Location;
#Repository
public interface LocationRepository extends JpaRepository<Location, Integer> {
}
This is my Service Interface
package com.logan.location.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.logan.location.entities.Location;
#Service
public interface LocationService {
Location saveLocation(Location location);
Location updateLocation(Location location);
void deleteLocation(Location location);
Location getLocationById(int id);
List<Location> getAllLocations();
}
This is my serviceImpl
package com.logan.location.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.logan.location.entities.Location;
import com.logan.location.repos.LocationRepository;
#Service
public class LocationServiceImpl implements LocationService {
private LocationRepository locationrepo;
#Autowired
public void setLocationrepo(LocationRepository locationrepo) {
this.locationrepo = locationrepo;
}
public Location saveLocation(Location location) {
// TODO Auto-generated method stub
return locationrepo.save(location);
}
public Location updateLocation(Location location) {
// TODO Auto-generated method stub
return locationrepo.save(location);
}
public void deleteLocation(Location location) {
// TODO Auto-generated method stub
locationrepo.delete(location);
}
public Location getLocationById(int id) {
// TODO Auto-generated method stub
return locationrepo.findById(id).get();
}
public List<Location> getAllLocations() {
// TODO Auto-generated method stub
return locationrepo.findAll();
}
public LocationRepository getLocationrepo() {
return locationrepo;
}
}
And this is my entity Class
package com.logan.location.entities;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class Location {
#Id
private int id;
private String code;
private String name;
private String type;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
This is the starter class
package com.logan.location;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#EntityScan("com.logan.location.entities")
#EnableJpaRepositories(basePackages = {"com.logan.location.repos"})
#SpringBootApplication
public class LocationApplication {
public static void main(String[] args) {
SpringApplication.run(LocationApplication.class, args);
}
}
It is showing my location entity class is unmanaged ,I have tried various answers but its not working ,any help??
Remove the #Repository annotation before LocationRepository. There is no need to add it.
public interface LocationRepository extends JpaRepository<Location, Integer> {
}
Also remove #EntityScan("com.logan.location.entities") and #EnableJpaRepositories(basePackages = {"com.logan.location.repos"})
#SpringBootApplication
public class LocationApplication {
public static void main(String[] args) {
SpringApplication.run(LocationApplication.class, args);
}
}
Add location repository bean like this:
#Service
public class LocationServiceImpl implements LocationService {
private LocationRepository locationrepo;
public LocationServiceImpl(LocationRepository locationrepo){
this.locationrepo = locationrepo;
}
}
Try with this.
Please add #Configuration and #ComponentScan annotations in your LocationApplication class. And also you are missing the #Column annotaions in the entity class as well and please autowire the service properly.
#Autowired
private LocationRepository locationrepo;

#Inject delivers null in Weld 3 using Java SE

I am using CDI 2.0 with Weld 3.0.0 final (complete weld-se-shaded.jar in the classpath) in a plain Java SE 8 program, as shown below. What is wrong with it or am I missing something, since #Inject does nothing, i.e., the references stay null? Programmatic access as illustrated works.
I thought to post this into JBoss/Weld Jira Bug tracking system. However, after registration and login, I cannot find the button to create a new entry.
Thanks for your help,
StartUp.java
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
public class StartUp {
public static void main(String[] args) {
// start CDI Container
SeContainerInitializer initializer = SeContainerInitializer.newInstance();
try (SeContainer container = initializer.initialize()) {
Test t = new Test();
// start tests
t.test(container);
}
}
}
Test.java
import javax.enterprise.event.Event;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.spi.CDI;
import javax.inject.Inject;
public class Test {
#Inject
// IMyBean myBean; // does not work
MyBean myBean; // does not work
#Inject
Event<UserEvent> event; // does not work
public void test(SeContainer container) {
myBean.greete("World"); // NullPointerException
// manual lookup
// MyBean myBean2 = container.select(MyBean.class).get();
IMyBean myBean2 = CDI.current().select(IMyBean.class).get();
myBean2.greete("World");
event.fire(new UserEvent("info")); // NullPointerException
}
}
MyBean.java
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
#Named
#ApplicationScoped
public class MyBean implements IMyBean {
public void greete(String s) {
System.out.println("Hello, " + s + "!");
}
}
SyncEventObserver.java
import javax.enterprise.event.Observes;
public class SyncEventObserver {
public void observeUserEvent(#Observes UserEvent userEvent) {
System.out.println("Received event:" + userEvent.getMessage());
}
}
UserEvent.java
public class UserEvent {
private String message;
public UserEvent() {
}
public UserEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
META-INF/beans.xml
<beans version="2.0" bean-discovery-mode="all"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd">
</beans>
IMyBean.java
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
#Named
#ApplicationScoped
public interface IMyBean {
void greete(String s);
}
Your main method should look like this
public static void main(String[] args) {
// start CDI Container
SeContainerInitializer initializer = SeContainerInitializer.newInstance();
try (SeContainer container = initializer.initialize()) {
Test t = container.select(Test.class).get();
// start tests
t.test(container);
}
}
The reason being - you have injection points in your test class, but you instantiate it. That removes CDI from managing your beans.

Qualifier not working in spring

CallingApp.java
#Service
#ComponentScan(basePackages = { "com.codegeekslab.type" })
public class CallingApp {
#Autowired
#Qualifier("BasicPhone")
private Phone phone;
public CallingApp(Phone phone) {
this.phone = phone;
}
public void makeCall(int number) {
phone.openApp(number);
}
}
Phone.java
package com.geekslab.device;
public interface Phone {
public void openApp(int number);
}
BasicPhone.java
package com.codegeekslab.type;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.geekslab.device.Phone;
#Component("BasicPhone")
public class BasicPhone implements Phone {
{
System.out.println("BasicPhone");
}
public void openApp(int number) {
System.out.println("calling via simcard... " + number);
}
}
SmartPhone.java
package com.codegeekslab.type;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.geekslab.device.Phone;
#Component("SmartPhone")
public class SmartPhone implements Phone {
{
System.out.println("SmartPhone");
}
public void openApp(int number) {
System.out.println("calling via whatsapp..." + number);
}
}
Test.java
package com.codegeekslab.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.codegeekslab.app.CallingApp;
import com.codegeekslab.type.BasicPhone;
import com.codegeekslab.type.SmartPhone;
import com.geekslab.device.Phone;
public class Test {
public static void main(String[] args) {
//ApplicationContext context =
// new GenericXmlApplicationContext("beans.xml");
//SpringHelloWorld helloSpring = context.getBean("springHelloWorld", SpringHelloWorld.class);
//comment this for xml less spring
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.codegeekslab.app","com.codegeekslab.type");
//context.register( BasicPhone.class,SmartPhone.class,CallingApp.class);
context.refresh();
CallingApp callingApp = context.getBean("callingApp", CallingApp.class);
callingApp.makeCall(99999);
}
}
Even though i am giving qualifier as #Qualifier("BasicPhone") in CallingApp class ,I am getting Exception as follows:
No qualifying bean of type [com.geekslab.device.Phone] is defined: expected single matching bean but found 2: BasicPhone,SmartPhone
You pass phone as a constructor argument in your CallingApp service without specifying the bean.
Try either to put a qualifier at your constructor or stick to the autowire injection which is something your already do.
i removed the CallingApp class constructor and it worked.
public CallingApp(Phone phone) {
this.phone = phone;
}
As Constructor was overriding the setter method.
You need to add no argument constructor
public CallingApp(){
//do nothing
}
public CallingApp(Phone phone) {
this.phone = phone;
}

Categories

Resources