In my spring boot application I have a class 'repo' that is found by its class name
Class<?> repo = Class.forName("com.example.demo.repository." + modelName + "Repository");
where the modelName is a String.
repo.toString() returns
interface com.example.demo.repository.LaptopRepository
I want to have an ability to use laptopRepository.findAll() methods.
I exactly don't know which modelName I will have.
So I could not use #Autowired annotation outside the method.
Instead I want to use laptopRepository inside the method, which takes the modelName attribute.
#GetMapping("/administration")
public String getModelInstances(#RequestParam("modelName")String modelName, Model model) throws ClassNotFoundException {
Class<?> repo = Class.forName("com.example.demo.repository." + modelName + "Repository");
// #Autowired
// repo repoRepository;
model.addAttribute("objects", repoRepositories.findAll());
return "administration";
}
Just use Sping application context to get desired repository bean by its type.
1) Autowire context in your controller
#Autowired
private ApplicationContext appContext;
2) Use it in your method
appContext.getBean(Class.forName("com.example.demo.repository." + modelName + "Repository"));
Related
I have my JWT utils class:
#Component
public class JwtUtils {
private final String jwtSecret;
private final int jwtExpirationMs;
public JwtUtils(#Value("${app.jwtSecret}") String jwtSecret, #Value("${app.jwtExpirationMs}") String jwtExpirationMs)){
this.jwtSecret = jwtSecret;
this.jwtExpirationMs = jwtExpirationMs;
}
...
}
and inside my WebTokenConfig I need to initialize it for my AuthTokenFilter:
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter(new JwtUtils(..., ...), ...);
}
So basically, JwtUtils needs 2 parameters, but those parameters should be set from properties' context. How to handle the constructor's injection?
Plus, I'm trying to test JwtUtils class with a junit test with mockito.
Using #Autowired on fields (and not on constructor) and then instantiating the bean with new JwtUtils(), those parameters are not being init'd, staying null and 0 (even with #TestPropertySource, System.setProperties, ...). That's why I'm trying with constructor injection (which as I read is always the best option). This way I just have to pass the parameters through the new JwtUtils() and that's it.
But when done inside other beans (no test), it just makes no sense to me to pass them since they should be retrieved from context and not be passed.
I'm probably missing something.
Spring Boot | MyBatis
When I try to declare a mybatis mapper in controller, it gets underlined by IDE, and doesn't compile.
#Controller
#RequestMapping("demo")
#MapperScan("com.sample.mapper")
public class MessageController {
private static final String MESSAGE = "message";
private static final String INDEX = "index";
#Autowired
private MessageMapper messageMapper;
#RequestMapping("printMessage/{message}")
public String printMessage(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "M");
return INDEX;
}
#RequestMapping("printHello")
public String printHello(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "Hello, ");
return INDEX;
}
I got this class compiled somehow recently, however, when I try to use messageMapper instance, like messageMapper.insert() as it's not assigned any value, it gives me NullPointerException. It seems like Spring is for some reason is not working for me.
According to the documencation, I think the #MapperScan is not the right class, they cannot be autowired because they are not in the context on controller creation time. When it is defined in Mybatis XML config file, it is loaded with an Sql Session Factory Provider, a place that actually makes more sense, then it shall not be different with annotations style.
I've read questions here in stackoverflow such as:
Anyway to #Autowire a bean that requires constructor arguments?
How to #Autowire bean with constructor
I've also read links provided in these questions such as 3.9.3 Fine-tuning annotation-based autowiring with qualifiers but nothing that I tried worked.
Here's my class:
public class UmbrellaRestClient implements UmbrellaClient {
private static final Logger LOGGER = LoggerFactory.getLogger(UmbrellaRestClient.class);
private static final Map<String, String> PARAMETROS_INFRA_UMBRELLA = ApplicationContextProvider.getApplicationContext().getBean(ParametrosInfraComponent.class)
.findByIdParametroLikeAsMap("%UMBRELLA%");
private final HttpConnectionRest conexaoHttp;
#Autowired
#Qualifier
private TemplateLoaderImpl templateLoader;
public UmbrellaRestClient(final String url) {
this.conexaoHttp = new HttpConnectionRest(UmbrellaRestClient.PARAMETROS_INFRA_UMBRELLA.get("UMBRELLA_HOST") + url, "POST", true);
}
/**
* {#inheritDoc}
*/
#Override
public String enviarNfe(final String cnpjFilial, final String idPedido, final BigDecimal valorGNRE, final String arquivoNfe) {
if (StringUtils.isBlank(arquivoNfe)) {
throw new ClientException("Arquivo de NF-e não carregado.");
}
final String usuario = StringUtils.defaultIfBlank(UmbrellaRestClient.PARAMETROS_INFRA_UMBRELLA.get("USUARIO_UMBRELLA"), "WS.INTEGRADOR");
Map<String, String> parametrosTemplate = new HashMap<>(6);
parametrosTemplate.put("usuario", usuario);
parametrosTemplate.put("senha", StringUtils.defaultIfBlank(UmbrellaRestClient.PARAMETROS_INFRA_UMBRELLA.get("SENHA_UMBRELLA"), "WS.INTEGRADOR"));
parametrosTemplate.put("valorGNRE", valorGNRE.toPlainString());
parametrosTemplate.put("idPedido", idPedido);
parametrosTemplate.put("cnpjFilial", cnpjFilial);
parametrosTemplate.put("arquivoNfe", arquivoNfe);
final String xmlRequisicao = ConverterUtils.retornarXMLNormalizado(this.templateLoader.preencherTemplate(TemplateType.ENVIO_XML_NFE, parametrosTemplate));
this.conexaoHttp.setXmlEnvio(xmlRequisicao);
UmbrellaRestClient.LOGGER.info("XML ENVIO #####################: {}", xmlRequisicao);
return this.conexaoHttp.enviarXML();
}
}
The field templateLoader does not get injected. I tested in other classes that have dependency injection and works. I guess this is happening because I have a constructor that depends on a parameter and this parameter is really passed by each class that needs to use it so I cannot use dependency injection to the parameter of the constructor in applicationContext for example.
What should I do to get field injected?
Using Rest APIs with Spring framework needs to be handled differently. Here is brief explanation.
Spring is a framework that maintains the lifecycle of the component beans and is fully responsible from bean creation to their destruction.
REST APIs are also responsible for maintaining the life cycle of the web services they create.
So, Spring and REST container are working independently to manage the components they have created effeciently.
In my recent project what I did to use both technologies, by creating a seperate class which implements Spring's ApplicationContextAware interface, and collect the beans in a HashMap. This resource can be accessed statically from REST contexts.
The weak point about this is we have to use beans.xml file and register the beans and in the class that implements ApplicationContextAware interface getting the beans by name etc.
The easiest way to create a Spring controlled bean is directly through the ApplicationContext:
#Autowired
private ApplicationContext context;
private UmbrellaRestClient getNewUmbrellaRestClient(String url) {
return context.getBean("umbrellaRestClient", new Object[]{url});
}
Basically this is a factory method. For this to work the UmbrellaRestClient must be declared a bean of scope prototype. As all beans that have a non default constructor must be of scope prototype.
In the case where the class is in a package that is component scanned, this will suffice:
#Service
#Scope("prototype")
public class UmbrellaRestClient implements UmbrellaClient {
...
I have an entity with a private field countryCode. I want to add a convenient method in my entity class for setting the country code: it's possible to set the country code either with an CountryCode object or a string.
If the country code is set by string, a CountryCode repository is necessary. However, I can't get Spring to initialize the repository field. Even if I put #Component and #Scope("prototype") on my entity...
What am I missing ?
private CountryCode countryCode;
public void setCountryCode(String code) {
this.countryCode = getByCode(code);
}
#Autowired
#Transient
private CountryCodeRepository countryCodeRepository;
private CountryCode getByCode(String code) {
if (code == null) {
throw new NullPointerException("The country code cannot be null.");
}
// countryCodeRepository is NULL below...
CountryCode finalCC = countryCodeRepository.findByAlpha2OrAlpha3(code);
// ...
}
I guess your entity class is instantiated by some ORM framework and therefore not instantiated by spring. So spring can not autowire the fields automatically.
If so you have these option
Using AspectJ to dependency inject domain objects with Spring
if you are using hibernate you can write an PostLoadListener which also implements ApplicationContextAware
get the AutowireCapableBeanFactory and autowire the loaded entities on post load.
Please provide more information.
I have configured a custom generic service DAO for my spring / hibernate project - the idea being that I can reuse it easily from my controllers.
It essentially looks like this:
public class DefaultService<T> {
private Class<T> e;
public String className(Class<T> e) {
String clip = e.getName();
clip = clip.substring(clip.lastIndexOf('.') + 1, clip.length());
return clip;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + className(e) + " WHERE status = " + status);
return query.list();
}
...
Which gets referenced by:
#Autowired
public DefaultService<Address> addressService;
addressService.get(1);
However the String clip = e.getName() line throws a Null pointer exception. I can get this to work if I move the class into the attributes section (so addressService.get(Address.class, 1) but I find this somewhat untidy, especially when there are multiple different classes being called upon.
Is there some way to get the class to generate a value correctly without repeatedly adding it into all my functions?
Thanks in advance.
I did something similar, you need the generic class to be a constructor argument as well, mine uses hibernate entities, but you could pass in the string of table name.
public class DomainRepository<T> {
#Resource(name = "sessionFactory")
protected SessionFactory sessionFactory;
public DomainRepository(Class genericType) {
this.genericType = genericType;
}
#Transactional(readOnly = true)
public T get(final long id) {
return (T) sessionFactory.getCurrentSession().get(genericType, id);
}
You can then subclass (if you need to) to customize or simply set up you bean in the spring config like below t :
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
So in your code you could then reference tagRepository like so (no other cod eis needed than that posted above, and below) :
#Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
Also, I would call it a repository not a service, a service deals with different types and their interactions (not just one). And for specifically your example using SQL strings :
public final String tableName;
public DomainRepository(String tableName) {
this.tableName = tableName;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + tableName + " WHERE status = " + status);
return query.list();
}
and have your beans defined like so
<bean id="addressRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="address"/>
</bean>
And then you can alsow create subclasses youself where necessary :
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super("person"); //assumes table name is person
}
As I understand you got NPE because you did not set any value for this field.
So you can resolve this problem by 2 ways:
Set manually class object as in comment NimChimpsky.
Get class type dynamically. E.g, if you use Spring try this one:
protected Class getEntityClass() {
return GenericTypeResolver.resolveTypeArguments(getClass(), DefaultService.class)[0];
}
or some workaround here
It's better to define a specific class for Address service
public class AddressService extends DefaultService<Address>{
public String getClassName(){
return "Address";
}
}
where
public String getClassName();
is an abstract method declared in DefaultService, and used (like your method className()) in your data access logic.
Using this approach, you will be able to add specific data access logic (example, getUsersByAddress)