spring ioc

spring-IOC

Spring Framework主要包括几个模块:

支持IoC和AOP的容器;
支持JDBC和ORM的数据访问模块;
支持声明式事务的模块;
支持基于Servlet的MVC开发;
支持基于Reactive的Web开发;
以及集成JMS、JavaMail、JMX、缓存等其他模块

IoC容器

什么是容器?
tomcat就是容器,提供的是http等基础服务

docker也是容器, 提供的是linux等系统的底层服务

Spring也是容器,提供的是它可以管理所有轻量级的JavaBean组件,
提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等

IOC原理 Inversion of Control

就是你比如说写了一个类这里边要操作数据库 就得使用连接池把 就得new连接池把还得读取配置文件
另一个人又写了一个类也得使用连接池把
一个人写了一个公用的方法 你要用得new吧  别人要用得new把 他改了个名 你new多次就得改多少次吧

像这些可以公用的代码,是不是交给一个容器管理,比较好。
就是谁都可以写,写了之后注册到容器里了。容器来给你new。你直接引入实例就行了

这样既方便也不会出问题  

为什么叫控制反转呢
原来是我写的东西来服务每一个类。跑到每一个类里去服务他们。是线下的啊。
现在我线上了。谁弄谁在线上来买就行了。

为啥又叫AOP了呢 AOP的一个特点就是无侵入
你用了spring 你继承啥了吗 没有 你把spring删掉能不能继续写代码 当然是能就是得自己new了
AOP 就是面向切面编程。 既然是切面。我是横向的一只。跟你的主线没啥关系。你根本感觉不到我的存在。我其实就是你的一个临时数据库。
你可以从这取。也可以在你内存里取

装配Bean 和 获取bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.itranswarp.learnjava.service.UserService">
        <property name="mailService" ref="mailService" />
    </bean>

    <bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
</beans>

每个<bean ...>都有一个id标识,相当于Bean的唯一ID;
在userServiceBean中,通过<property name="..." ref="..." />注入了另一个Bean;
Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="password" />
    <property name="maximumPoolSize" value="10" />
    <property name="autoCommit" value="true" />
</bean>

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Spring容器就是ApplicationContext,它是一个接口,有很多实现类,
这里我们选择ClassPathXmlApplicationContext,表示它会自动从classpath中查找指定的XML配置文件。
UserService userService = context.getBean(UserService.class);

Spring还提供另一种IoC容器叫BeanFactory,使用方式和ApplicationContext类似:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);
BeanFactory和ApplicationContext的区别在于,BeanFactory的实现是按需创建,
即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean。
实际上,ApplicationContext接口是从BeanFactory接口继承而来的,
并且,ApplicationContext提供了一些额外的功能,包括国际化支持、事件和通知机制等。
重点
BeanFactory和ApplicationContext的区别在于,BeanFactory的实现是按需创建,
即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean。
实际上,ApplicationContext接口是从BeanFactory接口继承而来的

Annotation

由于xml的配置方式很繁琐,每写一个类就得对应写一个xml的配置

于是注解出现了来解决这个问题

@Component
public class MailService {
    ...
}
这个@Component注解就相当于定义了一个Bean,它有一个可选的名称,默认是mailService,即小写开头的类名。
@Component 就相当于在xml中写了一个配置

如何使用这个
@Component
public class UserService {
    @Autowired
    MailService mailService;

    ...
}
使用@Autowired就相当于把指定类型的Bean注入到指定的字段中。和XML配置相比,
@Autowired大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中

注意你要使用spring容器里的东西 你自己也必须在容器里啊。要不然没法访问。所以自己也得加上@Compent

最后,编写一个AppConfig类启动容器:

@Configuration
@ComponentScan
public class AppConfig {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        User user = userService.login("bob@example.com", "password");
        System.out.println(user.getName());
    }
}
除了main()方法外,AppConfig标注了@Configuration,表示它是一个配置类,因为我们创建ApplicationContext时:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
使用的实现类是AnnotationConfigApplicationContext,必须传入一个标注了@Configuration的类名。

此外,AppConfig还标注了@ComponentScan,它告诉容器,自动搜索当前类所在的包以及子包,
把所有标注为@Component的Bean自动创建出来,并根据@Autowired进行装配。

