Spring IoC&DI补充

IoC详解

        IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象.也就是bean的存储.

        Bean的存储

        共有两类注解类型可以实现Bean的存储:

        1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.

        2. 方法注解:@Bean.

        @Controller(控制器存储)

        使用@Controller存储Bean的代码如下:

@Controller
public class UserController {
    public void sayHi() {
        System.out.println("hi,UserController");
    }
}

        在启动类中,从Spring IoC容器中获取对象

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserController bean = context.getBean(UserController.class);
        bean.sayHi();
    }

        Spring容器中已包含这个Bean.

        @Service(服务存储)

        使用@Service存储Bean的代码如下所示:

@Service
public class UserService {
    public void sayHi(){
        System.out.println("hi,UserService");
    }
}
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserService bean = context.getBean(UserService.class);
        bean.sayHi();
    }

        

        @Repository(仓库存储)
 
@Repository
public class UserRepository {
    public void sayHi() {
        System.out.println("Hi, UserRepository~");
    }
}
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserRepository bean = context.getBean(UserRepository.class);
        bean.sayHi();
    }

        @Component(组件存储)
@Component
public class UserComponent {
    public void sayHi() {
        System.out.println("Hi, UserComponent");
    }
}
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserComponent bean = context.getBean(UserComponent.class);
        bean.sayHi();
    }

        @Configuration(配置存储)
@Configuration
public class UserConfiguration {
    public void sayHi() {
        System.out.println("Hi,UserConfiguration");
    }
}    
public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserConfiguration bean = context.getBean(UserConfiguration.class);
        bean.sayHi();
    }

        为什么要这么多注解?

        从上面可以观察到,这些注解的用法看起来大差不差.但是这些注解与应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.

        @Controller 控制层,接受请求,对请求进行处理,并进行相应.

        @Service 业务逻辑层,处理具体的业务逻辑.

        @Repository 数据访问层,也称为持久层,负责数据访问操作.

        @Configuration 配置层,处理项⽬中的⼀些配置信息.

        查看 @Controller/@Service/@Repository/@Configuration等注解的源码发现:他们的注解都包含@Component,也就是这些注解都是@Component的"子类".@Component的功能,这些注解都包含.

        @Controller,@Service和@Repository⽤于更具体的⽤例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使⽤@Componen或@Service,显然@Service是更好的选择.

        方法注解@Bean

        类注解是注解在某个类上的,但是这样做有两个问题:

        1.在使用外部包的类时,无法使用类注解对外部包的类进行注解.

        2.一个类需要多个对象时,类注解只会产生一个对象.

        在上述的两个场合,就需要使用方法注解@Bean.

        观察@Bean如何使用:

public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("zhangsan");
        return userInfo;
    }
}
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.springboot.ioc.model.UserInfo' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1148)
	at com.springboot.ioc.IoCApplication.main(IoCApplication.java:18)

        直接使用,并尝试获取对象时报错,NoSuchBeanDefinitionException根据后续信息判断出,这里@Bean注解没有成功添加Bean到容器中.

        方法注解要配合类注解使用

        在Spring框架的设计中,⽅法注解 @Bean要配合类注解才能将对象正常的存储到Spring容器中,如下代码所⽰:

@Component
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("zhangsan");
        return userInfo;
    }
}

        成功获取到对象.

        定义多个对象
@Component
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("zhangsan");
        return userInfo;
    }
    @Bean
    public UserInfo user(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("lisi");
        return userInfo;
    }
    @Bean
    public UserInfo userInfo1(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("wangwu");
        return userInfo;
    }
}
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.springboot.ioc.model.UserInfo' available: expected single matching bean but found 3: userInfo,user,userInfo1
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1287)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:483)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:338)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1148)
	at com.springboot.ioc.IoCApplication.main(IoCApplication.java:18)

        此时获取对象又报错了,NoUniqueBeanDefinitionException,根据报错信息判断出,Spring容器中包含多个Bean,但是Spring无法判断选择哪一个.此时我们只需要指定获取哪个Bean即可.

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        //指定获取名为user的对象
        UserInfo bean = context.getBean("user",UserInfo.class);
        System.out.println(bean);
    }

        在指定获取之后,程序成功允许并获得了正确的结果.

        重命名Bean

        可以通过name属性给Bean对象进行重命名操作.

    //将user重命名为UUUUUUU
    @Bean(name = {"UUUUUUU","user"})
    public UserInfo user(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("lisi");
        return userInfo;
    }
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserInfo bean = context.getBean("UUUUUUU",UserInfo.class);
        System.out.println(bean);
    }

        重命名并使用新的名称指定获取成功.

        扫描路径

        项目中的代码并不是所有代码都能被Spring扫描到的,程序被Spring扫描并管理需要满足以下条件:

        1.被Spring扫描到,Spring默认扫描路径为启动类所在的目录包含子目录.可以通过@ComponentScan(basePackage="")进行修改.

        2.类需要配合五大注解使用.

DI详解

        依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象.在之前程序案例中,使⽤了@Autowired 这个注解,完成了依赖注⼊的操作.

        依赖注入,Spring也提供了三种方案:

        1.属性注入

        2.构造方法注入

        3.Setter注入

        属性注入

        属性注⼊是使用@Autowired实现的,将Service类注⼊到Controller类中:

