Spring学习笔记-廖雪峰

spring

Spring Framework主要包括几个模块:

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

6(>= 17)和5不同

IoC容器

容器:软件环境

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

创建组件时,依赖共同的组件,导致资源获取和释放重复和困难,依赖关系复杂

因此,核心问题是:

  1. 谁负责创建组件?
  2. 谁负责根据依赖关系组装组件?
  3. 销毁时,如何按依赖顺序正确销毁?

IoC:控制反转,控制权从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制。因此,IoC又称为依赖注入

将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

最简单的配置是通过XML文件来告诉IoC容器如何创建组件,以及各组件的依赖关系

所有组件统称为JavaBean,即配置一个组件就是配置一个Bean

Spring的IoC容器同时支持属性注入(set)和构造方法注入,并允许混合使用。

装配Bean

xml Schema的格式是固定的,写<bean>的部分

<bean id = "" class = "">
	// 注入bean
    <property name="" ref="" />
    // 注入boolean、int、String
    <property name="maximumPoolSize" value="10" />
</bean>

Main中

创建IoC容器示例,加载配置文件

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 根据Bean的类型获取Bean的引用
        UserService userService = context.getBean(UserService.class);
        User user = userService.login("bob@example.com", "password");
        System.out.println(user.getName());
    }
}

不想写xml怎么办?

使用Annotation配置

@Component
定义Bean,默认名称是小写开头的类名
@Autowired
可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中(传参上)
一般把@Autowired写在字段上,通常使用package权限的字段,便于测试。

编写一个AppConfig类启动容器,和上面的Main差不多,实现类不同

@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());
    }
}

添加@Configuration:表示是配置类,因为建立容器要传入一个类名
@ComponentScan:自动搜索当前类所在的包以及子包,把所有标注为@Component的Bean自动创建出来,并根据@Autowired进行装配。
  • 每个Bean被标注为@Component并正确使用@Autowired注入;
  • 配置类被标注为@Configuration@ComponentScan
  • 所有Bean均在指定包以及子包内。

注意包的层次结构。通常来说,启动配置AppConfig位于自定义的顶层包(例如com.itranswarp.learnjava),其他Bean按类别放入子包。

Bean

普通的是单例(Singleton),调用getBean(Class)获取到的Bean总是同一个实例

Prototype(原型)Bean,每次调用getBean(Class),容器都返回一个新的实例

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
    ...
}

注入list

@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);
        }
    }
}

Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,我们每新增一个Validator类型,就自动被Spring装配到Validators中了。可以通过@Order(1)指定顺序

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

可选注入

这个参数告诉Spring容器,如果找到一个类型为ZoneId的Bean,就注入,如果找不到,就忽略。这种方式非常适合有定义就使用定义,没有就使用默认值的情况。

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

创建第三方Bean
在@Configuration类中编写一个Java方法创建并返回它,注意给方法标记一个@Bean注解。(单例)

@Configuration
@ComponentScan
public class AppConfig {
    // 创建一个Bean:
    @Bean
    ZoneId createZoneId() {
        return ZoneId.of("Z");
    }
}

初始化和销毁
初始化流程
调用构造方法创建实例;
根据@Autowired进行注入;
调用标记有@PostConstruct的init()方法进行初始化。
销毁时,容器会首先调用标记有@PreDestroy的shutdown()方法
*Spring只根据Annotation查找无参数方法,方法名可以不是init和shutdown。

别名(多个相同类型的Bean)
默认情况下,对一种类型的Bean,容器只创建一个实例。需要对一种类型的Bean创建多个实例,使用@Bean("z")或者@Bean+@Qualifier("name")指定别名。
注入时,要指定Bean的名称@Qualifier("z")
或者创建时@Primary // 指定为主要Bean,使用时不指定名称就会使用主要的Bean

怎么读取配置文件?

Resource

想在代码中应用配置文件中的配置?
AppService需要读取logo.txt这个文件,需要定位文件,打开InputStream。使用Properties 读出配置。

org.springframework.core.io.Resource
使用@Value注入

 @Value("classpath:/logo.txt")
    private Resource resource;