Spring的这几个知识点太常用了。我要单独出来写

bean的Scope属性

scope一种是单例。容易创建的时候,实例就创建了。容器销毁才销毁 Scope默认就是这个模式 Singleton
scope另一种是 原型(Prototype) 这个是调用就会创建实例。使用的时候需要制定scope
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
    ...
}

注入List

有些时候,我们会有一系列接口相同,不同实现类的Bean。例如,注册用户时,
我们要对email、password和name这3个变量进行验证。为了便于扩展,我们先定义验证接口:

public interface Validator {
    void validate(String email, String password, String name);
}
然后,分别使用3个Validator对用户参数进行验证:

@Component
public class EmailValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
            throw new IllegalArgumentException("invalid email: " + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!password.matches("^.{6,20}$")) {
            throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (name == null || name.isBlank() || name.length() > 20) {
            throw new IllegalArgumentException("invalid name: " + name);
        }
    }
}
最后,我们通过一个Validators作为入口进行验证:

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}
注意到Validators被注入了一个List<Validator>,
Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,
我们每新增一个Validator类型,就自动被Spring装配到Validators中了,非常方便。

因为Spring是通过扫描classpath获取到所有的Bean,而List是有序的,要指定List中Bean的顺序,可以加上@Order注解:

@Component
@Order(1)
public class EmailValidator implements Validator {
    ...
}

@Component
@Order(2)
public class PasswordValidator implements Validator {
    ...
}

@Component
@Order(3)
public class NameValidator implements Validator {
    ...
}

可选注入

@Autowired 如果没有找到就会报错
如果这么写就不会@Autowired(required = false)

创建第三方的bean

例如我们要注入ZoneId,怎么注入
@Configuration
@ComponentScan
public class AppConfig {
    // 创建一个Bean:
    @Bean
    ZoneId createZoneId() {
        return ZoneId.of("Z");
    }
}
Spring对标记为@Bean的方法只调用一次,因此返回的Bean仍然是单例

初始化和销毁 @PostConstruct @PreDestroy

有些时候,一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。
在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个init()方法进行初始化,
定义一个shutdown()方法进行清理,然后,引入JSR-250定义的Annotation:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
在Bean的初始化和清理方法上标记@PostConstruct和@PreDestroy:

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Shutdown mail service");
    }
}
Spring容器会对上述Bean做如下初始化流程

使用别名

如果注册的两个类型相同怎么办? 使用别名 或者视同@Primary 数据库的连接池就是用的@Primary

@Configuration
@ComponentScan
public class AppConfig {
    @Bean("z")  第一种配置别名的方式
    ZoneId createZoneOfZ() {
        return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")  第二种配置别名的方式
    ZoneId createZoneOfUTC8() {
        return ZoneId.of("UTC+08:00");
    }
}
可以用@Bean("name")指定别名,也可以用@Bean+@Qualifier("name")指定别名。

第二种方式@Primary
@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Primary // 指定为主要Bean
    @Qualifier("z") // 指定为@Primary之后这个就可以不写了
    ZoneId createZoneOfZ() {
        return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {
        return ZoneId.of("UTC+08:00");
    }
}
@Autowire之后默认就执照promary的这个bean,除非他指定名字

使用FactoryBean

用工厂模式创建Bean需要实现FactoryBean接口。我们观察下面的代码:

@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {

    String zone = "Z";

    @Override
    public ZoneId getObject() throws Exception {
        return ZoneId.of(zone);
    }

    @Override
    public Class<?> getObjectType() {
        return ZoneId.class;
    }
}
当一个Bean实现了FactoryBean接口后,Spring会先实例化这个工厂,然后调用getObject()创建真正的Bean。
getObjectType()可以指定创建的Bean的类型,因为指定类型不一定与实际类型一致,可以是接口或抽象类。

因此,如果定义了一个FactoryBean,要注意Spring创建的Bean实际上是这个FactoryBean的getObject()方法返回的Bean。
为了和普通Bean区分,我们通常都以XxxFactoryBean命名


Spring默认使用Singleton创建Bean,也可指定Scope为Prototype;
可将相同类型的Bean注入List;
可用@Autowired(required=false)允许可选注入;
可用带@Bean标注的方法创建Bean;
可使用@PostConstruct和@PreDestroy对Bean进行初始化和清理;
相同类型的Bean只能有一个指定为@Primary,其他必须用@Quanlifier("beanName")指定别名;
注入时,可通过别名@Quanlifier("beanName")指定某个Bean;
可以定义FactoryBean来使用工厂模式创建Bean

使用Resource
Resource是Spring中的Resource是在这个包的org.springframework.core.io.Resource
不是注解那个
这个就是用来读取文件的

@Component
public class AppService {
    @Value("classpath:/logo.txt")
    private Resource resource;