@Service
public class UserService {
    public void sayHi(){
        System.out.println("hi,UserService");
    }
}
@Controller
public class UserController {
    //属性注入
    @Autowired
    private UserService userService;

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}

        注入成功,代码正确执行.

        构造方法注入

        依旧将Service类注⼊到Controller类中:

@Controller
public class UserController {
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}

        直接写出包含Service的构造方法,即可正确注入.

        但是如果类中包含多个构造方法,那么就会出现错误.

@Controller
public class UserController {
    private UserService userService;

    public UserController() {
    }

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.springboot.ioc.service.UserService.sayHi()" because "this.userService" is null
	at com.springboot.ioc.controller.UserController.sayHi(UserController.java:20)
	at com.springboot.ioc.IoCApplication.main(IoCApplication.java:19)

        抛出了空指针异常,说明Spring直接使用了无参数的构造方法,如果想让Spring使用我们提供的包含Serveice的构造方法,需要标上@Autowired注解.

@Controller
public class UserController {
    private UserService userService;

    public UserController() {
    }

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}

        包含多个构造方法时:

@Controller
public class UserController {
    private UserService userService;
    private UserConfiguration userConfiguration;

//    public UserController() {
//    }


    public UserController(UserService userService) {
        this.userService = userService;
    }

    public UserController(UserService userService, UserConfiguration userConfiguration) {
        this.userService = userService;
        this.userConfiguration = userConfiguration;
    }

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [E:\Java\IoC\target\classes\com\springboot\ioc\controller\UserController.class]: Failed to instantiate [com.springboot.ioc.controller.UserController]: No default constructor found
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1306) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1198) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[spring-context-6.0.4.jar:6.0.4]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.4.jar:6.0.4]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.2.jar:3.0.2]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-3.0.2.jar:3.0.2]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[spring-boot-3.0.2.jar:3.0.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-3.0.2.jar:3.0.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[spring-boot-3.0.2.jar:3.0.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[spring-boot-3.0.2.jar:3.0.2]
	at com.springboot.ioc.IoCApplication.main(IoCApplication.java:17) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.springboot.ioc.controller.UserController]: No default constructor found
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1300) ~[spring-beans-6.0.4.jar:6.0.4]
	... 17 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.springboot.ioc.controller.UserController.<init>()
	at java.base/java.lang.Class.getConstructor0(Class.java:3585) ~[na:na]
	at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:79) ~[spring-beans-6.0.4.jar:6.0.4]
	... 18 common frames omitted

        分析报错信息,得出是因为没有默认的构造函数导致的.此时给我们需要的构造函数加上@Autowired注解即可.

        Setter注入

        Setter注⼊和属性的Setter⽅法实现类似,只不过在设置set⽅法的时候需要加上@Autowired注解,如下代码所⽰:

@Controller
public class UserController {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("hi,UserController");
        userService.sayHi();
    }
}

        三种注入的优缺点分析

        属性注⼊
        优点:简洁,使⽤⽅便
        缺点:
        1.只能⽤于IoC容器,如果是⾮IoC容器不可⽤,并且只有在使⽤的时候才会出现NPE(空指针异常)
        2.不能注⼊⼀个Final修饰的属性

        构造函数注⼊(Spring4.X推荐)
        优点:
        1.可以注⼊final修饰的属性
        2.注⼊的对象不会被修改
        3.依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
        4.通⽤性好,构造⽅法是JDK⽀持的,所以更换任何框架,他都是适⽤的.
         缺点:注⼊多个对象时,代码会⽐较繁琐

        Setter注⼊(Spring3.X推荐)
        优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊
        缺点:
        1.不能注⼊⼀个Final修饰的属性.
        2.注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险.

        @Autowired存在问题

        Autowired会先尝试获取与所修饰的变量同名的Bean,当不存在同名的Bean时,尝试获取同类型的Bean.当同一类型存在多个Bean时,使用@Autowired就会存在问题:

@Controller
public class UserController {
    @Autowired
    private UserInfo userInfo2;

    public void sayHi() {
        System.out.println("hi,UserController");
        System.out.println(userInfo2);
    }
}

        报错的原因是存在非唯一的bean对象.

        解决方案:

        使用@Primary

        使用@Primary来标识默认的Bean对象.

    @Primary
    @Bean
    public UserInfo userInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setName("zhangsan");
        return userInfo;
    }

        使用@Qualifier

        使用@Qualifier注解:指定当前要注⼊的bean对象.在@Qualifier的value属性中,指定注⼊的bean的名称.

public class UserController {
    //指定获取变量名为user的Bean
    @Qualifier("user")
    @Autowired
    private UserInfo userInfo2;

    public void sayHi() {
        System.out.println("hi,UserController");
        System.out.println(userInfo2);
    }
}

        使⽤@Resource

        使⽤@Resource注解:是按照bean的名称进⾏注⼊.通过name属性指定要注⼊的bean的名称.

public class UserController {
    //获取userInfo1
    @Resource(name = "userInfo1")
    private UserInfo userInfo2;

    public void sayHi() {
        System.out.println("hi,UserController");
        System.out.println(userInfo2);
    }
}

        @Autowired与@Resource的区别:

        @Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解.

        @Autowired默认是按照类型注⼊,而@Resource是按照名称注⼊.相比于@Autowired来说,@Resource⽀持更多的参数设置,例如name设置,根据名称获取Bean.
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值