引用
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的情况```javaApplicationContext 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类型不一定与实际类型一致,可以是接口或抽象类。
@Componentpublic class ZoneIdFactoryBean implements FactoryBean<ZoneId> {String zone = "Z";@Overridepublic ZoneId getObject() throws Exception {return ZoneId.of(zone);}//可以指定创建的Bean的类型@Overridepublic 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);}@Componentpublic class Validators {//Spring会注入所有实现Validators接口的Bean类到List@AutowiredList<Validator> validators;}
可选注入
@Autowired(required = false)
创建第三方Bean
@Configuration@ComponentScanpublic class AppConfig {// 创建一个Bean:@BeanZoneId 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查找无参数方法,对方法名不作要求。
@Componentpublic class MailService {@Autowired(required = false)ZoneId zoneId = ZoneId.systemDefault();@PostConstructpublic void init() {System.out.println("Init mail service with zoneId = " + this.zoneId);}@PreDestroypublic 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.propertiespublic class AppConfig {@Value("${app.zone:Z}")String zoneId;@BeanZoneId createZoneId() {return ZoneId.of(zoneId);}}# app.propertiesapp.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># AOPAOP即面向切面编程,它和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语法```java//@Aspect注解//表示它的@Before标注的方法需要注入到UserService的每个public方法执行前//@Around标注的方法需要注入到MailService的每个public方法执行前后。@Aspect@Componentpublic 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@EnableAspectJAutoProxypublic 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. 定义Aspect1. 在`@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 />正确使用AOP1. 访问被注入的Bean时,总是调用方法而非直接访问字段;1. 编写Bean时,如果可能会被代理,就不要编写`public final`方法。这样才能保证有没有AOP,代码都能正常工作。<a name="WTqef"></a>## AOP术语- Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;- Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;- Pointcut:切入点,即一组连接点的集合;- Advice:增强,指特定连接点上执行的动作;- Introduction:引介,指为一个已有的Java对象动态地增加新的接口;- Weaving:织入,指将切面整合到程序的执行流程中;- Interceptor:拦截器,是一种实现增强的方式;- Target Object:目标对象,即真正执行业务的核心逻辑对象;- AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。<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>
- 增删改

- 查询并返回某个值


- 查询并返回对象


- 查询并返回集合


- 返回基本类型集合

- 批量修改


