引用
https://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1266263217140032
https://juejin.im/post/6844903665887625230

IOC容器

IOC即控制反转(Inverse of Control,IoC)也叫:依赖注入(Dependency Injection)

IOC原理

通常情况下当某个类需要调用另一个类的方法时通常有两种方法:

  1. 主动创建被依赖对象
  2. 主动通过工厂获取对象

在Spring IOC容器中调用者只需被动接受Spring容器为调用者的成员变量赋值,注入方式有

  1. 设值注入:通过成员变量的setter方法
  2. 构造注入:通过调用者的构造器方法
  • 两种方法允许混合使用

两种注入方式的对比

设值注入有如下优点:

  • 与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受。通过setter方法设定依赖关系显得更加直观、自然。
  • 对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
  • 尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。

构造注入优势如下:

  • 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
  • 对于依赖关系无需变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏。
  • 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

IOC容器优点

在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:

  1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
  2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

配置Bean

Spring Bean管理由两点

  • 创建对象
  • 注入属性

    通过xml

    ```xml <?xml version=”1.0” encoding=”UTF-8”?>

    //无参构造(setter注入)

    1. <bean id="bean名称" class="完整包名">
    2. //注入属性
    3. <property name="引用的bean变量" ref="引用的bean类id" />
    4. <property name="非bean变量" value="值" />
    5. <property name="">
    6. <null/>
    7. </property>
    8. <property name="ada">
    9. //value可以含有特殊符号
    10. <value>
    11. <![CDATA[value]]>
    12. </value>
    13. </property>

    //有参构造(构造注入)

    1. //eg
    2. <bean id="userService" class="com.itranswarp.learnjava.service.UserService">
    3. <property name="userDao" ref="userDao" />

    //开启组件扫描 //包和子包都会被扫描

    1. <context:component-scan base-package="com.itranswarp.learnjava.service" use-default-filters="false">
    2. //扫描包含
    3. <context:include-filter type="过滤器类型" expression="值"/>
    4. //扫面不包含
    5. <context:exclude-filter type="过滤器类型" expression="值"/>

  1. <bean>中的属性
  2. - **id**
  3. - **class**
  4. - name
  5. <a name="FKBYe"></a>
  6. ### 通过注解
  7. 使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:
  8. - 每个Bean被标注为`@Component`并正确使用`@Autowired`注入;
  9. - 配置类被标注为`@Configuration`和`@ComponentScan`;(作为配置类代替xml和启动扫描)
  10. - 所有Bean均在指定包以及子包内。(默认扫描)
  11. 注意
  12. - Bean的默认名称为小写开头的类名
  13. - @Component @Controller @Repository @Service 作用都是一样的
  14. - 不需要setter方法
  15. - 创建ApplicationContext时使用new AnnotationConfigApplicationContext
  16. <a name="JC771"></a>
  17. #### 注解属性
  18. - @Autowired 根据属性类型进行自动装配
  19. - @Qualifier 根据属性名称(和@Autowired或@Bean一起使用,)
  20. - @Resource 根据类型或名称(在javax包中)
  21. - @Value 注入普通类型属性
  22. <a name="EfKTn"></a>
  23. ### 创建Bean
  24. - 通过xml的情况
  25. ```java
  26. ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
  27. UserService userService = context.getBean(UserService.class);
  • 通过配置类的情况

    1. ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  • BeanFactory在加载xml时,Bean不会被创建,只有在获取Bean的时候才会创建

  • ApplicationContext在加载xml时,所有Bean都会被创建

    Bean的作用域

    Spring支持如下五种作用域:
  1. singleton: 单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例。(默认)
  2. prototype: 每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例。
  3. request: 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。
  4. session:该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。
  5. global session: 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。

使用注解配置作用域:

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

xml配置作用域

  1. <bean id="userService"
  2. class="com.itranswarp.learnjava.service.UserService" scope="prototype">
  3. </bean>

FactoryBean

工厂Bean类型不一定与实际类型一致,可以是接口或抽象类。

  1. @Component
  2. public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {
  3. String zone = "Z";
  4. @Override
  5. public ZoneId getObject() throws Exception {
  6. return ZoneId.of(zone);
  7. }
  8. //可以指定创建的Bean的类型
  9. @Override
  10. public Class<?> getObjectType() {
  11. return ZoneId.class;
  12. }
  13. }

注入List

当有一系列接口相同,不同实现类的Bean时,可以将所有实现同一接口Bean类注入List
@Order(int)
可以指定注入顺序

  1. @Component
  2. @Order(1)
  3. public class EmailValidator implements Validator {
  4. ...
  5. }
  6. @Component
  7. @Order(2)
  8. public class PasswordValidator implements Validator {
  9. ...
  10. }
  11. @Component
  12. @Order(3)
  13. public class NameValidator implements Validator {
  14. ...
  15. }
  16. //Validator 为接口
  17. public interface Validator {
  18. void validate(String email, String password, String name);
  19. }
  20. @Component
  21. public class Validators {
  22. //Spring会注入所有实现Validators接口的Bean类到List
  23. @Autowired
  24. List<Validator> validators;
  25. }

