Currently I'm having a spring boot application and for each class like SampleClass that I would like to have a log I need to initialize the logger like this:
private static Logger log = LoggerFactory.getLogger(SampleClass.class);
Which means that we need statically send the classname to the getLogger method.
I was thinking of creating a loggable interface and everytime a class implements this interface, it dynamically finds the class name and properly write the log to the outputstream with proper classname.
I googled to find a proper solution but every example is specifically sending the classname in the compile time. Does spring have this ability?
I recommend to use Lombok #log varient in your project, it will automatically set at compile time
If you are using slf4j use #Slf4j
It's best approach and even spring uses internally in some projects
Create a new #Log annotation.
#Documented
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Log {}
Now implement the BeanPostProcessor which gives us the postProcessBeforeInitialization method which allows us to manage a bean before initialization. We search for the #Log annotation and inject an implementation with the LoggerFactory.
#Component
public class LoggerPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
Logger log = LoggerFactory.getLogger(bean.getClass());
ReflectionUtils.makeAccessible(field);
field.set(bean, log);
}
}, new ReflectionUtils.FieldFilter() {
#Override
public boolean matches(Field field) {
return field.isAnnotationPresent(Log.class);
}
});
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
And use the #Log annotation at field level to describe that we want to inject a logger.
#Log
private Logger logger;
Related
I have a interface which cntaining two implementations.
public interface IEncryptDecryptService {
String encrypt(String text);
String decrypt(String text);
}
one class is
#Service("SunJks")
public class GeneralEncryptDecryptServiceImpl implements
IEncryptDecryptService {
public String encrypt{
}
public String decrypt{
}
}
another class is
#Service("SafenetHsm")
public class SafenetHsmEncryptDecryptServiceImpl implements
IEncryptDecryptService {
public String encrypt{
}
public String decrypt{
}
}
I want to inject one of two classes in another class.
#Component
public class LogService implements ILogService {
#Resource(name = "${vault.encryptdecrypt.provider}")
private IEncryptDecryptService edservice;
public display{
edservice.encrypt("***");
}
This is the class where i need two inject the one of the two beans.
In application.properties i have configured that
#Provider Configurer
vault.encryptdecrypt.provider=SunJks
then "GeneralEncryptDecryptServiceImpl"is injected.
#Provider Configurer
vault.encryptdecrypt.provider=SafenetHsm
then SafenetHsmEncryptDecryptServiceImpl is injected into the "LogService" class.
it works fine.
and if i implement same thing in Custom JsonSerializer class it is not working,bean is not injected.
#Component
public class MaskSerializer extends JsonSerializer<Xclass> {
#Resource(name = "${vault.encryptdecrypt.provider}")
private IEncryptDecryptService edservice;
#Override
public void serialize(Xclass value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
String str = value.getPersistenceValue();
String strr = edservice.encrypt(str);
gen.writeStartObject();
gen.writeFieldName(strr);
gen.writeEndObject();
}
i am getting nullpointer exception at edservice.encrypt(str) in above class.
Bean is not injected????
From your code I'm assuming you are using Spring. I can think of two reason why field edservice may be null null:
Property vault.encryptdecrypt.provider is not found in its application context.
Bean(s) with given name(s) are not found in its application context.
You can add
#Value("vault.encryptdecrypt.provider") private String provider;
to class MaskSerializer to determine if the property is found - if it is then you'll know that the bean and not the property is missing from the application context. Then you simply need to define that particular bean in that particular context. Otherwise you 'll need to define an appropriate PropertySource to read your property file.
The above is of course assuming that MaskSerializer and LogService beans are in different application contexts. Otherwise the only thing I can think of is that you're doing this:
MaskSerializer maskSerializer = new MaskSerializer();
instead of this (or equivalent with #Resource or #Inject):
#Autowired MaskSerializer maskSerializer;
somewhere in your code.
I'm trying out a simple custom Spring annotation, but it seems like Spring isn't executing anything when i slap the annotation on a method...anyone have any ideas? I see no logging at all. Maybe i need some aop dependency?
#Aspect
#Component
public class LethargicLoggerAspect {
private final Logger log = LoggerFactory.getLogger(getClass());
#Around("#annotation(LethargicLogger)")
public Object logSlowExecutionTime(ProceedingJoinPoint
proceedingJoinPoint) throws
Throwable {
log.error("HIIIIIIIIII david");
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface LethargicLogger {
}
It looks good, you need to add package to the #Around annotation.
#Around("#annotation(com.example.package.LethargicLogger)")
public Object logSlowExecutionTime(ProceedingJoinPoint
proceedingJoinPoint) throws
Throwable {
}
I'm trying to make a custom validation (checking if an email is already present in the database). For single class my annotation is working fine but I need to make this validation work for two objects implementing common interface. I have User interface and Visitor and Exhibitor classes which are implementing it.
Here is my annotation:
#Documented
#Constraint(validatedBy = UniqueEmailValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail {
String message() default "Email is already existing!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Here is Validator class
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
#Autowired
private ApplicationContext applicationContext;
private UserService userService;
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
#Override
public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) {
return !userService.isEmailPresent(email);
}
}
UserService is a common interface of VisitorService and ExhibitorService
public interface UserService {
boolean isEmailPresent(String email);
}
And it's implementation...
#Service
public class VisitorService implements UserService {
#Autowired
VisitorDao visitorDao;
#Override
public boolean isEmailPresent(String email) {
try {
return !visitorDao.findAllByEmail(email).isEmpty();
} catch (NullPointerException e) {
return false;
}
}
}
Currently I'm getting NullPointerException
java.lang.NullPointerException: null
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:24) ~[classes/:na]
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:10)
Any ideas what's the reason? I was trying to debug and UserService is null... but I'm trying to add a new visitor so it should use VisitorService. Please help.
I managed to solve my problem by specifing exact implementation in annotation's parameter
#UniqueEmail(service = VisitorService.class)
and then creating it's instance in
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
However I had to separate all Spring-based validation from Hibernate's entity to make it work.
If you don't want to have two different annotations with different #Qualifier() marked beans, you can consider to choose that bean in runtime using application context. But injecting whole Spring context to your business logic is considered as a very bad practice:
1) Mixing infrastructure (your WHOLE infrastructure) with business processes makes your code hard to understand and decouple.
2) It is hard to unit test this, need to mock the context object instead of your services.
But after using Google Guice DI I found myself in using Provider<Service> pattern, because injecting spring into spring is OK. So you can create a class like:
#Service
class UserServiceProvider<T extends UserService> implements Provider<T> {
#Autowired private ApplicationContext context;
public UserService get(Class<T> exactServiceType) {
return (UserService) context.getBean(exactServiceType);
}
}
Maybe there is a much better "Spring way" to do this, but this code is easy to understand and maintain. Works lile a Scope.Prototype bean, but a bit more flexible.
Google Guice Provider tutorial
EDIT:
Spring has a similar interface FactoryBean<T> and it has an enhanced handling, because DI will inject not the factory, but what factory provides.
But the one problem is you can't do it with some condition.
Simple example
How can we log each bean instantiation in Spring?
I wish to log a message every time a bean is initialized.
I have N number of bean and I don't want to put any logger on init method for each bean.
I see this as a cross-cutting concern, but not sure how to achieve this.
Is there a way.?
You can use a BeanPostProcessor
#Component
public class LogBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
LOGGER.log(String.format("Bean instantiated with name %s and class %s", beanName, bean.getClass().getSimpleName()));
return bean;
}
}
Try setting logging level of org.springframework.beans.factory to TRACE or DEBUG.
I use log4j2 with xml configuration:
<logger name="org.springframework.beans.factory" level="trace"/>
You can use Spring's event listener (explained here) to listen for event. I believe the event you need to listen to is ContextRefreshedEvent, e.g.:
#Component
public class MyListener
implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
...
}
}
I am using spring with aspect-j annotation support to allow for an #Loggable annotation. This allows automatic logging on a class based on the configuration.
I am wondering if I can somehow use this annotation to expose an slf4j Logger variable into the class for direct use, so that I don't have to do something to the effect of:
Logger logger = LoggerFactory.getLogger(MyClass.class);
It would be nice if the above was implicitly available due to the annotation and I could just go about doing logger.debug("..."); without the declaration. I'm not sure if this is even possible.
You can use the BeanPostProcessor interface, which is called by the ApplicationContext for all created beans, so you have the chance to fill the appropriate properties.
I created a simple implementation, which does that:
import java.lang.reflect.Field;
import java.util.List;
import net.vidageek.mirror.dsl.Mirror;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
#Component
public class LoggerPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
List<Field> fields = new Mirror().on(bean.getClass()).reflectAll().fields();
for (Field field : fields) {
if (Logger.class.isAssignableFrom(field.getType()) && new Mirror().on(field).reflect().annotation(InjectLogger.class) != null) {
new Mirror().on(bean).set().field(field).withValue(LoggerFactory.getLogger(bean.getClass()));
}
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
You don't have to do any complex registration step, since the ApplicationContext is capable of recognizing BeanPostProcessor instances and automatically register them.
The #InjectLogger annotation is:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectLogger {
}
And then you can easily use the annotation:
public static #InjectLogger Logger LOGGER;
...
LOGGER.info("Testing message");
I used the Mirror library to find the annotated fields, but obviously you may perform a manual lookup in order to avoid this additional dependency.
It's actually a nice idea to avoid repeated code, and even small issues that come from copying and paste the Logger definitions from other classes, like when we forget to change the class parameter, which leads to wrong logs.
You can't do it with an aspect, but lombok can help you in a, in my opinion, elegant way. See #Log annotation.
I think the solution from #Redder is a great way of doing this. However, I didn't want to include the Mirror library so I wrote an implementation of LoggerPostProcessor that uses the Java reflect library instead. Here it is:
package com.example.spring.postProcessor;
import com.example.annotation.InjectLogger;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
#Component
public class LoggerPostProcessor implements BeanPostProcessor {
private static Logger logger = LoggerFactory.getLogger(LoggerPostProcessor.class);
/* (non-Javadoc)
* #see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
List<Field> fields = Arrays.asList(bean.getClass().getDeclaredFields());
for (Field field : fields) {
if (Logger.class.isAssignableFrom(field.getType()) && field.getAnnotation(InjectLogger.class) != null) {
logger.debug("Attempting to inject a SLF4J logger on bean: " + bean.getClass());
if (field != null && (field.getModifiers() & Modifier.STATIC) == 0) {
field.setAccessible(true);
try {
field.set(bean, LoggerFactory.getLogger(bean.getClass()));
logger.debug("Successfully injected a SLF4J logger on bean: " + bean.getClass());
} catch (IllegalArgumentException e) {
logger.warn("Could not inject logger for class: " + bean.getClass(), e);
} catch (IllegalAccessException e) {
logger.warn("Could not inject logger for class: " + bean.getClass(), e);
}
}
}
}
return bean;
}
/* (non-Javadoc)
* #see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
I want to make some improvements to #Redder's solution.
First - we can omit introduction of new annotation #Log, instead we
can use Spring's #Autowired annotation with 'required' flag set to
'false' to make Spring not to check that bean was injected or not
(because, we will inject it later).
Second - use Spring's ReflectionUtils API that provides all
needed methods for field discovering and manipulation, so we don't need additional external dependencies.
Here an example (in Java 8, but can be rewritten in Java 7/6/etc., also slf4j facade is used but it can be replaced with any other logger):
#Component
public class LoggerPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Logger logger = getLogger(bean.getClass());
doWithFields(bean.getClass(), field -> {
makeAccessible(field);
setField(field, bean, logger);
}, field -> field.isAnnotationPresent(Autowired.class) && Logger.class.equals(field.getType()));
return bean;
}
...
}
...
//logger injection candidate
#Autowired(required = false)
private Logger log;
Since I got this as the first result when trying to do the same thing in CDI (JSR 299: Context and Dependency Injection), this link shows the straightforward way to do this using CDI (and also an alternative using Spring):
Basically, you only need to inject:
class MyClass {
#Inject private Log log;
And have a logger factory like so:
#Singleton
public class LoggerFactory implements Serializable {
private static final long serialVersionUID = 1L;
static final Log log = LogFactory.getLog(LoggerFactory.class);
#Produces Log createLogger(InjectionPoint injectionPoint) {
String name = injectionPoint.getMember().getDeclaringClass().getName();
log.debug("creating Log instance for injecting into " + name);
return LogFactory.getLog(name);
}
}
I found that I needed to add transient to the injected log so that I did not get a passivating scope exception in my session scoped beans:
#Named()
#SessionScoped()
public class MyBean implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private transient Log log;
Herald provides a very simple BeanPostProcessor which does all the magic for you. You can annotate any field of Spring bean with a #Log annotation to let Herald inject suitable logger in this field.
Supported logging frameworks:
JavaTM 2 platform's core logging framework
Apache Commons Logging
Simple Logging Facade for Java (SLF4J)
SLF4J Extended logger
Logback
Apache Log4j
Apache Log4j 2
JBoss Logging
Syslog4j
Syslog4j fork from Graylog
Fluent Logger for Java
It is also possible to add other logging frameworks.
Github repo: https://github.com/vbauer/herald