引用
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原理
通常情况下当某个类需要调用另一个类的方法时通常有两种方法:
- 主动创建被依赖对象
- 主动通过工厂获取对象
在Spring IOC容器中调用者只需被动接受Spring容器为调用者的成员变量赋值,注入方式有
- 设值注入:通过成员变量的setter方法
- 构造注入:通过调用者的构造器方法
- 两种方法允许混合使用
两种注入方式的对比
设值注入有如下优点:
- 与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受。通过setter方法设定依赖关系显得更加直观、自然。
- 对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
- 尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。
构造注入优势如下:
- 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
- 对于依赖关系无需变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏。
- 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
IOC容器优点
在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:
- 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
- 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。
配置Bean
Spring Bean管理由两点
- 创建对象
-
通过xml
```xml <?xml version=”1.0” encoding=”UTF-8”?>
//无参构造(setter注入)
<bean id="bean名称" class="完整包名">
//注入属性
<property name="引用的bean变量" ref="引用的bean类id" />
<property name="非bean变量" value="值" />
<property name="">
<null/>
</property>
<property name="ada">
//value可以含有特殊符号
<value>
<![CDATA[value]]>
</value>
</property>
//有参构造(构造注入)
//eg
<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="userDao" ref="userDao" />
//开启组件扫描 //包和子包都会被扫描
<context:component-scan base-package="com.itranswarp.learnjava.service" use-default-filters="false">
//扫描包含
<context:include-filter type="过滤器类型" expression="值"/>
//扫面不包含
<context:exclude-filter type="过滤器类型" expression="值"/>
<bean>中的属性
- **id**
- **class**
- name
<a name="FKBYe"></a>
### 通过注解
使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:
- 每个Bean被标注为`@Component`并正确使用`@Autowired`注入;
- 配置类被标注为`@Configuration`和`@ComponentScan`;(作为配置类代替xml和启动扫描)
- 所有Bean均在指定包以及子包内。(默认扫描)
注意
- Bean的默认名称为小写开头的类名
- @Component @Controller @Repository @Service 作用都是一样的
- 不需要setter方法
- 创建ApplicationContext时使用new AnnotationConfigApplicationContext
<a name="JC771"></a>
#### 注解属性
- @Autowired 根据属性类型进行自动装配
- @Qualifier 根据属性名称(和@Autowired或@Bean一起使用,)
- @Resource 根据类型或名称(在javax包中)
- @Value 注入普通类型属性
<a name="EfKTn"></a>
### 创建Bean
- 通过xml的情况
```java
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = context.getBean(UserService.class);
通过配置类的情况
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
BeanFactory在加载xml时,Bean不会被创建,只有在获取Bean的时候才会创建
- ApplicationContext在加载xml时,所有Bean都会被创建
Bean的作用域
Spring支持如下五种作用域:
- singleton: 单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例。(默认)
- prototype: 每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例。
- request: 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。
- session:该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。
- global session: 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。
使用注解配置作用域:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
xml配置作用域
<bean id="userService"
class="com.itranswarp.learnjava.service.UserService" scope="prototype">
</bean>
FactoryBean
工厂Bean类型不一定与实际类型一致,可以是接口或抽象类。
@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {
String zone = "Z";
@Override
public ZoneId getObject() throws Exception {
return ZoneId.of(zone);
}
//可以指定创建的Bean的类型
@Override
public Class<?> getObjectType() {
return ZoneId.class;
}
}
注入List
当有一系列接口相同,不同实现类的Bean时,可以将所有实现同一接口Bean类注入List
@Order(int)
可以指定注入顺序
@Component
@Order(1)
public class EmailValidator implements Validator {
...
}
@Component
@Order(2)
public class PasswordValidator implements Validator {
...
}
@Component
@Order(3)
public class NameValidator implements Validator {
...
}
//Validator 为接口
public interface Validator {
void validate(String email, String password, String name);
}
@Component
public class Validators {
//Spring会注入所有实现Validators接口的Bean类到List
@Autowired
List<Validator> validators;
}
可选注入
@Autowired(required = false)
创建第三方Bean
@Configuration
@ComponentScan
public class AppConfig {
// 创建一个Bean:
@Bean
ZoneId createZoneId() {
return ZoneId.of("Z");
}
}
初始化和销毁
Maven 引入
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
在Bean的初始化init()和清理方法shutdown()上标记@PostConstruct
和@PreDestroy
:
Spring只根据Annotation查找无参数方法,对方法名不作要求。
@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");
}
}
Bean别名
相同类型的Bean只能有一个指定为@Primary
,其他必须用@Quanlifier("beanName")
指定别名;
注入时,可通过别名@Quanlifier("beanName")
指定某个Bean;
使用Resource
获得Resource文件夹下的资源
- 通过@Value(“classpath:/文件名”)获得输入流 ```java @Value(“classpath:/logo.txt”) private Resource resource; resource.getInputStream();
- 通过@PropertySource("文件名")和Value("${}")
```java
@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.properties
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() {
return host;
}
public int getPort() {
return port;
} }
//在需要读取的地方,使用#{smtpConfig.host}注入: @Component public class MailService { @Value(“#{smtpConfig.host}”) private String smtpHost;
@Value("#{smtpConfig.port}")
private int smtpPort;
}
<a name="C6Caw"></a>
## 使用条件装配
- @Profile("条件")
- 根据条件判断是否创建Bean
- 三个条件
- native -- 开发
- test -- 测试
- production -- 生产
- 支持否定("! ")和多个条件{" "," "}
- @Conditional(OnSmtpEnvCondition.class)
- OnSmtpEnvCondition 为实现了Condition接口的类,重写了matches方法
- 根据具体逻辑判断是否创建Bean
- @ConditionalOnProperty(name="app.smtp", havingValue="true")
- 根据属性判断是否创建Bean
- @ConditionalOnClass(name = "javax.mail.Transport")
- 根据类的存在
<a name="kvTiR"></a>
# AOP
AOP即面向切面编程,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。
- AOP把横跨多个业务的方法从中剥离出来,**不需要重复编写业务代码**,又能做到**在不修改源代码的情况下添加新功能**
<a name="iaP3f"></a>
## AOP原理
- AOP本质就是一个**动态代理**
**
<a name="DSUTl"></a>
## AOP的实现方式
在Java平台上,对于AOP的织入,有3种方式:
1. 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
1. 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
1. 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。
- 有接口时使用JVM的动态代理
- 无接口通过[CGLIB](https://github.com/cglib/cglib)
<a name="NzufL"></a>
## 使用AOP
通过Maven引入Spring对AOP的支持:
<a name="mx6Lc"></a>
### 装配AOP
<a name="bcb4d77a"></a>
#### 使用AspectJ
- AspectJ语法
![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)
```java
//@Aspect注解
//表示它的@Before标注的方法需要注入到UserService的每个public方法执行前
//@Around标注的方法需要注入到MailService的每个public方法执行前后。
@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个public方法前执行:
// AspectJ
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}
// 在执行MailService的每个public方法前后执行:
// Around注解的方法第一个参数必须是ProceedingJoinPoint,返回值为Object
@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
//执行被增强的方法并返回值
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}
//紧接着,我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解:
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}
一共需要三步:
- 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
- 为需要增强的类标记
@Component
和@Aspect
; - 在
@Configuration
类上标注@EnableAspectJAutoProxy
。通过切入点重用AspectJ
- 通过注解 ```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. 在需要Aspect的方法上使用该注解
1. 定义Aspect
1. 在`@Configuration`类上标注`@EnableAspectJAutoProxy`。
<a name="TmHjW"></a>
### AOP执行顺序
使用@Order(Number)注解设置AOP执行顺序,数字越小越优先
<a name="r06qv"></a>
### 拦截器类型
拦截器有以下类型:
- @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;<br />
- @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;<br />
- @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;<br />
- @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;<br />
- @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
Around -> Before -> Method -> Around -> After -> AfterReturning = AfterThrowing
<a name="0m7RX"></a>
## AOP避坑指南
Spring通过CGLIB创建的代理类,不会初始化代理类自身继承的任何成员变量,包括final类型的成员变量!<br />正确使用AOP
1. 访问被注入的Bean时,总是调用方法而非直接访问字段;
1. 编写Bean时,如果可能会被代理,就不要编写`public final`方法。
这样才能保证有没有AOP,代码都能正常工作。
<a name="WTqef"></a>
## AOP术语
- Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
- Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
- Pointcut:切入点,即一组连接点的集合;
- Advice:增强,指特定连接点上执行的动作;
- Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
- Weaving:织入,指将切面整合到程序的执行流程中;
- Interceptor:拦截器,是一种实现增强的方式;
- Target Object:目标对象,即真正执行业务的核心逻辑对象;
- AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。
![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)
<a name="YeGYo"></a>
# JdbcTemplate
```java
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
- 一个JdbcTemplate实例,需要注入
DataSource
,这是通过方法参数完成注入的。
xml中配置druid连接池和jdbcTemplate
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://111.229.166.200:3306/easybuy?serverTimezone=Asia/Shanghai" />
<property name="username" value="admin" />
<property name="password" value="cyl032512" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
- 增删改
- 查询并返回某个值
- 查询并返回对象
- 查询并返回集合
- 返回基本类型集合
- 批量修改