最近看到了很多关于@Autowired不一样的用法代码,现在将收获到的知识分享给大家。
@Autowired比你想象中更强大,它主要作用范围包含构造器、方法、参数、成员变量、注解。
熟悉Spring的都知道@Autowired注解,是用来自动装配对象的。
示例如下:
package com.sue.cache.service;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
public void product() {//无参构造
}
}
package com.sue.cache.service;
import org.springframework.stereotype.Service;
@Service
public class ProductService2 {
@Autowired//注入对象
private ProductService productService;
public void product2() {//午餐构造
}
}
正常情况下是可以装配成功的,都知道spring默认按照类型注入的(即byType方式)。
看过底层源码的应该知道,@Autowired注解有一个required参数,其值默认为true(默认开启自动装配),如果不想使用自动装配可将该参数设置成false。
可以设想一下,如果多个相同类型的对象,再用@Autowired来注入这同类型的对象会发生什么?
项目新建一个目录,创建一个同名的类ProductService。示例如下:
package com.sue.cache.service.test;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
public void product() {
}
}
再启动项目时,抛出如下的异常:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'testService1' for bean class [com.sue.cache.service.test.TestService1] conflicts with existing, non-compatible bean definition of same name and class [com.sue.cache.service.ProductService]
这个异常时告诉我们类名称有冲突,然后项目嗝屁了,起不来了。
注意:
其实这并不是因为@Autowired注入了两个同类型的对象导致的。其实是spring中@Service注解不允许有同名的类,spring自动把类首字母大写转为小写作为它管理的bean名称,默认情况下bean名称必须是唯一的。
如何产生两个相同的类型bean?:
public class ProductService1 {
public void product1() {
}
}
@Service
public class ProductService2 {
@Autowired
private ProductService1 productService1;
public void product2() {
}
}
@Configuration
public class ProductConfig {
@Bean("productService1")
public ProductService1 productService1() {
return new ProductService1();
}
@Bean("productService2")
public ProductService1 productService2() {
return new ProductService1();
}
}
在ProductConfig类中手动创建ProductService1实例。
重启项目抛出异常:
抛出异常,提示productService1是单例的,却找到两个实例。
两个类实现同一个接口后,另一个类@Autowired这个接口,也会产生两个相同的类型bean异常,实例如下:
public interface IUser {
void say();
}
@Service
public class User1 implements IUser{
@Override
public void say() {
}
}
@Service
public class User2 implements IUser{
@Override
public void say() {
}
}
@Service
public class UserService {
@Autowired
private IUser user;
}
项目重启抛出异常:
和上面一样的配出同样的异常信息。
不难看出,实际项目中第二种情况在我们实际项目中遇到的更多!
当然在项目中用@Autowired装配对象时,是解决不了上面的问题。这是就是spring的强大之处了,spring还给我们提供了改用按名称装配实例。
此时用@Autowired + @Qualifier("beanName“)可以完美解决上述问题:
@Service
public class UserService {
@Autowired
@Qualifier("user1")
private IUser user;
}
调整之后,再重启项目就ok了。
Qualifier一般都是和Autowired结合使用,通过Qualifier的参数指定一个bean的名称,实现多个同对象的装配。
Spring还给我们提供了@Primary注解解决上面的问题。在一个对象上加上@Primary注解:
@Primary
@Service
public class User1 implements IUser{
@Override
public void say() {
}
}
此时另一个对象只使用@Autowired注解即可:
@Service
public class UserService {
@Autowired
private IUser user;
}
重启项目,一样解决了问题。
当用@Autowired自动装配对象时,有多个同名对象,其中一个使用@Primary注解修饰,就会把该对象作为候选者被选中,来作为自动配置的值。
注:其实项目中这种方式不是很常用。
下面我们聊一聊@Autowired注解除了作用在成员变量上,还有哪些作用范围?
看看源码中@Autowired注解的定义:
不难看出@Autowired作用在5种目标类型上,图例总结一下:
接下来,我们重点看看在其他地方该怎么用?
@Autowired作用在成员变量上:
@Service
public class UserService {
@Autowired
private IUser user;
}
@Autowired作用在构造器上:
@Service
public class UserService {
private IUser user;
@Autowired
public UserService(IUser user) {
this.user = user;
System.out.println("user:" + user);
}
}
注意:@Autowired作用在构造器上,实际仍然用Autowired装配方式,并非构造器装配。
@Autowired作用在普通方法上:
@Service
public class UserService {
@Autowired
public void test(IUser user) {
user.say();
}
}
项目启动中,Spring自动调用一次加了@Autowired的方法此时可以在该方法内做一些初始化工作。
@Autowired也可以作用在setter方法上:
@Service
public class UserService {
private IUser user;
@Autowired
public void setUser(IUser user) {
this.user = user;
}
}
@Autowired作用在构造器的入参上:
@Service
public class UserService {
private IUser user;
public UserService(@Autowired IUser user) {
this.user = user;
System.out.println("user:" + user);
}
}
@Autowired作用在非静态方法的入参上:
@Service
public class UserService {
public void test(@Autowired IUser user) {
user.say();
}
}
基本没人用,偷个懒不介绍了。。。
上面几种@Autowired都是自动装配单实例,其实它也能自动装配多个实例,惊喜补,怎么回事呢?
把UserService成员变量变通一下,用List集合接收IUser类型的参数:
@Service
public class UserService {
@Autowired
private List userList;
@Autowired
private Set userSet;
@Autowired
private Map userMap;
public void test() {
System.out.println("userList:" + userList);
System.out.println("userSet:" + userSet);
System.out.println("userMap:" + userMap);
}
}
创建一个UController:
@RequestMapping("/u")
@RestController
public class UController {
@Autowired
private UserService userService;
@RequestMapping("/test")
public String test() {
userService.test();
return "success";
}
}
启动项目并调用接口:
控制台中:userList、userSet和userMap都分别打印出来了各自的元素,并没有抛出异常,说明@Autowired会自动把相同类型的IUser对象收集到集合中。
有时候就算用了@Autowired装配的对象结果还是null,这又是怎么回事呢?
在类上面不加@Controller、@Service、@Component、@Repository等注解,这种情况下Spring就不能完成对象的装配,示例如下:
public class UserService {
@Autowired
private IUser user;
public void test() {
user.say();
}
}
web项目启动的顺序:listener>filter>servlet。
示例如下:
public class UserFilter implements Filter {
@Autowired
private IUser user;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
user.say();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new UserFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
此时我们启动项目一样会抛出异常,并告诉我们tomcat不能正常启动:
这又是什么原因呢?
Springmvc的启动主要依靠DisptachServlet(常说的核心处理器),而核心处理器是在listener和filter之后执行的。那么此时我们在监听器或者过滤器里面用@Autowired装配某个对象时,因此此时核心处理器还没执行,bean还没有初始化,自然无法完成自动注入的。
下面我们讲一讲项目中遇到这种情况该怎么处理呢?
public class UserFilter implements Filter {
private IUser user;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
this.user = ((IUser)(applicationContext.getBean("user1")));
user.say();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
这里用到了WebApplicationContextUtils.getWebApplicationContext获取当前的ApplicationContext,再通过它获取到bean实例(其实这里实际用到的是Java里面的反射来完成的,小伙伴们可以了解一下)。
Spring中@Component、@Repository、@Controller、@Service、@Configuration等注解,是需要通过@ComponentScan注解扫描,收集元数据的。
试想一下,不加@ComponentScan,或配置的@ComponentScan扫描的包路径错误,或包路径范围太小,此时也会导致有些注解的元数据无法收集,最后导致@Autowired不能自动注入bean。
当然Springboot项目中就不存在此类问题,Springboot的@SpringBootApplication注解,已经内置了@ComponentScan注解。
循环依赖,相信伙伴们都遇到过吧,这里简单说一下,如果A依赖于B,B依赖于C,C又依赖于A,就形成了死循环依赖。
Spring的bean默认是单例的,单例对象用@Autowired自动注入,大多数情况能解决循环依赖问题。
那么当bean是多例的情况下,就会出现循环依赖,导致bean无法自动注入。
有时候@Autowiredw无法解决,改成@Resource却能解决问题。接下来,我们重点看看@Autowired和@Resource的区别。
@Autowired的装配顺序如下:
@Resource的装配顺序如下:
1.如果同时指定了name和type
2.如果指定了name
3.如果指定了type
4.如果既没有指定name,也没有指定type:
关注公众号:编程怪咖,学习更多知识。
页面更新:2024-04-23
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号