    private String logo;

    @PostConstruct
    public void init() throws IOException {
        try (var reader = new BufferedReader(
                new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
            this.logo = reader.lines().collect(Collectors.joining("\n"));
        }
    }
}

注入配置 @PropertySource(“app.properties”) // 表示读取classpath的app.properties

"${app.zone}"表示读取key为app.zone的value,如果key不存在,启动将报错;
"${app.zone:Z}"表示读取key为app.zone的value,但如果key不存在,就使用默认值Z。

Spring容器可以通过@PropertySource自动读取配置,并以@Value("${key}")的形式注入;
可以通过${key:defaultValue}指定默认值;
以#{bean.property}形式注入时,Spring容器自动把指定Bean的指定属性值注入

使用条件装配

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Profile("!test")
    ZoneId createZoneId() {
        return ZoneId.systemDefault();
    }

    @Bean
    @Profile("test")
    ZoneId createZoneIdForTest() {
        return ZoneId.of("America/New_York");
    }
}
如果当前的Profile设置为test,则Spring容器会调用createZoneIdForTest()创建ZoneId,
否则,调用createZoneId()创建ZoneId。注意到@Profile("!test")表示非test环境。

运行程序时,加上JVM参数-Dspring.profiles.active=test就可以指定以test环境启动。

实际上,Spring允许指定多个Profile,例如:

-Dspring.profiles.active=test,master
可以表示test环境,并使用master分支代码。

要满足多个Profile条件,可以这样写:

@Bean
@Profile({ "test", "master" }) // 同时满足test和master
ZoneId createZoneId() {
    ...
}

使用Conditional

除了根据@Profile条件来决定是否创建某个Bean外,
Spring还可以根据@Conditional决定是否创建某个Bean。

例如,我们对SmtpMailService添加如下注解:

@Component
@Conditional(OnSmtpEnvCondition.class)
public class SmtpMailService implements MailService {
    ...
}
它的意思是,如果满足OnSmtpEnvCondition的条件,才会创建SmtpMailService这个Bean。
OnSmtpEnvCondition的条件是什么呢?我们看一下代码:

public class OnSmtpEnvCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "true".equalsIgnoreCase(System.getenv("smtp"));
    }
}
因此,OnSmtpEnvCondition的条件是存在环境变量smtp,值为true。这样,我们就可以通过环境变量来控制是否创建SmtpMailService。
Spring只提供了@Conditional注解,具体判断逻辑还需要我们自己实现。
Spring Boot提供了更多使用起来更简单的条件注解,例如,如果配置文件中存在app.smtp=true,则创建MailService:

@Component
@ConditionalOnProperty(name="app.smtp", havingValue="true")
public class MailService {
    ...
}
如果当前classpath中存在类javax.mail.Transport,则创建MailService:

@Component
@ConditionalOnClass(name = "javax.mail.Transport")
public class MailService {
    ...
}
后续我们会介绍Spring Boot的条件装配。我们以文件存储为例,假设我们需要保存用户上传的头像,并返回存储路径,
在本地开发运行时,我们总是存储到文件:

@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "file", matchIfMissing = true)
public class FileUploader implements Uploader {
    ...
}
在生产环境运行时,我们会把文件存储到类似AWS S3上:

@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "s3")
public class S3Uploader implements Uploader {
    ...
}
其他需要存储的服务则注入Uploader:

@Component
public class UserImageService {
    @Autowired
    Uploader uploader;
}
当应用程序检测到配置文件存在app.storage=s3时,自动使用S3Uploader,
如果存在配置app.storage=file,或者配置app.storage不存在,则使用FileUploader。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值