Je travaille sur un Java/Spring/ Apache Cxf
application web et tout à coup, je reçois l'erreur pendant que j'effectuais des changements apparemment naïfs,
25-Aug-2017 11:48:43.036 INFO [RMI TCP Connection(2)-127.0.0.1] org.Apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
log4j:WARN No appenders could be found for logger (org.Apache.cxf.common.logging.LogUtils).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.Apache.org/log4j/1.2/faq.html#noconfig for more info.
25-Aug-2017 11:48:43.540 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.Apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file
25-Aug-2017 11:48:43.554 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.Apache.catalina.core.StandardContext.startInternal Context [] startup failed due to previous errors
[2017-08-25 11:48:43,586] Artifact jaxrs-tutorials:war exploded: Error during artifact deployment. See server log for details.
25-Aug-2017 11:48:49.258 INFO [localhost-startStop-1] org.Apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/Applications/Tomcat-8.5.16/webapps/manager]
25-Aug-2017 11:48:49.310 INFO [localhost-startStop-1] org.Apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Applications/Tomcat-8.5.16/webapps/manager] has finished in [51] ms
Je suppose que c'est le principal info
de l'erreur,
25-Aug-2017 11:48:43.540 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.Apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file
25-Aug-2017 11:48:43.554 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.Apache.catalina.core.StandardContext.startInternal Context [] startup failed due to previous errors
La structure du projet est fournie,
Le répertoire config
est utilisé pour le Java annotation based config
et le code est fourni ci-dessous.
Le fichier AppConfig
,
@Configuration
@ComponentScan(AppConfig.SERVICE_PACKAGE)
public class AppConfig {
public static final String BASE_PACKAGE = "mobi.puut";
public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".services";
private static final String RESOURCES_PACKAGE = BASE_PACKAGE + ".rest";
private static final String PROVIDER_PACKAGE = BASE_PACKAGE + ".rest.provider";
public static final String API_BASE = "/api/*";
@ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
@Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
@Bean
@DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(), JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setAddress("/" + factory.getAddress());
factory.setProviders(restProviderList(appContext, jsonProvider()));
return factory.create();
}
@Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
@Bean
public JacksonJsonProvider jsonProvider() {
return new JacksonJsonProvider();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, AppConfig.RESOURCES_PACKAGE);
}
private List<Object> restProviderList(final ApplicationContext appContext,
final JacksonJsonProvider jsonProvider) {
final List<Object> providers = RestProviderBeanScanner.scan(appContext, PROVIDER_PACKAGE);
providers.add(jsonProvider);
return providers;
}
}
WebInitializer
est fourni,
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
Set<String> mappingConflicts = appServlet.addMapping(AppConfig.API_BASE);
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(AppConfig.class);
return appContext;
}
}
J'ai vu les questions similaires à la plate-forme et celles-ci ne m'aident pas et j'ai si peu d'informations pour résoudre le problème.
Comment puis-je utiliser logger
pour obtenir plus d'informations et où dois-je les utiliser? De plus, tout aperçu sur la résolution du problème sera utile. J'ai mis à jour les fichiers JAR
avec mvn clean install
et mvn idea:idea
commandes.
UPDATE
Tomcat Localhost Log
Tomcat Catalina Log
Je voudrais écrire une réponse détaillée sur le problème et les étapes que j'ai suivies pour résoudre le problème.
A. Il n'y avait pas assez d'informations de journalisation. J'ai recherché le POM
et l'ai trouvé,
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
J'ai exclu la journalisation en faveur de SLF4J
et je viens de supprimer la balise XML d'exclusion pour obtenir plus d'informations de journalisation. Donc ça devient comme ça,
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
B. Maintenant, je reçois les informations de journalisation. J'ai ce message d'erreur comme,
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userService' for bean class [mobi.puut.services.UserServiceImpl] conflicts with existing, non-compatible bean definition of same name and class [mobi.puut.services.UserService2Impl]
Vous obtenez le ConflictingBeanDefinitionException
pour créer le bean avec le même nom. Bien que j'ai une certaine expérience avec le Spring
, je dois créer avec un projet avec le Apache Cxf
et, par conséquent, clonez un projet de démonstration dans le même but. Ils ont la même entité User
et la même definition - interface
et même implementation
de cela. Bien que j'aie refactored
à toutes les classes du projet et qu'il n'y ait aucune erreur apparente, je garde toujours un problème - je garde le même nom de bean "userService
dans 2 implémentations de l'interface.
Le User2
classe dans les entités,
public class User2 {
private Integer id;
private String name;
public User2() {
}
public User2(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return String.format("{id=%s,name=%s}", id, name);
}
}
La classe User
dans les entités,
@Entity
@Table(name = "users")
public class User {
@Id
@Column
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotNull
@Column(name = "name")
@Size(min = 5, max = 45, message = "Name must be between 5 and 45 characters.")
private String name;
public User() {
}
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
public User(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
if (getId() != user.getId()) return false;
return getName().equals(user.getName());
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getName().hashCode();
return result;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Les interfaces dans les répertoires services
,
public interface IUserService2 {
Collection<User2> getUsers();
User2 getUser(Integer id);
Response add(User2 user);
}
public interface IUserService {
List<User> getCurrentStatuses();
void create(User user);
List<User> getAllUsers();
}
Les implémentations des interfaces à l'intérieur du répertoire services
,
@Service("userService")
public class UserService2Impl implements IUserService2 {
private static Map<Integer, User2> users = new HashMap<Integer, User2>();
static {
users.put(1, new User2(1, "foo"));
users.put(2, new User2(2, "bar"));
users.put(3, new User2(3, "baz"));
}
public UserService2Impl() {
}
@Override
public Collection<User2> getUsers() {
return users.values();
}
@Override
public User2 getUser(Integer id) {
return users.get(id);
}
@Override
public Response add(User2 user) {
user.setId(users.size()+1);
users.put(user.getId(), user);
//do more stuff to add user to the system..
return Response.status(Response.Status.OK).build();
}
}
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
@Qualifier("userDao")
public IUserDao userDao;
public List<User> getCurrentStatuses() {
return userDao.getAllUsers();
}
public void create(User user) {
userDao.saveOrUpdate(user);
}
public List<User> getAllUsers() {
List<User> users = userDao.getAllUsers();
if (Objects.isNull(users)) {
return null;
}
return users;
}
}
Et, il y avait le error
- J'ai annoté 2 classes avec le même nom de bean "userService"
. J'ai dû le changer comme ça pour donner au bean un nom différent,
@Service("user2Service")
public class UserService2Impl implements IUserService2 {
// some code
}
Et, cette erreur disparaît. En bref, l'erreur ConflictingBeanDefinitionException
due aux 2 beans du même nom et je devais juste fournir un nom différent.
C. J'avais encore des choses à régler. Ensuite, lorsque j'exécute le programme, j'obtiens
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'mobi.puut.database.IUserDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=userDao)}
// some consequent error messages not necessay to solve the issue
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'mobi.puut.database.IUserDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=userDao)}
L'erreur ici est UnsatisfiedDependencyException
et indique qu'il ne peut pas créer un bean avec le nom de userService
. OK, c'est le code que j'avais et intégré au projet. La dépendance non satisfaite exprimée à travers le champ à savoir userDao
. userDao
est l'instance de l'interface de IUserDao
et c'était @autowired
comme ça,
@Autowired
public IUserDao userDao;
Voici plus d'informations sur le code,
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
public IUserDao userDao;
public List<User> getCurrentStatuses() {
return userDao.getAllUsers();
}
public void create(User user) {
userDao.saveOrUpdate(user);
}
public List<User> getAllUsers() {
List<User> users = userDao.getAllUsers();
if (Objects.isNull(users)) {
return null;
}
return users;
}
}
J'ai une interface dans le répertoire database
et l'implémentation conséquente de l'interface pour l'utilisateur. Le nom de l'interface est IUserDao
et ressemble
soemthing like this,
public interface IUserDao {
boolean create(User user);
void saveOrUpdate(User user);
boolean create(List<User> users);
List<User> getAllUsers();
User getById(int id);
}
Et, la mise en œuvre,
@Repository("userDao")
public class UserDaoImpl implements IUserDao {
@Autowired
private SessionFactory sessionFactory;
// the HQL queries
}
La partie conséquente du message d'erreur était NoSuchBeanDefinitionException
et l'application ne trouve pas de bean qualifiant du type (classe) de IUserDao
. J'ai toutes les requêtes HQL
dans l'implémentation de IUserDao
et le code fonctionne parfaitement avant.
Je dois prendre un moment pour réfléchir et enfin, j'ai une intuition qui peut être que la couche database
est NOT
intégrée à l'application. Voici le configuration
que j'ai utilisé,
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
Set<String> mappingConflicts = appServlet.addMapping(AppConfig.API_BASE);
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
// register all the config classes here
appContext.register(AppConfig.class);
return appContext;
}
}
Évidemment, le code no database est intégré dans le WebInitializer
. J'ai écrit une nouvelle classe qui fournit toutes les informations du database connection
et le hibernate integration
et ressemble,
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = {"mobi.puut.database"})
public class DatabaseConfig {
@Bean
public LocalSessionFactoryBean sessionFactory() {
// mobi.puut.entities
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[]{"mobi.puut.entities"});
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean
@Autowired
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager
= new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
// dataSource.setUrl("jdbc:mysql://localhost:3306/wallet?createDatabaseIfNotExist=true");
dataSource.setUrl("jdbc:mysql://localhost:3306/Wallet");
dataSource.setUsername("testuser");
dataSource.setPassword("testpassword");
return dataSource;
}
Properties hibernateProperties() {
Properties properties = new Properties();
// properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
}
Et puis enfin intégrer dans le WebInitializer
. Ceci est le code que j'ai utilisé pour registered
la configuration plus tôt dans le WebInitializer
,
appContext.register(AppConfig.class);
Mise à jour de la ligne avec le,
appContext.register(AppConfig.class, DatabaseConfig.class);
Enfin, tout fonctionne bien. Ainsi, le répertoire config
ressemble,
SUMMERY
J'ai dû résoudre ce problème grâce aux 3 erreurs,
i. ConflictingBeanDefinitionException
ii. UnsatisfiedDependencyException
iii. NoSuchBeanDefinitionException
ConflictingBeanDefinitionException
-> 2 beans portant le même nom
UnsatisfiedDependencyException
-> Ayez un bean (= "userDao")
dans la classe qui n'était pas correcte à utiliser
NoSuchBeanDefinitionException
-> le code était correct mais il fallait ajouter la couche de base de données dans le congig pour que le Spring IoC
trouve le bean.
J'espère sincèrement que cela aidera certaines personnes.