可选注入

  1. @Autowired(required = false)

创建第三方Bean

  1. @Configuration
  2. @ComponentScan
  3. public class AppConfig {
  4. // 创建一个Bean:
  5. @Bean
  6. ZoneId createZoneId() {
  7. return ZoneId.of("Z");
  8. }
  9. }

初始化和销毁

Maven 引入

  1. <dependency>
  2. <groupId>javax.annotation</groupId>
  3. <artifactId>javax.annotation-api</artifactId>
  4. <version>1.3.2</version>
  5. </dependency>

在Bean的初始化init()和清理方法shutdown()上标记@PostConstruct@PreDestroy
Spring只根据Annotation查找无参数方法,对方法名不作要求。

  1. @Component
  2. public class MailService {
  3. @Autowired(required = false)
  4. ZoneId zoneId = ZoneId.systemDefault();
  5. @PostConstruct
  6. public void init() {
  7. System.out.println("Init mail service with zoneId = " + this.zoneId);
  8. }
  9. @PreDestroy
  10. public void shutdown() {
  11. System.out.println("Shutdown mail service");
  12. }
  13. }

Bean别名
相同类型的Bean只能有一个指定为@Primary,其他必须用@Quanlifier("beanName")指定别名;
注入时,可通过别名@Quanlifier("beanName")指定某个Bean;

使用Resource

获得Resource文件夹下的资源

  • 通过@Value(“classpath:/文件名”)获得输入流 ```java @Value(“classpath:/logo.txt”) private Resource resource; resource.getInputStream();
  1. - 通过@PropertySource("文件名")和Value("${}")
  2. ```java
  3. @Configuration
  4. @ComponentScan
  5. @PropertySource("app.properties") // 表示读取classpath的app.properties
  6. public class AppConfig {
  7. @Value("${app.zone:Z}")
  8. String zoneId;
  9. @Bean
  10. ZoneId createZoneId() {
  11. return ZoneId.of(zoneId);
  12. }
  13. }
  14. # app.properties
  15. app.zone=XXX

注意注入的字符串语法,它的格式如下:

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

  • 通过JavaBean读取 ```java //通过一个简单的JavaBean持有所有的配置 @Component public class SmtpConfig { @Value(“${smtp.host}”) private String host;

    @Value(“${smtp.port:25}”) private int port;

    //getter方法获得属性 public String getHost() {

    1. return host;

    }

    public int getPort() {

    1. return port;

    } }

//在需要读取的地方,使用#{smtpConfig.host}注入: @Component public class MailService { @Value(“#{smtpConfig.host}”) private String smtpHost;

  1. @Value("#{smtpConfig.port}")
  2. private int smtpPort;

}

  1. <a name="C6Caw"></a>
  2. ## 使用条件装配
  3. - @Profile("条件")
  4. - 根据条件判断是否创建Bean
  5. - 三个条件
  6. - native -- 开发
  7. - test -- 测试
  8. - production -- 生产
  9. - 支持否定("! ")和多个条件{" "," "}
  10. - @Conditional(OnSmtpEnvCondition.class)
  11. - OnSmtpEnvCondition 为实现了Condition接口的类,重写了matches方法
  12. - 根据具体逻辑判断是否创建Bean
  13. - @ConditionalOnProperty(name="app.smtp", havingValue="true")
  14. - 根据属性判断是否创建Bean
  15. - @ConditionalOnClass(name = "javax.mail.Transport")
  16. - 根据类的存在
  17. <a name="kvTiR"></a>
  18. # AOP
  19. AOP即面向切面编程,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。
  20. - AOP把横跨多个业务的方法从中剥离出来,**不需要重复编写业务代码**,又能做到**在不修改源代码的情况下添加新功能**
  21. <a name="iaP3f"></a>
  22. ## AOP原理
  23. - AOP本质就是一个**动态代理**
  24. **
  25. <a name="DSUTl"></a>
  26. ## AOP的实现方式
  27. 在Java平台上,对于AOP的织入,有3种方式:
  28. 1. 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
  29. 1. 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  30. 1. 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。
  31. - 有接口时使用JVM的动态代理
  32. - 无接口通过[CGLIB](https://github.com/cglib/cglib)
  33. <a name="NzufL"></a>
  34. ## 使用AOP
  35. 通过Maven引入Spring对AOP的支持:

org.springframework spring-aspects 5.2.9.RELEASE

  1. <a name="mx6Lc"></a>
  2. ### 装配AOP
  3. <a name="bcb4d77a"></a>
  4. #### 使用AspectJ
  5. - AspectJ语法
  6. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1561124/1601190593133-74ed190d-45d9-49e4-bd7d-d30908daf53d.png#align=left&display=inline&height=26&margin=%5Bobject%20Object%5D&name=image.png&originHeight=51&originWidth=950&size=89205&status=done&style=none&width=475)
  7. ```java
  8. //@Aspect注解
  9. //表示它的@Before标注的方法需要注入到UserService的每个public方法执行前
  10. //@Around标注的方法需要注入到MailService的每个public方法执行前后。
  11. @Aspect
  12. @Component
  13. public class LoggingAspect {
  14. // 在执行UserService的每个public方法前执行:
  15. // AspectJ
  16. @Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
  17. public void doAccessCheck() {
  18. System.err.println("[Before] do access check...");
  19. }
  20. // 在执行MailService的每个public方法前后执行:
  21. // Around注解的方法第一个参数必须是ProceedingJoinPoint,返回值为Object
  22. @Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
  23. public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
  24. System.err.println("[Around] start " + pjp.getSignature());
  25. //执行被增强的方法并返回值
  26. Object retVal = pjp.proceed();
  27. System.err.println("[Around] done " + pjp.getSignature());
  28. return retVal;
  29. }
  30. }
  31. //紧接着,我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解:
  32. @Configuration
  33. @ComponentScan
  34. @EnableAspectJAutoProxy
  35. public class AppConfig {
  36. ...
  37. }

