I should probably mention that Spring is my first framework that I am learning for Java.
What I am trying to do is to get the JDBC working for me using the Spring Data JDBC dependency I downloaded from spring.io. (2.6.4) I followed a tutorial on YouTube where the guy demonstrated it with something similar to the following code. (Should also mention the code he used was for Spring version 2.2 but I'm not sure where else to find right code for 2.6.4)
There is also an UnsatisfiedDependencyException I keep getting where it says:
Error creating bean with name 'applicationRunner' defined in com.example.demo.DemoApplication: Unsatisfied dependency expressed through method 'applicationRunner' parameter 0;
I do not know why this is happening. But here is the actual code:
package com.example.demo;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
ApplicationRunner applicationRunner(StudentRepo studentRepo) {
return args -> {
var s1 = Student.createStudent("John", "Doe");
var s2 = Student.createStudent("Jane", "Doe");
System.out.println(studentRepo.save(s1));
System.out.println(studentRepo.save(s2));
System.out.println(studentRepo.findByFName("John"));
};
}
}
CRUD Repository interface:
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface StudentRepo extends CrudRepository<Student, String> {
#Query("SELECT * FROM Students WHERE name = :name")
List<Student> findByFName(#Param("name") String fName);
}
schema.sql file:
CREATE TABLE Students (
id VARCHAR(50) IDENTITY PRIMARY KEY,
firstName VARCHAR(50),
lastName VARCHAR(50),
rank VARCHAR(50)
);
I think it's also important for me to include pom.xml file: (Though it should also be mentioned that the pom.xml file didn't originally include the hsqldb dependency. I added that in myself.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Edit:
Exception after including #Repository in the CRUD repository interface:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationRunner' defined in com.example.demo.DemoApplication: Unsatisfied dependency expressed through method 'applicationRunner' parameter 0
Exception after replacing #Repository with #NoRepositoryBean:
2022-03-23 18:30:06.531 INFO 98567 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-03-23 18:30:06.556 ERROR 98567 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
APPLICATION FAILED TO START
Description:
Parameter 0 of method applicationRunner in com.example.demo.DemoApplication required a bean of type 'com.example.demo.StudentRepo' that could not be found.
Action:
Consider defining a bean of type 'com.example.demo.StudentRepo' in your configuration.
Process finished with exit code 1
try modifying the bean:
#Bean
public CommandLineRunner demo(StudentRepo studentRepo) {
return (args) -> {
studentRepo.save(new Student("Jack", "Bauer"));
studentRepo.save(new Student("Chloe", "O'Brian"));
}
you can consult the guide https://spring.io/guides/gs/accessing-data-jpa/
The problem lies in the schema.sql script. An IDENTITY column in HSQLDB has to be of the type INTEGER or BIGINT (see documentation at http://www.hsqldb.org/doc/1.8/guide/ch09.html, section CREATE TABLE). All other errors are just subsequent errors.
Related
I'm trying to build a Backend Service project using the example from the site
Using Spring Cloud Gateway with OAuth 2.0 Patterns
Here is the repository itself
backend
Added dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.test.gw.oauth.resource</groupId>
<artifactId>backresource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backresource</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>14</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
I moved the properties from the quotes-application.properties file to this project
server.port=11002
# Resource server settings
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:8484/auth/realms/demo/protocol/openid-connect/token/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id=gateway
spring.security.oauth2.resourceserver.opaquetoken.client-secret=dfdslksfkljweewrfsd
Added a class
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import reactor.core.publisher.Mono;
// Custom ReactiveTokenIntrospector to map realm roles into Spring GrantedAuthorities
public class KeycloakReactiveTokenInstrospector implements ReactiveOpaqueTokenIntrospector {
private final ReactiveOpaqueTokenIntrospector delegate;
public KeycloakReactiveTokenInstrospector(ReactiveOpaqueTokenIntrospector delegate) {
this.delegate = delegate;
}
#Override
public Mono<OAuth2AuthenticatedPrincipal> introspect(String token) {
return delegate.introspect(token)
.map( this::mapPrincipal);
}
protected OAuth2AuthenticatedPrincipal mapPrincipal(OAuth2AuthenticatedPrincipal principal) {
return new DefaultOAuth2AuthenticatedPrincipal(
principal.getName(),
principal.getAttributes(),
extractAuthorities(principal));
}
protected Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
//
Map<String,List<String>> realm_access = principal.getAttribute("realm_access");
List<String> roles = realm_access.getOrDefault("roles", Collections.emptyList());
List<GrantedAuthority> rolesAuthorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
Set<GrantedAuthority> allAuthorities = new HashSet<>();
allAuthorities.addAll(principal.getAuthorities());
allAuthorities.addAll(rolesAuthorities);
return allAuthorities;
}
}
And the main class of the project
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import ru.test.gw.oauth.resource.backresource.security.KeycloakReactiveTokenInstrospector;
#SpringBootApplication
//#PropertySource("classpath:quotes-application.properties")
#EnableWebFluxSecurity
public class BackresourceApplication {
public static void main(String[] args) {
SpringApplication.run(BackresourceApplication.class, args);
}
#Bean
public SpringOpaqueTokenIntrospector keycloakIntrospector(OAuth2ResourceServerProperties props) {
NimbusReactiveOpaqueTokenIntrospector delegate = new NimbusReactiveOpaqueTokenIntrospector(
props.getOpaquetoken().getIntrospectionUri(),
props.getOpaquetoken().getClientId(),
props.getOpaquetoken().getClientSecret());
return new KeycloakReactiveTokenInstrospector(delegate);
}
}
And in this class I get an error on SpringOpaqueTokenIntrospector, writes that it is not defined. Although all the imports completely coincide with the training example.
If I add a dependency that the IDE tells me to
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
, then I get an error
Type mismatch: cannot convert from KeycloakReactiveTokenInstrospector to SpringOpaqueTokenIntrospector
What's the problem here? Is there some kind of dependency missing?
I completely repeated the structure of the project from the training material.
So far, I would like to build a project without errors.
Marcus is right in his comment, your keycloakIntrospector #Bean type should be ReactiveOpaqueTokenIntrospector (and not SpringOpaqueTokenIntrospector as declared in your conf)
Few facts:
SpringReactiveOpaqueTokenIntrospector is a ReactiveOpaqueTokenIntrospector but SpringOpaqueTokenIntrospector isn't
your KeycloakReactiveTokenInstrospector is (implements) a ReactiveOpaqueTokenIntrospector too but is neither a SpringReactiveOpaqueTokenIntrospector, SpringOpaqueTokenIntrospector nor OpaqueTokenIntrospector
Side notes
Introspection VS JWT decoding
Keycloak issues JWTs. JWT decoding is far more efficient than introspection: resource-server needs to fetch public-key only once from authorization-server to validate all incoming JWTs when introspection requires to submit access-token to authorization-server for each and every incoming request.
Also, you might not be able to implement multi-tenant scenarios with introspection: how to figure out by which issuer (Keycloak instance or realm) an opaque token was emitted? => you would have to "try" introspection on each issuer until one responds positively :/
Overriding introspector VS providing an authentication converter
If you switch to spring-security 5.8 or higher, customizing introspection is easier: you don't have to override the all introspector but can just provide a ReactiveOpaqueTokenAuthenticationConverter bean instead:
http.oauth2ResourceServer().opaqueToken().authenticationConverter(
(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) ->
new BearerTokenAuthentication(...));
This bean is called after introspection was successfuly completed (and token attributes retrieved) but before Authentication is instanciated and put in security-context which allows you to just map authorities from any attribute you like or completely switch the authentication implementation.
Simplifying your resource-server configuration
I host a set of libs to ease OAuth2 resource-server testing and configuration. There are various spring-boot starters depending on introspection or JWT decoding is used into servlet or reactive apps.
According to your case (reactive app with introspection), you should have a look at this sample with BearerTokenAuthentication and this other one with a custom authentication.
Configuration can be as simple as:
#EnableReactiveMethodSecurity
#Configuration
public class SecurityConfig {
}
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://localhost:8443/realms/master/protocol/openid-connect/token/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id=spring-addons-confidential
spring.security.oauth2.resourceserver.opaquetoken.client-secret=change-me
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,resource_access.spring-addons-public.roles,resource_access.spring-addons-confidential.roles
# this is probably too permissive, addapt to your needs
com.c4-soft.springaddons.security.cors[0]=/**
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webflux-introspecting-resource-server</artifactId>
<version>6.0.3</version><!-- warning, this version goes with spring-boot 3.0.0-RC1 -->
</dependency>
I have been checking all of my code for hours now and i just can't seem to find tthe error in here
it keeps throwing org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'imageRepository': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Not a managed type: class entitiys.Image
my controller looks like this:
package imageSearcher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class AppController {
public static void main(String[] args) {
SpringApplication.run(AppController.class, args);
}
}
this is what the Image entity looks like:
package entitiys;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity(name = "image")
public class Image {
#Id
#Column(name = "id")
public Integer id;
#Column(name= "imageURL")
public String imageURL;
}
The image mapping looks as following
package imageSearcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import entitiys.Image;
#CrossOrigin(origins = "*", allowedHeaders = "*")
#RestController
public class ImageController {
#Autowired
private ImageRepository imageRep;
#GetMapping(path="/all")
public Iterable<Image> index() {
return imageRep.findAll();
}
#GetMapping(path = "/all/URL")
public Iterable<String> AllURL() {
return imageRep.findAllURL();
}
}
the imahe CrudRepository looks like this:
package imageSearcher;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import entitiys.Image;
#Repository
public interface ImageRepository extends CrudRepository<Image, Integer>{
#Query(value = "select * from Image", nativeQuery = true)
Iterable<Image> findAll();
#Query(value = "select Image.imageURL from Image", nativeQuery = true)
Iterable<String> findAllURL();
}
this is the pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Use MySQL Connector-J -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
and this is the application.properties:
server.port=91
spring.datasource.url=jdbc:mysql://localhost:3306/gip2021
spring.datasource.username=root
spring.datasource.password=
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.naming_startegy=org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.hibernate.ddl-auto=none
I have no clue of what is wrong here i hope someone can help me because this is for my final project and even my Teacher can't see the issue
also here is the database script for if you need it:
create table Image(
id integer unsigned auto_increment primary key,
imageURL varchar(255)
);
insert into image (imageURL) values
("test"),
("test2");
for anyone wanting the git repo:
https://github.com/michiel2003/GIP2021.git
Just moved everything to the same package and it worked my teacher told me to put the entitiys in a different package and that's not what you are supposed to do
The from spring boot documentation for #SpringBootApplication :
Many Spring Boot developers always have their main class annotated
with #Configuration, #EnableAutoConfiguration and #ComponentScan.
Since these annotations are so frequently used together (especially if
you follow the best practices above), Spring Boot provides a
convenient #SpringBootApplication alternative.
The #SpringBootApplication annotation is equivalent to using
#Configuration, #EnableAutoConfiguration and #ComponentScan with their
default attributes: [...]
and #ComponentScan:
If specific packages are not defined, scanning will occur from the
package of the class that declares this annotation.
So only imageSearcher package is being scanned. You need to rearrange you packages or change #SpringBootApplication annotation to
#SpringBootApplication(scanBasePackages = {"imageSearcher","entitiys"})
EDIT:
If your teacher told you to move it to different package maybe he wanted you to make workaround for just like I proposed
created the pull request: https://github.com/michiel2003/GIP2021/pull/1 that will fix the bug, you can simply merge it :)
agreeing with every points that have already been mentioned above regarding #ComponentScan and adding additional things that caught my eyes:
this bug can be easily reproduced by adding a dummy loadContext test, this test will be generated when you start bootstrapping a spring boot application by using spring initializr, which is always the best way to start spring booting
I know test experience has always been treated as an overhead for junior developers, but it is worth it if you want to go down the road to a senior :)
when looking into your git histories: please do not simply delete getter and setters just because they look like template code, if you feel comfortable you can try adding lombok
I upgrade my Spring boot version from 2.0.5.RELEASE to 2.1.8.RELEASE (so Spring Integration from 5.0 to 5.1) and the automatic type casting inside integration flow doesn't work anymore. I am used to define a set of #IntegrationConverter components and automatic casting with the operation transform(Type.class, p -> p) inside integration flow code but with the new version it seems to be broken.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.grorg</groupId>
<artifactId>grointegration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grointegration</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ip</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
And the Main.java file:
package org.grorg.grointegration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.integration.config.IntegrationConverter;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.ip.dsl.Tcp;
import org.springframework.stereotype.Component;
class Test {
#Override
public String toString() {
return "test";
}
}
#Component
#IntegrationConverter
class Convert implements Converter<String, Test> {
#Override
public Test convert(String s) {
return new Test();
}
}
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(GrointegrationApplication.class, args);
}
#Bean
public IntegrationFlow server() {
return IntegrationFlows
.from(Tcp.inboundGateway(Tcp.netServer(1234)))
.transform(Transformers.objectToString())
.transform(Test.class, id -> id) // in 2.1 I could use .convert(Test.class) but the problem is the same
.log()
.handle((p, h) -> "OK")
.get();
}
}
Use with a shell:
telnet localhost 1234
> test
> OK
[...]
With the previous version (2.0.5.RELEASE) the program work nicely like previously, but with the new version (2.1.8.RELEASE) I get this error (and no "OK" response):
org.springframework.integration.handler.ReplyRequiredException: No reply produced by handler 'server.org.springframework.integration.config.ConsumerEndpointFactoryBean#1', and its 'requiresReply' property is set to true.
[...]
What I have found is that the ConversionService has been remplaced by MessageConverter and now Jackson is used to transform message from one type to another.
Am I wrongly using type casting with integration flow? Do you have a new solution for casting object with the new version? Or is this just a regression?
Thanks in advance!
This is a bug in Spring Integration.
We just don't use a proper ConversionService in the GenericMessageConverter.
Please, raise a GH issue on the matter.
Meanwhile a workaround for you is like this:
#Bean(name = IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)
public static ConfigurableCompositeMessageConverter configurableCompositeMessageConverter(
#Qualifier(IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME) ConversionService conversionService) {
return new ConfigurableCompositeMessageConverter(
Collections.singleton(new GenericMessageConverter(conversionService)));
}
So, we override whatever is configured by the framework and inject a proper IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME bean which is supplied with your #IntegrationConverter component.
I've tried to create an Application and ran into some funky behavior. First, I'll run through my setup. Here's my configuration class:
ProblemApp.java
#SpringBootApplication
public class ProblemApp
{
public static void main(String[] args)
{
var context = SpringApplication.run(ProblemApp.class);
var tblController = context.getBean(TableController.class);
tblController.printTable();
}
#Bean
public TableController getTableController()
{
return new TableController();
}
#Bean("componentTable")
public String[] getComponentTable() //weird
{
return new String[] { "application.components" };
}
}
Here's my component:
TableController.java
#Controller
public class TableController
{
private static final Logger log = LoggerFactory.getLogger(TableController.class);
#Autowired
private String[] componentTable; //weird
public void printTable()
{
log.info("Component table: " + Arrays.deepToString(componentTable));
}
}
This is my module-info.java and directory structure:
module problem.application
{
exports application;
exports controller;
opens controller to spring.core;
opens application to spring.core;
requires spring.context;
requires spring.boot;
requires spring.boot.autoconfigure;
requires spring.beans;
requires spring.core;
requires java.sql;
requires org.slf4j;
}
src/main/java/
application
ProblemApp.java
controller
TableController.java
module-info.java
src/test/java/ is empty
src/main/resources/ is empty
This is the pom.xml I use to retrieve dependencies (using Maven in Spring Tool Suite 4):
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>problem.application</groupId>
<artifactId>problem-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Problematic application</name>
<description>An application with some problems</description>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>12</release>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
This code, as it is, works as I expected it to. The relevant part of the output is:
2019-09-11 10:55:21.351 INFO 21060 --- [ main] controller.TableController :
Component table: [application.components]
Now, I tried to change the type of Bean componentTable to Object[]. In ProblemApp:
#Bean("componentTable")
public Object[] getComponentTable() //weird
{
return new Object[] { "application.components" };
}
In TableController:
#Autowired
private Object[] componentTable; //weird
Suddenly, the output of the program changes drastically:
2019-09-11 10:57:05.630 INFO 10156 --- [ main] controller.TableController :
Component table: [org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#b40bb6e,
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#3f28bd56,
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#3276732,
org.springframework.context.annotation.ConfigurationClassPostProcessor#f74e835,
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#48c35007,
org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar#4b3c354a,
org.springframework.context.event.EventListenerMethodProcessor#31e3250d,
org.springframework.context.event.DefaultEventListenerFactory#19fe4644,
application.ProblemApp$$EnhancerBySpringCGLIB$$7e1f363f#21d8bcbe,
...
// more Spring-looking things and then environment information
What is going on here?
When auto-wring on an array or a list of type T , it will first try to inject all beans which the type is T into it. So for :
#Autowired
private String[] componentTable;
It will first try to inject all beans which the type is String . However since there are no such beans, it will then try to inject a bean which the type is String[] . As you define componentTable bean as String[] , it will be injected.
Follow the same logic on :
#Autowired
private Object[] componentTable;
It will first try to inject all beans which the type is Object. As every bean must be an Object type . It means all spring beans will be injected. Hence it prints out all the spring bean information.
I am trying to deploy the spring boot application with built in Tomcat Server. And, I am getting the below error before even the application gets started.
Field error in object 'spring.datasource' on field 'type': rejected value [org.apache.tomcat.dbcp.dbcp2.BasicDataSource]; codes [typeMismatch.spring.datasource.type,typeMismatch.type,typeMismatch.java.lang.Class,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [spring.datasource.type,type]; arguments []; default message [type]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Class' for property 'type'; nested exception is java.lang.IllegalArgumentException: Cannot find class [org.apache.tomcat.dbcp.dbcp2.BasicDataSource]] - o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext
2017-11-26 22:18:27:489 - INFO - Correlation-Id = - AppName = - Server-IP = - RequestorApp = - RequestorIp = - UserId = - Total-Time = -
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled. - o.s.b.a.l.AutoConfigurationReportLoggingInitializer
2017-11-26 22:18:27:492 - ERROR - Correlation-Id = - AppName = - Server-IP = - RequestorApp = - RequestorIp = - UserId = - Total-Time = -
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.autoconfigure.jdbc.DataSourceProperties#466317f failed:
Property: spring.datasource.type
Value: org.apache.tomcat.dbcp.dbcp2.BasicDataSource
Reason: Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Class' for property 'type'; nested exception is java.lang.IllegalArgumentException: Cannot find class [org.apache.tomcat.dbcp.dbcp2.BasicDataSource]
Action:
Update your application's configuration
- o.s.b.d.LoggingFailureAnalysisReporter
Given below is my pom.xml file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cnanational.productservice</groupId>
<artifactId>product-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>product-service</name>
<description>Product Service</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss z</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.cnanational.servicecommon</groupId>
<artifactId>service-common</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.5.4</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven-releases</id>
<name>CNA National Release Artifacts</name>
<url>https://nexus.cnanational.net/repository/maven-releases/</url>
</repository>
<repository>
<id>maven-snapshots</id>
<name>CNA National Snapshot Artifacts</name>
<url>https://nexus.cnanational.net/repository/maven-snapshots/</url>
</repository>
<repository>
<id>maven-central</id>
<name>CNA National Central</name>
<url>https://nexus.cnanational.net/repository/maven-central/</url>
</repository>
</repositories>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<packagingExcludes>
**/product-service/
</packagingExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Given below is the spring-boot yml file.
security:
user:
name: qcSa
password: qcPassword
server:
port: 8100
context-path: /product-service
endpoints:
jmx:
domain: com.cnanational.productservice
management:
context-path: /${common.application-version}/actuator
spring:
application:
name: product-service
datasource:
# jndi-name: jdbc/web_base_user
url: jdbc:oracle:thin:#devrac-scan.cnanational.net:1521/d02wpa.cnanational.net
username: web_base_user
password: ubew6130
type: org.apache.tomcat.dbcp.dbcp2.BasicDataSource
product-service:
paths:
product-attributes: '/dealerProductAttributes'
product-surcharges: '/surcharges'
product-products: '/products'
deductible-descriptions:
S: Standard
D: Disappearing
sales-ws:
validation-rules-endpoint:
endpoint: https://dev03tc.cnanational.com/sales-ws/v1/uiGlobalSettings/search?appName=SalesRatingTool
user: SVC_DEV_CORS
password: 113501*3Q3E105^9^z7b
sql-properties:
product-procedure-no-rates-error-code: 20000
controlobj-sql: >
begin begin dbms_session.set_role(controlobj.getdisableddbroles);
exception when others then null;
end;
controlobj.web_set_user('%s', 'N','%s');
end;
controlobj-clear-errors-sql: >
call errorobj.clearerrormessage()
controlobj-query-errors-sql: >
select error_message from error_messagev1
agent-sales-rating-id-seq-sql:
select agent_sales_rating_id_seq.nextval from dual
rate-insertion-sql: >
insert into agent_sales_rating_parms
(agent_sales_rating_id, parm_name, parm_value1, parm_value2, parm_value3)
values
(:agent_sales_rating_id, :parm_name, :parm_value1, :parm_value2, :parm_value3)
rate-procedure-name: ins_agent_sales_rating_outputs
rate-params-query-sql: >
select
agent_sales_rating_id,
parm_name,
parm_value1,
parm_value2,
parm_value3
from
agent_sales_rating_parms
where
agent_sales_rating_id = :agent_sales_rating_id
and parm_name in (:param_names)
rate-query-sql: >
select
listagg(model_description,',') WITHIN GROUP ( order by model_description) as model
,vehicle_class
,term_months
,term_miles
, series_deductibles_id
, deduct_option
, deduct_amt
, retail_price
, dealer_cost
from
agent_sales_rating_outputs
where
agent_sales_rating_id = :agent_sales_rating_id
group by
vehicle_class
, term_months
, term_miles
, series_deductibles_id
, deduct_option
, deduct_amt
, retail_price
, dealer_cost
order by
vehicle_class desc
, series_deductibles_id
, deduct_option
, deduct_amt
, term_months
, term_miles
common:
application-version: v1
message-automation:
app-name: product-service
heavyweight: false
fallback-message: 'A system error has occurred'
message-ids:
common-exception-handler: 600000
common-error-controller: 600003
required-headers-filter: 600012
too-many-sort-arguments: 600011
invalid-sort-field: 600010
rest:
paths:
build-info: /buildInfo
remote-user-header-required-urls:
- /v1/**
headers:
remote-user: oam_remote_user
correlation-id: Correlation-Id
requestor-app: REQUESTOR_APP
health:
dependencies:
-
name: 'Oracle Datasource'
dependency-type: DB
Below is the context.xml file which has the beans information. And the Java class below has the application properties.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="timeElapsedAspect" class="com.cnanational.servicecommon.aop.TimeElapsedAspect"></bean> <aop:config> <aop:aspect id="timeElapsedAspect" ref="timeElapsedAspect"> <aop:pointcut id="controllerPointcut" expression="execution(public * com.cnanational.productsservice.controller.*.*(..))"/> <aop:around method="logTimeElapsed" pointcut-ref="controllerPointcut"/> </aop:aspect> </aop:config> </beans>
package com.cnanational.productservice;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
import org.springframework.context.support.ResourceBundleMessageSource;
import com.cnanational.productservice.config.ProductProperties;
import com.cnanational.servicecommon.config.CommonConfiguration;
#SpringBootApplication
#EnableConfigurationProperties({ ProductProperties.class })
#Import({ CommonConfiguration.class })
#ImportResource({ "classpath:/product-service/context.xml" })
public class ProductServiceApplication
{
public static final String PROFILE_TEST = "test";
public static void main(String[] args)
{
new SpringApplicationBuilder(ProductServiceApplication.class)
.sources(ProductServiceApplication.class)
.properties(properties())
.run(args);
}
public static Properties properties()
{
Properties props = new Properties();
props.put("spring.config.location", "classpath:product-service/");
props.put("spring.config.name", "product-service");
props.put("logging.config", "classpath:product-service/logback.xml");
return props;
}
#Bean
public MessageSource messageSource()
{
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("product-service/ValidationMessages");
messageSource.setUseCodeAsDefaultMessage(false);
return messageSource;
}
/**
* JUnit test configuration only to be applied when test profile
* is active. This is required for inherited tests to work without
* having to add the EnableAutoConfiguration tag to every concrete
* test class.
*/
#Profile(PROFILE_TEST)
#Configuration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
public static class TestConfiguration
{
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* Let us know that the test configuration is loaded.
*/
#PostConstruct
public void postConstruct()
{
log.info("TestConfiguration loaded");
}
}
}