@Value("file:/path/to/logo.txt")
private Resource resource;

   @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"));
        }
        try (var input = new InputStreamReader(properties.getInputStream(), StandardCharsets.UTF_8)) {
			Properties props = new Properties();
			props.load(input);
			this.name = props.getProperty("app.name", "Cannot fetch the property [app.name]");
			this.version = props.getProperty("app.version", "Cannot fetch the property [app.version]");
		}
    }

使用Maven的标准目录结构,所有资源文件放入src/main/resources即可。
读取properties

roperties properties = new Properties();
// System.getProperty获取jvm环境变量值
String cfgPath = System.getProperty("user.dir") + "/etc/123.cfg";
InputStream inputStream = new BufferedInputStream(new FileInputStream(new File(cfgPath)));
properties.load(inputStream);
String version = properties.getProperty("app.version");

注入配置,注入一个文件

在@Configuration配置类上再添加一个注解@PropertySource(“app.properties”) // 表示读取classpath的app.properties
@PropertySource读取的配置是针对IoC容器全局的,其他任何bean都可以直接引用已读取到的配置

@Configuration
@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties
public class AppConfig {
    @Value("${app.zone:Z}")
    String zoneId;

    @Bean
    ZoneId createZoneId() {
        return ZoneId.of(zoneId);
    }
}

"${app.zone:Z}"表示读取key为app.zone的value,但如果key不存在,就使用默认值Z。也可以用于方法参数。

可以先通过一个简单的JavaBean持有所有的配置,在使用的地方用bean注入。

条件装配

创建某个Bean时,Spring容器可以根据注解@Profile来决定是否创建

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

    @Bean
    @Profile("test")
    ZoneId createZoneIdForTest() {
        return ZoneId.of("America/New_York");
    }
}

使用-Dspring.profiles.active=test,master
还可以用@Conditional(OnSmtpEnvCondition.class)

AOP

为什么要有AOP
业务方法中有安全检查、日志、事务等功能,横跨”多个业务方法。不得不在每个业务方法上重复编写代码。
把权限检查视作一种切面(Aspect),把日志、事务也视为切面,然后,以某种自动化的方式,把切面织入到核心逻辑中,实现Proxy模式。
依次实现:

核心逻辑,即BookService;
切面逻辑,即:

  • 权限检查的Aspect
  • 日志的Aspect
  • 事务的Aspect。

然后,以某种方式,让框架来把上述3个Aspect以Proxy的方式“织入”到BookService中。
如果客户端获得了BookService的引用,当调用bookService.createBook()时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。

装配AOP

  1. 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")

拦截器类型:
@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
@AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
@Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
2. 在切面类上标记@Component和@Aspect;

@Aspect
@Component
public class LoggingAspect
  1. 在@Configuration类上标注@EnableAspectJAutoProxy。
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
    ...
}

上面的问题:总不能每一个类都写一个注解吧?
写成包:包里每个Bean都加?面积有点大
前缀匹配:误伤更多
最好通过一个注解,让被自动代理的Bean知道自己被“安排”了。(反向,标这个注解的是希望被自动装配的)

// 定义注解
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
    String value();
}

// 被装配的方法加注解标志
@Component
public class UserService {
    // 监控register()方法性能:
    @MetricTime("register")
    public User register(String email, String password, String name) {
        ...
    }
    ...
}

// 切面类
@Aspect
@Component
public class MetricAspect {
    @Around("@annotation(a)")
    public Object metric(ProceedingJoinPoint joinPoint, MetricTime a) throws Throwable {
        String name = metricTime.value();
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long t = System.currentTimeMillis() - start;
            // 写入日志或发送至JMX:
            System.err.println("[Metrics] " + name + ": " + t + "ms");
        }
    }
}

真正重要的是MetricAspect metric方法第二个参数的引用类型。

注意

  1. 访问被注入的Bean时,不要直接访问字段的代码,改为通过方法get访问。
  2. 编写Bean时,如果可能会被代理,就不要编写public final方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值