一共需要三步:

  1. 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
  2. 为需要增强的类标记@Component@Aspect
  3. @Configuration类上标注@EnableAspectJAutoProxy

    通过切入点重用AspectJ

    image.png
  • 通过注解 ```java //定义一个注解 @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) { … } … }

//定义MetricAspect @Aspect @Component public class MetricAspect { //所有使用了MetricTime注解的方法都会被注入 @Around(“@annotation(metricTime)”) public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) 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”); } } }

  1. 1. 定义一个注解
  2. 1. 在需要Aspect的方法上使用该注解
  3. 1. 定义Aspect
  4. 1. `@Configuration`类上标注`@EnableAspectJAutoProxy`
  5. <a name="TmHjW"></a>
  6. ### AOP执行顺序
  7. 使用@Order(Number)注解设置AOP执行顺序,数字越小越优先
  8. <a name="r06qv"></a>
  9. ### 拦截器类型
  10. 拦截器有以下类型:
  11. - @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;<br />
  12. - @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;<br />
  13. - @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;<br />
  14. - @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;<br />
  15. - @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
  16. Around -> Before -> Method -> Around -> After -> AfterReturning = AfterThrowing
  17. <a name="0m7RX"></a>
  18. ## AOP避坑指南
  19. Spring通过CGLIB创建的代理类,不会初始化代理类自身继承的任何成员变量,包括final类型的成员变量!<br />正确使用AOP
  20. 1. 访问被注入的Bean时,总是调用方法而非直接访问字段;
  21. 1. 编写Bean时,如果可能会被代理,就不要编写`public final`方法。
  22. 这样才能保证有没有AOP,代码都能正常工作。
  23. <a name="WTqef"></a>
  24. ## AOP术语
  25. - Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
  26. - Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
  27. - Pointcut:切入点,即一组连接点的集合;
  28. - Advice:增强,指特定连接点上执行的动作;
  29. - Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
  30. - Weaving:织入,指将切面整合到程序的执行流程中;
  31. - Interceptor:拦截器,是一种实现增强的方式;
  32. - Target Object:目标对象,即真正执行业务的核心逻辑对象;
  33. - AOP ProxyAOP代理,是客户端持有的增强后的对象引用。
  34. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1561124/1601190225547-fffb3762-1edb-475f-ba11-73dc4e37e133.png#align=left&display=inline&height=302&margin=%5Bobject%20Object%5D&name=image.png&originHeight=604&originWidth=717&size=261564&status=done&style=none&width=358.5)
  35. <a name="YeGYo"></a>
  36. # JdbcTemplate
  37. ```java
  38. <dependency>
  39. <groupId>org.springframework</groupId>
  40. <artifactId>spring-jdbc</artifactId>
  41. <version>5.2.0.RELEASE</version>
  42. </dependency>
  • 一个JdbcTemplate实例,需要注入DataSource,这是通过方法参数完成注入的。

image.png
xml中配置druid连接池和jdbcTemplate

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  2. <!-- 基本属性 url、user、password -->
  3. <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
  4. <property name="url" value="jdbc:mysql://111.229.166.200:3306/easybuy?serverTimezone=Asia/Shanghai" />
  5. <property name="username" value="admin" />
  6. <property name="password" value="cyl032512" />
  7. <!-- 配置初始化大小、最小、最大 -->
  8. <property name="initialSize" value="1" />
  9. <property name="minIdle" value="1" />
  10. <property name="maxActive" value="20" />
  11. </bean>
  12. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  13. <property name="dataSource" ref="dataSource"/>
  14. </bean>
  • 增删改

image.png

  • 查询并返回某个值

image.png
image.png

  • 查询并返回对象

image.pngimage.png

  • 查询并返回集合

image.png
image.png

  • 返回基本类型集合

image.png

  • 批量修改

image.png
image.png