Reputation: 3336
I understand that a managed bean works like a controller, because your only task is "link" the View Layer with Model.
To use a bean as a managed bean I must declare @ManagedBean
annotation, doing that I can communicate JSF with bean directly.
If I want to inject some component (from Spring) in this managedBean I have two possibles ways:
Choose the property in ManagedBean (like "BasicDAO dao") and declare @ManagedProperty(#{"basicDAO"})
above the property. Doing it, i'm injecting the bean "basicDAO"
from Spring in ManagedBean.
Declared @Controller in ManagedBean Class, then i'll have @ManagedBean
and @Controller
annotations, all together. And in property "BasicDAO dao"
i must use @Autowired
from Spring.
Is my understanding correct?
Upvotes: 52
Views: 42115
Reputation: 1108722
@Named
/@ManagedBean
vs @Controller
/@Component
First of all, you should choose one framework to manage your beans. You should choose either CDI/JSF or Spring to manage your beans. Whilst the following example works, it is fundamentally wrong:
@Named // CDI-managed (same applies to @ManagedBean)
@Controller // Spring-managed (same applies to @Component)
public class BadBean {}
You end up with two completely separate instances of the very same managed bean class, one managed by CDI (or JSF) and another one managed by Spring. It's not directly clear which one would actually be used in EL when you reference it as #{someBean}
. If you have the SpringBeanFacesELResolver
registered in faces-config.xml
, then it would be the Spring-managed one, not the CDI/JSF-managed one. If you don't have that, then it would be the CDI/JSF-managed one.
Also, when you declare a JSF managed bean specific scope, such as @ViewScoped
or @FlowScoped
from jakarta.faces.*
package, then it will only be recognized and used by @Named
/@ManagedBean
. It won't be understood by @Controller
/@Component
as it expects its own @Scope
annotation. The Spring scope defaults to singleton (application scope) when absent.
@Named // CDI-managed.
@ViewScoped // JSF-specific scope.
@Controller // Spring-managed (without own scope, so actually becomes a singleton).
public class BadBean {}
When you reference the above bean via #{someBean}
, it would return the Spring-managed application scoped bean, not the CDI/JSF-managed view scoped bean.
@ManagedProperty
vs @Autowired
The JSF-specific @jakarta.faces.annotation.ManagedProperty
works only in CDI/JSF-managed beans, i.e. when you're using @Named
/@ManagedBean
. The Spring-specific @Autowired
works only in Spring-managed beans, i.e. when you're using @Controller
/@Component
. Below approaches are less or more equivalent and cannot be mixed:
@Named // CDI-managed.
@RequestScoped // CDI-managed scope.
public class GoodBean {
@Inject @ManagedProperty("#{springBeanName}")
private SpringBeanClass springBeanName;
}
@Component // Spring-managed.
@Scope("request") // Spring-managed scope.
public class GoodBean {
@Autowired
private SpringBeanClass springBeanName;
}
Do note that when you have the SpringBeanFacesELResolver
registered in faces-config.xml
as per the javadoc,
<application>
...
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
and thus you can reference Spring managed beans in EL via #{springBeanName}
, then you can just reference them in @ManagedProperty
too, as it basically sets the evaluated result of the given EL expression. In order to get @Inject
to work with Faces implicit objects you only need a @ApplicationScoped @FacesConfig
annotated class in the project in order to auto-activate it.
@FacesConfig
@ApplicationScoped
public class FacesConfiguration {}
The other way round, injecting a CDI/JSF-managed bean via @Autowired
, is in no way supported. You can however use @Autowired
in a CDI/JSF managed bean when you extend your bean from SpringBeanAutowiringSupport
. This will automatically register the CDI/JSF-managed bean instance in Spring autowirable context during constructor invocation, which means that everything @Autowired
will be available in @PostConstruct
and later.
@Named // CDI-managed.
@ViewScoped // JSF-specific scope.
public class GoodBean extends SpringBeanAutowiringSupport implements Serializable {
@Autowired
private SpringBeanClass springBeanName;
@PostConstruct
private void init() {
// springBeanName is now available.
}
}
Or when your architecture doesn't allow extending beans from a different base class, then you can always manually register the JSF managed bean instance in Spring autowirable context as below. See also How to integrate JSF 2 and Spring 3 (or Spring 4) nicely for the trick.
@Named // CDI-managed.
@ViewScoped // JSF-specific scope.
public class GoodBean implements Serializable {
@Autowired
private SpringBeanClass springBeanName;
@PostConstruct
private void init() {
FacesContextUtils
.getRequiredWebApplicationContext(FacesContext.getCurrentInstance())
.getAutowireCapableBeanFactory().autowireBean(this);
// springBeanName is now available.
}
}
@Autowired
in @Named
Or if you don't want to do any of this manually, then consider using a CDI extension as described in this blog Using OmniFaces CDI @ViewScoped with unload/destroy in a Spring Boot project:
public class SpringAutowiredExtension implements Extension {
private Map<Class<?>, String> autowiredFields = new ConcurrentHashMap<>();
public <T> void processAnnotatedType(@Observes @WithAnnotations(Named.class) ProcessAnnotatedType<T> processAnnotatedType, BeanManager beanManager) {
Class<T> beanClass = processAnnotatedType.getAnnotatedType().getJavaClass();
if (!beanClass.getPackage().getName().startsWith(YourSpringConfiguration.BEANS_PACKAGE)) {
return; // This should filter out any CDI managed beans provided by e.g. Faces and OmniFaces themselves.
}
processAnnotatedType.configureAnnotatedType()
.filterFields(field -> field.isAnnotationPresent(Autowired.class))
.forEach(this::registerAutowiredField);
}
private void registerAutowiredField(AnnotatedFieldConfigurator<?> fieldConfigurator) {
fieldConfigurator.add(InjectLiteral.INSTANCE);
Field field = fieldConfigurator.getAnnotated().getJavaMember();
autowiredFields.put(field.getType(), field.getName());
}
public void afterBeanDiscovery(@Observes AfterBeanDiscovery event) {
autowiredFields.entrySet().forEach(autowiredField -> injectBeanViaSpringContext(event, autowiredField.getKey(), autowiredField.getValue()));
}
private static void injectBeanViaSpringContext(AfterBeanDiscovery event, Class<?> type, String name) {
event.addBean().addType(type).createWith(ignoreCdiContext -> getBeanFromSpringContext(type, name));
}
private static Object getBeanFromSpringContext(Class<?> type, String name) {
try {
try {
return Spring.getContext().getBean(type);
}
catch (NoUniqueBeanDefinitionException ignore) {
return Spring.getContext().getBean(name);
}
}
catch (Exception e) {
throw new IllegalStateException("Cannot get bean from Spring context", e);
}
}
}
This way you can transparently use @Autowired
in @Named
beans without the need to extend from some Spring-specific super class, or to manually grab the bean from Spring context via some Spring-specific helper class.
@Named // CDI-managed.
@ViewScoped // JSF-specific scope.
public class GoodBean implements Serializable {
@Autowired
private SpringBeanClass springBeanName;
@PostConstruct
private void init() {
// springBeanName is now available.
}
}
@XxxScoped
vs @Scope
Spring's @Scope
has limited support for JSF scopes. There's no equivalent for JSF's @ViewScoped
. You'd basically either homegrow your own scopes, or stick to manually registering the JSF managed bean instance in Spring autowirable context as shown above.
And, from the other side on, Spring WebFlow was taken over in JSF 2.2 via new @FlowScoped
annotation. So if you happen to be on JSF 2.2 already, then you don't necessarily need to use Spring WebFlow if you solely want the flow scope.
In case you're using Spring Boot, then you can use JoinFaces. It has already ported the JSF @ViewScoped
into the Spring scope. It only hasn't ported the OmniFaces @ViewScoped
into the Spring scope. For this, head to the aforementioned blog Using OmniFaces CDI @ViewScoped with unload/destroy in a Spring Boot project.
Upvotes: 105
Reputation: 11431
You can autowire individual beans without @Autowired
by leveraging getBean
of the current WebApplication context.
Please refer to @BalusC's answer for more details. This is just a slight modification over his example:
@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean implements Serializable {
// @Autowired // No Autowired required
private SpringBeanClass springBeanName; // No setter required.
@PostConstruct
private void init() {
WebApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
this.springBeanName = ctx.getBean(SpringBeanClass.class);
// springBeanName is now available.
}
}
Upvotes: 0
Reputation: 865
There is another way to use Spring-managed beans in JSF-managed beans by simply extending your JSF bean from SpringBeanAutowiringSupport
and Spring will handle the dependency injection.
@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean extends SpringBeanAutowiringSupport {
@Autowired
private SpringBeanClass springBeanName; // No setter required.
// springBeanName is now available.
}
Upvotes: 14
Reputation: 2974
The easy way to do this is via XML. I used @Component
in already made jsf managed bean but @Autowired
did not work because managed bean was already there in faces-config.xml. If it is mandatory to keep that managed bean definition along with its managed property in the xml file then it is suggested to add the spring bean as another managed property inside the managed bean tag. Here the spring bean is there defined in spring-config.xml(can be autowired somewhere alternately). please refer
https://stackoverflow.com/a/19904591/5620851
edited by me. I suggest to either implement it altogether through annotation @Managed and @Component or via xml for both.
Upvotes: 1