文章目录
- 1. Spring
- 2. IOC 控制反转
- 2-1. 通过配置文件定义Bean
- 2-1-1. 通过set方法来注入Bean
- 2-1-2. 通过构造方法来注入Bean
- 2-1-3. 自动装配
- 2-1-4. 集合注入
- 2-1-5. 数据源对象管理(第三方Bean)
- 2-1-6. 在xml配置文件中加载properties文件的数据(context命名空间)
- 2-1-7. 加载容器的其他方式
- 2-1-8. p命名空间
- 2-1-9. c命名空间
- 2-1-10. util命名空间
- 2-2. 通过注解加载Bean
- 2-2-1. 选择性实例化Bean
- 2-2-2. 纯注解加载Bean
- 2-2-3. 自动装配
- 2-2-4. 简单类型的注入
- 2-2-5. 管理第三方Bean
- 2-3. Bean的实例化方式
- 2-3-1. 通过构造方法来实例化Bean
- 2-3-2. 通过简单工厂模式实例化
- 2-3-3. 通过工厂方法模式来实例化
- 2-3-4. 通过FactoryBean接口实例化
- 2-3-5. BeanFactory 和 FactoryBean 的区别
- 2-3-6. FactoryBean 的应用(以自定义Date类型举例)
- 2-4. Bean的生命周期(5步)
- 2-4-1. Bean 的生命周期之7步
- 2-4-2. Bean 的生命周期之10步
- 2-4-3. Bean的作用域
- 2-4-4. 自己实例化的对象让spring容器去管理
- 2-5. Bean 循环依赖问题
- 2-5-1. set注入 + 单例模式之循环依赖
- 2-5-2. 构造器注入 + 单例模式之循环依赖
- 2-5-3. Spring 解决循环依赖的机理
- 2-6. 自定义 spring 框架
- 3. Spring之JdbcTemplate
- 4. Spring 代理模式
- 4-1. jdk 之动态代理
- 4-2. cglib 之动态代理
- 4-3. jdk 与 cglib 动态代理的区别
- 5. 面向切面编程 AOP
- 6. Spring 事务
- 6-1. 事务的传播特性
- 6-2. 事务的隔离级别
- 6-3. 事务的超时时间
- 6-4. 设置事务只读(readOnly)
1. Spring
主要包括两种功能,分别为IOC(Inverse Of Control,意为着控制反转,用于反转创建Bean的控制权。通过使用ioc,可以降低代码的耦合度,耦合度指的是类与类之间的依赖关系,如果耦合度高表明类与类之间的依赖关系越紧密,此时如果修改其中的一些代码,可能会造成其他类出错的情况,对于后期的维护及其不便。使用ioc容器管理的Bean,推荐使用实现接口的实现类)、AOP(Aspect Oriented Programming,意为面向切面编程)。
2. IOC 控制反转
IOC意为控制反转,也就是反转创建Bean的控制权。在这之前我们需要调用一个类下的某个方法时,通常做法是首先对这个类进行实例化,然后再调用其实例化对象的方法。通过IOC,把这个创建类交给一个容器去管理,我们需要用到时,只需要从容器中去拿即可。当然前提是我们需要定义配置文件,当然,随着版本的迭代,后期发展到我们只需要添加一些注解即可。
2-1. 通过配置文件定义Bean
这个前提是需要有spring-context的依赖(我的这个是SpringBoot项目哈),导入这个依赖之后,在resources这个目录下鼠标右键,找到新建xml配置文件就有对应的配置文件格式了。
具体xml配置文件格式如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/></beans>
我在这个配置文件里边定义了一个Bean,并且这个Bean的名字为userDao,此时我们使用java代码就可以从这个容器中去获取这个Bean了。当然首先需要先加载到这个配置文件,这里使用 ClassPathXmlApplicationContext去加载,加载完之后可以得到一个ioc容器对象,此时,只需要通过这个ioc容器对象通过getBean即可获取对应的Bean对象。
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.support.ClassPathXmlApplicationContext;@SpringBootTest(classes = Test2.class)
public class Test2 {@Testpublic void test1(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");UserDao userDao = (UserDao) ctx.getBean("userDao");System.out.println(userDao);}
}
2-1-1. 通过set方法来注入Bean
在一个类下如果需要引入另外一个类的方法,前提是需要对这个类实例化。如果使用ioc,添加set方法即可(还有其他)。
package com.lz.demo_spring_24_1.service;import com.lz.demo_spring_24_1.dao.UserDao;
public class UserService {private UserDao ud;public void setUd(UserDao ud) {this.ud = ud;}public void print(){System.out.println(ud);}
}
对应的配置文件中需要做的配置如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/><bean id="userService" class="com.lz.demo_spring_24_1.service.UserService"><property name="ud" ref="userDao"/></bean>
</beans>
测试代码如下:
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.support.ClassPathXmlApplicationContext;@SpringBootTest(classes = Test2.class)
public class Test2 {@Testpublic void test1(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");UserService us = (UserService) ctx.getBean("userService");System.out.println(us);us.print();}
}
注意:需要提醒一下是,set注入的配置文件Bean的property属性name值是根据对应类的set方法名来的,而不是根据对应Bean的变量名。
上述是错误写法,其中name属性值应该修改为abcSdi才对。
另外,关于xml配置文件中的其他一些属性。
name属性用于给这个Bean起别名,多个别名之间用逗号隔开。
scope属性用于设置对应的Bean是单例,还是原型,默认情况下是单例的。
可以修改为原型的。对于一个简单类型,想在配置文件中注入值,只需要设置其value属性即可。
如果想了解哪些类型是简单类型,可以去BeanUtils类下找到isSimpleValueType方法,查看对应的源码就可以知道。
常见的简单类型有八种基本数据类型、字符串、枚举类型、Class类型、日期类型(不过日期类型在配置文件需要写入特定的格式才支持,因此通常情况下会把日期类型当作是简单类型来注入)。
2-1-2. 通过构造方法来注入Bean
需要添加对应的构造方法即可,然后在配置文件中添加对应的构造参数即可。
package com.lz.demo_spring_24_1.service;import com.lz.demo_spring_24_1.dao.UserDao;public class UserService {private UserDao ud;private Integer val;public void setVal(Integer val) {this.val = val;}public void setUd(UserDao ud) {this.ud = ud;}public UserService(){}public UserService(UserDao ud){this.ud = ud;}public UserService(UserDao ud,Integer val){this.ud = ud;this.val = val;}public void print(){System.out.println(ud+" "+val);}
}
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/><bean id="userService" class="com.lz.demo_spring_24_1.service.UserService"><constructor-arg name="ud" ref="userDao"/><constructor-arg name="val" value="1000"/></bean></beans>
上述这样配置存在一个问题,那就是耦合度比较高(因为这是通过构造方法的变量名来进行注入的)比如如果我在类文件里边修改ud为ud1,那么此时就需要在配置文件中做对应的修改。此时可以通过设置类型type属性从而解决这个耦合度问题,如下:
但是如果构造器方法中存在很多相同类型,上述解决办法就不行了,此时可以通过设置构造器方法中的参数位置index属性来解决。
2-1-3. 自动装配
在xml配置文件中的配置autowire属性即可,本质依旧是set注入,因此在类文件下依旧需要添加对应的set方法,如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/><bean id="userService" class="com.lz.demo_spring_24_1.service.UserService" autowire="byType"/>
</beans>
属性autowire的值也可以修改为byName,此时根据的是通过Bean的名字进行注入,因此在配置文件中要注入的Bean的id值不能随便取,这里需要额外注意一下。上述是根据Bean的类型进行注入的,只不过在xml配置文件中要注入的Bean只能为一个,否则会报错,因为它不知道到底需要注入哪一个。
对应的java类如下:
package com.lz.demo_spring_24_1.service;import com.lz.demo_spring_24_1.dao.UserDao;
public class UserService {private UserDao ud;public void setUd(UserDao ud) {this.ud = ud;}public void print(){System.out.println(ud+" ");}
}
既然是自动装配,那么装配的那个Bean肯定是需要在配置文件中进行定义的。
2-1-4. 集合注入
对应java类参考代码如下:
package com.lz.demo_spring_24_1.other;import java.util.*;public class Datas {private int[] arr1;private List<String> list1;private Set<String> set;private Map<String,Object> map;private Properties properties;public void setArr1(int[] arr1) {this.arr1 = arr1;}public void setList1(List<String> list1) {this.list1 = list1;}public void setSet(Set<String> set) {this.set = set;}public void setMap(Map<String, Object> map) {this.map = map;}public void setProperties(Properties properties) {this.properties = properties;}@Overridepublic String toString() {return "Datas{" +"arr1=" + Arrays.toString(arr1) +", list1=" + list1 +", set=" + set +", map=" + map +", properties=" + properties +'}';}
}
xml配置文件中的配置如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="datas" class="com.lz.demo_spring_24_1.other.Datas"><property name="arr1"><array><value>123</value><value>456</value></array></property><property name="list1"><list><value>123</value><value>234</value></list></property><property name="set"><set><value>123</value><value>234</value></set></property><property name="map"><map><entry key="country" value="china"/><entry key="age" value="100"/></map></property><property name="properties"><props><prop key="country">china</prop><prop key="age">100</prop></props></property></bean>
</beans>
如果在数组、List、Set中注入非简单类型,只需要把value标签修改为ref标签,且在ref标签的bean属性中写入对应的Bean的名称即可。
对于map数据类型,如果key、value值是非简单类型,直接使用key-ref、value-ref即可。
2-1-5. 数据源对象管理(第三方Bean)
这里以druid数据源为例,需要导入druid的依赖。
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version>
</dependency>
在对应的xml配置文件中定义的Bean如下(这里使用的是set注入,由于其并没有对应设置对应配置的构造方法,所以使用set注入,而不使用构造器注入):
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mytest1"/><property name="username" value="root"/><property name="password" value="root"/></bean></beans>
运行结果:
2-1-6. 在xml配置文件中加载properties文件的数据(context命名空间)
前提是需要在xml中开启context命名空间,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd">
</beans>
之后需要在xml配置文件中加载properties文件,最后修改其中Bean的一些配置即可。propertis文件配置如下:
spring.application.name=demo_spring_24_1jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mytest1
jdbc.username=root
jdbc.password=root
此时在xml配置文件中只需要使用${}引入对应的名称即可。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="application.properties"/>
<!-- 使用context空间加载properties文件--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>
</beans>
如果想要加载多个properties文件,可以在location属性中用逗号隔开,写上其他properties文件即可。
<context:property-placeholder location="application.properties,application2.properties"/>
上述这种写法并不怎么规范,规范写法应该是这样,如下:
<context:property-placeholder location="classpath:*.properties"/>
如果想不加载系统属性,可以在上面context的属性system-properties-mode设置为NEVER即可。
<context:property-placeholder location="application.properties" system-properties-mode="NEVER"/>
2-1-7. 加载容器的其他方式
上述方式都是通过加载类路径下的配置文件来进行的,其实还可以通过加载绝对路径来进行。
2-1-8. p命名空间
这种方式本质上是set注入,因此依旧需要在对应的类文件中添加set方法,但是在配置文件中可以简化一些操作而已。(是set注入的一种简化而已)首先,需要对配置文件中添加一点配置,参考如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
package com.lz.demo_spring_24_1.entity;import java.util.Date;public class User {private String name;private Date birthDay;public void setName(String name) {this.name = name;}public void setBirthDay(Date birthDay) {this.birthDay = birthDay;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", birthDay=" + birthDay +'}';}
}
只需要在配置文件以p:变量名后添加对应的值即可,因为变量birthDay是日期类型,可以使用简单类型,也可以使用非简单类型。
运行结果:
2-1-9. c命名空间
本质上是构造方法注入,因此需要添加对应的构造方法。(只是构造器注入的一种简化而已)。和p命名空间一样,都需要对xml配置文件添加一些配置,用以开启c命名空间。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="date" class="java.util.Date"/><bean id="user" class="com.lz.demo_spring_24_1.entity.User" c:_0="张三" c:birthDay-ref="date"/></beans>
有两种方式可以进行注入,一种是通过参数的位置,另外一种是通过参数名,参考如上,运行结果和p命名空间一样。
2-1-10. util命名空间
和上面两种命名空间一样,需要在xml配置文件中添加util命名空间的配置。配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
现在假设我想要连接数据库,但是可以使用连接池可以用druid、c3p0,它们都需要添加一些连接数据库的配置。
并且连接的配置信息都相同,此时可以采用util命名空间(实现配置复用而已),如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><util:properties id="pro"><prop key="driver">com.mysql.cj.jdbc.driver</prop><prop key="url">jdbc:mysql://localhost:3306/mytest1</prop><prop key="username">root</prop><prop key="password">root</prop></util:properties><bean id="ds1" class="com.lz.demo_spring_24_1.entity.MyDataSource1"><property name="properties" ref="pro"/></bean><bean id="ds2" class="com.lz.demo_spring_24_1.entity.MyDataSource2"><property name="properties" ref="pro"/></bean>
</beans>
运行结果如下:
2-2. 通过注解加载Bean
这种方式最初的版本依旧需要写xml配置文件,同时需要在对应的类上加上注解@Component,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.lz.demo_spring_24_1.dao"/></beans>
只要是在com.lz.demo_spring_24_1.dao这个目录下所有的添加了@Component注解的类都会被容器进行管理。如果需要添加其他包下Bean被spring容器进行管理,可以在上述配置文件的包后用逗号隔开,之后再添加其他包路径即可。当然也可以写两个包的父包即可。
package com.lz.demo_spring_24_1.dao;import org.springframework.stereotype.Component;@Component
public class UserDao {
}
从@Component又衍生出其他三种注解,分别为@Repository(数据层)、@Service(业务层)、@Controller(表现层)。它们的功能都相同,只是为了便于分辨而已。
2-2-1. 选择性实例化Bean
对于一个包下所定义的Bean(添加了对应注解的),如何在xml配置文件中选择性去选择哪些Bean可以被实例化。比如现在我定义了两个Bean,其中一个为a,另一个为b,a上添加了注解@Service,b上添加了注解@Controller,现在使用context命名空间扫描这两个bean的父包。然后进行过滤,只把有注解@Service Bean a添加到spring容器中进行管理,参考代码如下:
package com.lz.demo_spring_24_1.beans;import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;@Service
public class A {public A(){System.out.println("A........");}
}@Controller
class B {public B(){System.out.println("B........");}
}
xml配置文件写法1
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.lz.demo_spring_24_1.beans" use-default-filters="true"><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan></beans>
xml配置文件写法2
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.lz.demo_spring_24_1.beans" use-default-filters="false"><context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/></context:component-scan></beans>
运行结果如下:
2-2-2. 纯注解加载Bean
这种模式不需要编写xml配置文件,在上述代码不变的基础上,新建一个配置类,当然需要添加@Configuration注解。写这个配置类相当于是代替上面那个xml配置文件。
package com.lz.demo_spring_24_1.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan(value = "com.lz.demo_spring_24_1.dao")
public class SpringConfig {
}
之前是加载那个配置文件xml,此时如果想要运行成功,需要加载这个配置类。
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.config.SpringConfig;
import com.lz.demo_spring_24_1.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;import java.sql.SQLException;@SpringBootTest(classes = Test2.class)
public class Test2 {@Testpublic void test1() {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);UserDao userDao = (UserDao) ctx.getBean("userDao");System.out.println(userDao);}
}
2-2-3. 自动装配
现在我在数据访问层定义了一个类UserDao,还在业务层定义了一个类UserService,其中需要在UserService中引用UserDao类下的某个方法。为此,需要在UserService中new UserDao,在前面知识中了解到,可以通过set、构造器、自动装配这三种方式注入UserDao类对象,但是,上述讲述的是xml配置文件来进行注入的,现在如何使用配置类来实现上述那种自动装配的效果呢?参考代码如下:
package com.lz.demo_spring_24_1.dao;import org.springframework.stereotype.Repository;@Repository
public class UserDao {
}
package com.lz.demo_spring_24_1.service;import com.lz.demo_spring_24_1.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserDao ud;public void print(){System.out.println(ud);}}
我们只需要在UserService类中使用注解@AutoWired这种注入方式即可,当然也可以使用注解@Resource。它们两者的区别是前者首先是根据类型去对应包下去查找是否存在UserDao这个Bean,如果没有,再通过Bean名去查找,如果两者都没有找到或者出现歧义,最终会报错;而后者正好相反。@Resource注解可以通过name属性指定Bean名,如果name值没有指定,那么会把变量名当作Bean名来使用。。(也就是说如果此时变量名和对应的Bean名不一致,此时再根据类型来进行装配,如果没有找到,会报错。。。)
如果所注入的Bean有多个(比如有多个类都实现了某个接口,而且注入的Bean类型使用了泛型),此时可以在注解@AutoWired下添加注解@Qualifier,并在@Qualifier内写上对应的Bean名,当然这个Bean名需要在对应的类上写上才行。如下,有一个接口UserDao,它有一个方法printUserInfo,它有两个实现类UserDaoImpl1、UserDaoImpl2,这个类上都添加了注解@Repository。然后有一个类UserService,添加了注解@Service,在这个类下需要UserDao的依赖,这里直接使用泛型。如下:
package com.lz.demo_spring_24_1.beans;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserDao ud;public void printUserInfo(){ud.printUserInfo();}
}
此时运行结果如下:
因为UserDao有两个实现类,此时不知道使用哪个,因此报错,此时就可以添加注解@Qualifier添加对应的Bean名(不设置对应Bean名,Bean名默认为类名首字母小写。。),如下:
当然也可以直接使用@Resource注解,写上对应的Bean名。
另外,注解@AutoWired也可以放在set方法上及构造方法上,如果所注入的Bean只有一个,@AutoWired可以省略,但是需要添加对应的构造方法,需要注意的是如果存在默认无参构造方法,这样是不行的,如下:
2-2-4. 简单类型的注入
通过使用注解@Value可以注入简单类型,如下:
package com.lz.demo_spring_24_1.dao;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;@Repository
public class UserDao {@Value("lize")private String name;@Overridepublic String toString() {return "UserDao{" +"name='" + name + '\'' +'}';}
}
不过,上述这样写基本上没有任何意义,直接在name后面添加等于号并写上对应的值不也是一样吗?@Value真正意义在于可以注入配置文件properties中的变量,如下:
首先和上面xml配置一样,首先需要在配置类上指明所对应的配置文件,需要用到注解@PropertySource。
使用的话只需要在@Value注解内用${}指明引用哪个变量的值。
运行结果如下:
另外@Value还可以使用在set方法上,以及构造方法上的对应参数上。。
2-2-5. 管理第三方Bean
首先需要定义一个配置类,在这个配置类下定义对应Bean的方法。参考代码如下:
package com.lz.demo_spring_24_1.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;@Configuration
public class SpringConfig2 {@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.driver");ds.setUrl("jdbc:mysql://localhost:3306/mytest1");ds.setUsername("root");ds.setPassword("root");return ds;}
}
但是上述方式存在一个问题,就是如果我想修改上述一些配置信息,此时还需要找到这个类,然后再进行修改,为此,我们可以把上述信息放到配置文件properties中去,需要用到时只需要通过@Value注解注入即可。
2-3. Bean的实例化方式
这部分有一些内容和前面有重复,但是这里相当于是总结了吧!
2-3-1. 通过构造方法来实例化Bean
在xml配置文件中定义对应的Bean,然后直接通过容器.getBean方法来获取对应的Bean。默认情况下,是调用Bean的无参构造方法。。
2-3-2. 通过简单工厂模式实例化
本质上依旧是通过构造方法来实例化Bean,参考代码如下:
工厂类
package com.lz.demo_spring_24_1.entity.factory;public class Factory1 {public static User getUser(){return new User();}}
xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.lz.demo_spring_24_1.entity.factory.Factory1" factory-method="getUser"/></beans>
运行结果:
2-3-3. 通过工厂方法模式来实例化
本质上依旧是通过构造方法来实例化Bean,参考代码如下:
工厂类
package com.lz.demo_spring_24_1.entity.factory;public class UserFactory {public User get(){return new User();}
}
xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user2Factory" class="com.lz.demo_spring_24_1.entity.factory.UserFactory"/><bean id="user2" factory-bean="user2Factory" factory-method="get"/>
</beans>
运行结果:
2-3-4. 通过FactoryBean接口实例化
可以说是第三种方式的一种简化。参考代码如下:
实现了FactoryBean接口的类
package com.lz.demo_spring_24_1.entity.factory;import org.springframework.beans.factory.FactoryBean;public class UserFactoryBean implements FactoryBean<User> {@Overridepublic boolean isSingleton() {
// return FactoryBean.super.isSingleton();return true;}// 默认是单例的@Overridepublic User getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return null;}
}
xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user3" class="com.lz.demo_spring_24_1.entity.factory.UserFactoryBean"/>
</beans>
运行结果:
2-3-5. BeanFactory 和 FactoryBean 的区别
BeanFactory 是spring ioc容器的最顶层对象,意为 “Bean工厂”,负责创建Bean对象。
FactoryBean 是一个Bean,是一个能够辅助 spring 实例化其他Bean对象的一个Bean。在spring中,Bean可以分为两类,一种为普通Bean,另外一种是工厂Bean。
2-3-6. FactoryBean 的应用(以自定义Date类型举例)
从前面可以知道,Date这种类型既可以当作是简单类型,也可以当作非简单类型。当作简单类型时,在xml配置文件中定义时,需要输入特定格式,否则会报错。而 FactoryBean 是Spring中一种用于辅助实例化其他Bean的Bean,为此,可以使用 FactoryBean 的形式,使在xml文件定义Date 类型的Bean支持自定义格式输入,参考代码如下:
继承了FactoryBean 接口的类
package com.lz.demo_spring_24_1.entity.factory;import org.springframework.beans.factory.FactoryBean;import java.text.SimpleDateFormat;
import java.util.Date;public class DateFactoryBean implements FactoryBean<Date> {private String date_str;public DateFactoryBean(String date_str) {this.date_str = date_str;}@Overridepublic boolean isSingleton() {return true;}@Overridepublic Date getObject() throws Exception {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 自定义输入的日期格式return sdf.parse(date_str);}@Overridepublic Class<?> getObjectType() {return null;}
}
xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="date" class="com.lz.demo_spring_24_1.entity.factory.DateFactoryBean"><constructor-arg name="date_str" value="2021-01-01"/></bean></beans>
运行结果:
2-4. Bean的生命周期(5步)
就是Bean从被创建到销毁的过程。Bean的生命周期可以被划分为5个过程,分别是实例化Bean、Bean属性赋值、初始化Bean、使用Bean、销毁Bean。
- 实例化Bean,调用无参数构造方法;
- Bean属性赋值,调用set方法;
- 初始化Bean,调用 Bean 的 init方法,需要自己编写代码,并进行配置;
- 使用Bean;
- 销毁Bean,调用 Bean 的destory方法,需要自己编写代码,并进行配置;
Bean的声明周期代码演示如下:
package com.lz.demo_spring_24_1.entity;// Bean 的生命周期
public class User3 {private String name;public User3() {System.out.println("1. 实例化Bean。。。");}public void setName(String name) {this.name = name;System.out.println("2. Bean 参数赋值...");}// 初始化Beanpublic void init(){System.out.println("3. 初始化Bean。。。");}// 销毁 Beanpublic void destory(){System.out.println("5. 销毁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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.lz.demo_spring_24_1.entity.User3" init-method="init" destroy-method="destory"><property name="name" value="张三"/></bean></beans>
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.entity.User3;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test2025_1 {@Testpublic void test3(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-2025-3.xml");User3 user3 = ctx.getBean("user", User3.class);System.out.println("4. 使用Bean"+user3);// 使用Beanctx.close();// 关闭容器,只能applicationContext实现类才有close方法。。。}
}
运行结果:
2-4-1. Bean 的生命周期之7步
在上面说到Bean的生命周期只有5步,7步的说法是在前面5步的基础上添加了2步,就是在5步的第4步 初始化Bean 前面加上 执行”Bean后处理器“的before方法,在后面加上 执行”Bean后处理器“的fater方法。。。
需要在前面基本上添加一个类,这个类需要实现BeanPostProcessor接口。。。,并重写其下面的2个方法。
package com.lz.demo_spring_24_1.entity.interfaces;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;public class LogBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("执行 Bean后处理器 的before方法");return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("执行 Bean后处理器 的after方法");return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}
另外需要在xml配置文件中配置这个Bean。
之后就可以看到运行结果了。。。
需要注意的是,上述添加这个Bean对所有的Bean都会生效。。。也就是说 当前容器对象 getBean 之后获取到Bean都会执行上述两个函数。。。
2-4-2. Bean 的生命周期之10步
在前面7步的基础之上,再额外添加3步,添加的位置分别为:
- 在 执行”Bean后处理器“的before方法 前面添加 检查 Bean 是否实现了Aware的相关接口,并设置相关依赖;
- 在 执行”Bean后处理器“的before方法 后面添加 检查 Bean 是否实现了InitializingBean接口,并调用接口方法;
- 在使用Bean 之后添加了 检查 Bean 是否实现了DispossableBean接口,并调用接口方法。
其中Aware的相关接口有:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。
- 当Bean实现了BeanNameAware,spring会将Bean的名字传递给Bean;
- 当Bean实现了BeanClassLoaderAware,spring会将加载该Bean的类加载器传递给Bean;
- 当Bean实现了BeanFactoryAware,spring会将Bean工厂对象传递给Bean。
总结而言:如果测试生命周期10步,需要让对应的类实现5个接口,分别为BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、InitializingBean、DispossableBean。
演示代码如下:
package com.lz.demo_spring_24_1.entity;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;// Bean 的生命周期
public class User3 implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {private String name;public User3() {System.out.println("1. 实例化Bean。。。");}public void setName(String name) {this.name = name;System.out.println("2. Bean 参数赋值...");}// 初始化Beanpublic void init(){System.out.println("4. 初始化Bean。。。");}// 销毁 Beanpublic void destory1(){System.out.println("7. 销毁Bean。。。");}@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {System.out.println("Bean的类加载器:"+classLoader);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("Bean的 工厂对象是:"+beanFactory);}@Overridepublic void setBeanName(String s) {System.out.println("Bean的名字是:"+s);}@Overridepublic void destroy() throws Exception {System.out.println("实现了DisposableBean接口。。。");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("实现了InitializingBean接口。。。");}
}
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.lz.demo_spring_24_1.entity.interfaces.LogBeanPostProcessor"/><bean id="user" class="com.lz.demo_spring_24_1.entity.User3" init-method="init" destroy-method="destory1"><property name="name" value="张三"/></bean></beans>
运行结果:
2-4-3. Bean的作用域
spring 容器只对 单例的 Bean进行完整的生命周期管理。如果是原型的Bean,spring容器只负责将Bean初始化完毕,等客户端一旦获取到该Bean之后,spring容器就不在管理该对象的生命周期了。如果需要测试的话,只需要在xml配置文件的Bean添加属性scope,并设置值为原型,运行结果如下:
2-4-4. 自己实例化的对象让spring容器去管理
直接复制类代码,导包让软件去导入。。。
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.entity.User3;
import com.lz.demo_spring_24_1.entity.User4;
import com.lz.demo_spring_24_1.entity.factory.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Date;public class Test2025_1 {@Testpublic void test4(){User4 user4 = new User4();System.out.println(user4);DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();beanFactory.registerSingleton("user",user4);User4 user1 = beanFactory.getBean(User4.class);System.out.println(user1);}
}
运行结果:
2-5. Bean 循环依赖问题
其实就是在一个Bean a中需要Bean b的依赖,而在Bean b中又需要Bean a的依赖。
2-5-1. set注入 + 单例模式之循环依赖
比如如下代码:
package com.lz.demo_spring_24_1.entity.xunhuan;public class UserA {private String name;private UserB userB;public void setName(String name) {this.name = name;}public void setUserB(UserB userB) {this.userB = userB;}@Overridepublic String toString() {return "UserA{" +"name='" + name + '\'' +", userB=" + userB.getName() +'}';}public String getName() {return name;}
}
package com.lz.demo_spring_24_1.entity.xunhuan;public class UserB {private String name;private UserA userA;public void setName(String name) {this.name = name;}public void setUserA(UserA userA) {this.userA = userA;}@Overridepublic String toString() {return "UserB{" +"name='" + name + '\'' +", userA=" + userA.getName() +'}';}public String getName() {return name;}
}
xml配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="usera" class="com.lz.demo_spring_24_1.entity.xunhuan.UserA"><property name="name" value="张三"/><property name="userB" ref="userb"/></bean><bean id="userb" class="com.lz.demo_spring_24_1.entity.xunhuan.UserB"><property name="name" value="李四"/><property name="userA" ref="usera"/></bean>
</beans>
运行结果:
如果上述代码UserA的toString方法中参数直接是UserB,并且在UserB的toString方法中参数直接是UserA。此时的结果会报错,因为输出UserA这个对象时,实际上调用的是重写toString方法,而在toString方法中又会输出UserB,而在UserB的toString方法下又有UserA,此时会陷入si循环。。最终导致内存溢出从而导致报错。
上面是 单例模式 + set注入(原型模式下不可以) 的模式下的运行结果,Spring容器在加载的时候,进行实例化,只要任意一个Bean实例化后,马上进行“曝光”,不等属性赋值;Bean被“曝光”之后,再进行属性赋值。(在spring中为什么可以解决循环依赖的问题。。。)需要注意的是,在spring中只有当两个Bean都是原型下,才会出现异常,但是如果其中有一个是单例的,就不会出现异常。。
2-5-2. 构造器注入 + 单例模式之循环依赖
这种方式是存在问题,因为这是直接在Bean a构造方法里面给属性赋值,但是其中一个参数Bean b还没有进行实例化,而Bean b里边又有一个参数Bean a也没有进行实例化。参考代码如下:
package com.lz.demo_spring_24_1.entity.xunhuan;public class UserA {private String name;private UserB userB;public UserA(String name, UserB userB) {this.name = name;this.userB = userB;}/*public void setName(String name) {this.name = name;}public void setUserB(UserB userB) {this.userB = userB;}*/@Overridepublic String toString() {return "UserA{" +"name='" + name + '\'' +", userB=" + userB.getName() +'}';}public String getName() {return name;}
}
package com.lz.demo_spring_24_1.entity.xunhuan;public class UserB {private String name;private UserA userA;public UserB(String name, UserA userA) {this.name = name;this.userA = userA;}/*public void setName(String name) {this.name = name;}public void setUserA(UserA userA) {this.userA = userA;}*/@Overridepublic String toString() {return "UserB{" +"name='" + name + '\'' +", userA=" + userA.getName() +'}';}public String getName() {return name;}
}
也就是说 构造器注入 + 单例模式 这种方式下spring是无法解决循环依赖问题的。
2-5-3. Spring 解决循环依赖的机理
set注入+单例模式下为什么能解决循环依赖问题?
根本原因在于:这种方式可以将 实例化Bean 和 给Bean属性赋值 这两个动作分开去完成。实例化Bean的时候,调用无参构造方法来完成,**此刻可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。**给Bean属性赋值的时候,调用setter方法来完成。两个过程是完全分开去完成的,并且两个过程不要求在同一个时间点上完成。
2-6. 自定义 spring 框架
这里的spring框架只有基本ioc功能,且还是通过配置文件的形式。。参考代码如下:
myspring核心代码
package com.lz.demo_spring_24_1.myspring.utils;import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class MyBeanFactory {private Map<String,Object> beanMap = new HashMap<>();// 用来存储bean的哈希表// 在构造方法这里的读取xml配置文件的数据,// 然后通过反射机制进行实例化对象,之后把实例化后的对象存储到哈希表中进行存储public MyBeanFactory(String configPath) {try{SAXReader reader = new SAXReader();InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(configPath);Document document = reader.read(stream);List<Node> nodes = document.selectNodes("//bean");// 获取所有的bean标签for (Node node : nodes) {Element ele = (Element) node;// 转换成Element类型String id = ele.attributeValue("id");String clazz = ele.attributeValue("class");// bean 的名称,class 字符串Class<?> aClass = Class.forName(clazz);Constructor<?> constructor = aClass.getDeclaredConstructor();Object o = constructor.newInstance();beanMap.put(id,o);// 通过反射对bean进行无参实例化}setBeanField(nodes,beanMap);}catch (Exception e){e.printStackTrace();}}/*** 获取bean的方法* beanName : Bean的名称* */public Object getBean(String beanName){return beanMap.get(beanName);}// 给对象属性赋值// 相当于set注入private void setBeanField(List<Node> nodes,Map<String,Object> beanMap){for (Node node : nodes) {try{Element ele = (Element) node;String id = ele.attributeValue("id");String clazz = ele.attributeValue("class");// bean 的名称,class 字符串Class<?> aClass = Class.forName(clazz);List<Element> properties = ele.elements();// 所有的属性标签properties.forEach(property->{try{String name = property.attributeValue("name");String value = property.attributeValue("value");String ref = property.attributeValue("ref");String setName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);Field field = aClass.getDeclaredField(name);
// field.setAccessible(true);Class<?> type1 = field.getType();Method setMethod = aClass.getDeclaredMethod(setName, type1);Object v = value;if(value != null){// 这个变量是私有的// 简单类型String typeSimpleName = type1.getSimpleName();// 属性类型名switch (typeSimpleName){case "byte":v = Byte.parseByte(value);break;case "short":v = Short.parseShort(value);break;case "int":v = Integer.parseInt(value);break;case "long":v = Long.parseLong(value);break;case "boolean":v = Boolean.parseBoolean(value);break;case "float":v = Float.parseFloat(value);break;case "double":v = Double.parseDouble(value);break;case "char":v = value.charAt(0);break;case "Byte":v = Byte.valueOf(value);break;case "Short":v = Short.valueOf(value);break;case "Integer":v = Integer.valueOf(value);break;case "Long":v = Long.valueOf(value);break;case "Boolean":v = Boolean.valueOf(value);break;case "Float":v = Float.valueOf(value);break;case "Double":v = Double.valueOf(value);break;case "Character":v = Character.valueOf(value.charAt(0));break;}setMethod.invoke(beanMap.get(id),v);}if(ref != null){// 非简单类型setMethod.invoke(beanMap.get(id),beanMap.get(ref));}}catch (Exception e){e.printStackTrace();}});}catch (Exception e){e.printStackTrace();}}}
}
测试类
package com.lz.demo_spring_24_1.myspring;public class User {private String name;private Integer age;private User2 user2;public void setAge(Integer age) {this.age = age;}public void setName(String name) {this.name = name;}public void setUser2(User2 user2) {this.user2 = user2;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", user2=" + user2 +'}';}
}
package com.lz.demo_spring_24_1.myspring;public class User2 {
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="user" class="com.lz.demo_spring_24_1.myspring.User"><property name="name" value="张三"/><property name="age" value="20"/><property name="user2" ref="user2"/></bean><bean id="user2" class="com.lz.demo_spring_24_1.myspring.User2"/></beans>
运行结果:
需要注意的是,因为需要解析xml文件数据,需要导入对应依赖,如下:
<!-- 用于解析xml文件的包--><dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.2.0</version></dependency>
3. Spring之JdbcTemplate
JdbcTemplate是Spring提供的一个jdbc模板类,是对jdbc的封装。当然,现在大多数用的都是MyBatis等。首先需要导入的依赖为:
<!-- MySQL JDBC 驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency>
<!-- spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency>
另外还需要spring-context的依赖哈。因为我这边使用的mysql 数据库版本为5.xxx的版本,因此使用mysql的驱动为5.xxx,在对应的xml配置文件中的配置为:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myDataSource" class="com.lz.demo_spring_24_1.jdbcTemplate.MyDataSource"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mytest1?characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="root"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="myDataSource"/></bean></beans>
往数据库中插入一条数据,如下:
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.jdbcTemplate.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;import java.util.List;public class MyJdbcTemplate {@Testpublic void test(){ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbcTemplate.xml");JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate",JdbcTemplate.class);String sql = "insert into user values(?,?)";jdbcTemplate.update(sql,2,"王五");}
}
插入是可以成功的。
如果想查询数据,并且查询出的数据字段都需要映射到对应实体类上对应变量上去,可以使用如下代码:
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.jdbcTemplate.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;import java.util.List;public class MyJdbcTemplate {@Testpublic void test(){ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbcTemplate.xml");JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate",JdbcTemplate.class);String sql = "select * from user";List<User> users = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class));for (User user : users) {System.out.println(user);}}
}
如果想要更换成其他的DataSource,只需要在xml配置文件中修改对应DataSource的配置即可,class属性值修改为druid的,driver、url、username、password这四个属性名可能有所不同。。
4. Spring 代理模式
关于Spring aop的功能实现本质上就是动态代理,参考文章链接为:Spring AOP原理–动态代理。。。关于上述文章的静态代理,这里有更加详细的参考,代理类和被代理类都需要实现公共的接口,如下:
公共的接口类
package com.lz.demo_spring_24_1.proxy;
// 这是一个接口
public interface IUser {void play();// 方法 play
}
被代理的类
package com.lz.demo_spring_24_1.proxy.impl;import com.lz.demo_spring_24_1.proxy.IUser;public class IUserImpl implements IUser {@Overridepublic void play() {System.out.println("学习编程技术。。。");}
}
代理类
package com.lz.demo_spring_24_1.proxy;// 代理类
public class UserProxy implements IUser{// 这里应用泛型,可以降低代码的耦合度private IUser iUser = null;// 通过构造方法来把对应变量赋值public UserProxy(IUser iUser) {this.iUser = iUser;}@Overridepublic void play() {System.out.println("这里可以做一些前置操作。。。");iUser.play();System.out.println("这里可以做一些后置操作。。。");}
}
测试运行
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.proxy.IUser;
import com.lz.demo_spring_24_1.proxy.UserProxy;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import org.junit.jupiter.api.Test;public class ProxyTest {@Testpublic void test(){IUser iUser = new IUserImpl();IUser userProxy = new UserProxy(iUser);userProxy.play();}
}
但是上述代理存在一个很大的问题,那就是接口下面的方法如果很多的话,并且在代理类上上的每个方法都需要增强,那么被代理类就需要写很多可能较为重复的增强代码;而且每个被代理类的都需要编写对应的代理类。因此,有了动态代理。。。
4-1. jdk 之动态代理
使用动态代理,代理类可以不用编写了,但是接口必须要有。。在上述代码基础之上进行操作。。。接口类和被代理类和上面一样。。jdk动态代理不需要额外添加依赖。
Proxy.newProxyInstance(arg1,arg2,arg3)
通过上述代码实现一个代理对象,其中参数分别表示的意思为:
- arg1:被代理类的类加载器;
- arg2:被代理类实现的接口;
- arg3:最为关键,实现InvocationHandler的对象a,且对象a传入参数为被代理的那个对象(这样才能实现增强代码);这个只需要写一个即可,就可以解决上述静态代理存在的那两个问题。
实现InvocationHandler接口的类
package com.lz.demo_spring_24_1.proxy.jdkProxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class MyHandler implements InvocationHandler {private Object target;public MyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// method 为被代理类方法// args 方法参数// 反射。。System.out.println("这里做一些前置操作。。。");Object ans = method.invoke(target,args);System.out.println("这里做一些后置操作。。。");return ans;}
}
运行代码:
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.proxy.IUser;
import com.lz.demo_spring_24_1.proxy.UserProxy;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import com.lz.demo_spring_24_1.proxy.jdkProxy.MyHandler;
import org.junit.jupiter.api.Test;import java.lang.reflect.Proxy;public class ProxyTest {@Testpublic void test2(){IUser iUser = new IUserImpl();IUser iUserProxy = (IUser) Proxy.newProxyInstance(iUser.getClass().getClassLoader(),iUser.getClass().getInterfaces(),new MyHandler(iUser));iUserProxy.play();}
}
运行结果和上述一致。。。
4-2. cglib 之动态代理
如果是maven项目,需要额外导入cglib的依赖才行。参考代码如下:
package com.lz.demo_spring_24_1.proxy.cglibProxy;import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class MyCallback implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("这里可以做一些前置操作。。。");Object ans = methodProxy.invokeSuper(target,args);System.out.println("这里可以做一些后置操作。。。");return ans;}
}
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.proxy.cglibProxy.MyCallback;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.Enhancer;public class ProxyTest {@Testpublic void test3(){Enhancer enhancer = new Enhancer();// 设置被代理的类enhancer.setSuperclass(IUserImpl.class);enhancer.setCallback(new MyCallback());IUserImpl iUser = (IUserImpl) enhancer.create();// 代理的类iUser.play();}
}
运行结果和上面一致。。
4-3. jdk 与 cglib 动态代理的区别
参考链接在这:jdk 与 cglib 动态代理的区别
5. 面向切面编程 AOP
详细请看这篇博文:Aop 面向切面编程
Spring 的AOP底层实现本质是动态代理,jdk、cglib动态代理两者都有,Spring在这两种动态代理中可以根据实际应用场景实现切换,如果是代理接口,会默认使用jdk动态代理;如果要代理某个类,这个类没有实现接口,那么就会切换到cglib。当然,也可以通过配置强制让Spring来使用两者动态代理中的一种。
首先需要导入aspect的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
之后编写xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.lz.demo_spring_24_1.proxy.aspect"/>
<!-- 自动扫描--><aop:aspectj-autoproxy/>
<!-- 让 @Aspect 起作用-->
</beans>
编写Aspect的切面类
package com.lz.demo_spring_24_1.proxy.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyAspect {@Before("execution(* com.lz.demo_spring_24_1.proxy.aspect.UserService.*(..))")public void fun1(){System.out.println("前置通知。。。");}}
测试代码
package com.lz.demo_spring_24_1;import com.lz.demo_spring_24_1.proxy.aspect.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyAspectTest {@Testpublic void test(){ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-2025-aspect.xml");UserService userService = ctx.getBean("userService",UserService.class);userService.selectAll();}
}
运行结果:
在这里可以打印一下UserService的Class值,可以发现它是属于cglib动态代理生成的。
因为UserService类并不是通过实现某某接口的。
如果想纯注解实现上述效果,只需要把上述xml配置文件用一个配置类来代替即可,配置类参考如下:
package com.lz.demo_spring_24_1.proxy.aspect;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan(value = "com.lz.demo_spring_24_1.proxy.aspect")
@EnableAspectJAutoProxy
public class Config {
}
当然测试代码的加载容器需要修改一下:
6. Spring 事务
具体详细请看这篇文章:Spring 事务。在一个业务中,可能涉及到多条sql数据的执行,这些sql数据要么全部执行成功,要么全部执行失败,为此,应用到了事务 Transaction。
事务的四个处理过程,包括开启事务、执行核心业务代码、提交事务、回滚事务。事务的四个特性为:原子性、一致性、隔离性、持久性,也就是常说的ACID,其中原子性表示事务不可以再分;一致性表示事务要么同时成功,要么同时失败;隔离性表示事务和事务之间互不干扰;持久性表示一旦事务提交,它对数据库的修改就是永久性的,即使系统发生故障,数据也不会丢失。
6-1. 事务的传播特性
关于事务的传播特性,总共有7种,下述只是给出常见的四种。
测试结果:如果外部事物存在,并且内部事务也存在,且两个事务的传播行为都为REQUIRED。此时内部事务有抛异常的代码,在外部事务里边进行了try/catch捕获,事务会进行回滚。
如果上述内部事务为REQUIRES_NEW,外部事务不变,此时外部事务会正常执行,内部事务会进行回滚。
6-2. 事务的隔离级别
事务的隔离级别包括读未提交、读已提交、可以重复读、串行化。在上面那篇文章里边只介绍了后3种,因为通过设置后面3种隔离级别,可以解决对应的问题,比如脏读、不可重复读、幻读。
实际测试:关于脏读,如果其中一个事务a执行查询操作,另外一个事务b执行插入操作。如果事务a设置的隔离级别为读未提交,b事务没有设置隔离级别(数据库是MYSQL,也就是隔离级别为读已提交)。事务b先执行,但是没有结束;事务a后执行并已结束,此时事务a读取到数据是脏数据,也就是脏读。如果事务a设置的隔离级别为读已提交,依旧按照上述执行流程来,此时事务a的运行结果会报错。
6-3. 事务的超时时间
如果事务设置了超时时间a,那么表示超过a秒如果该事务种所有的DML语句还没有执行完毕的话,最终结果会选择回滚。事务的超时时间指的事务开始到最后一条DML语句执行完的时间(只要不超过这个时间,就不会进行回滚操作)。如果最后一条DML语句后面还有很多业务逻辑,这些业务逻辑执行的时间不计入超时时间。
6-4. 设置事务只读(readOnly)
之所以设置事务为只读,是为了提高select语句的执行效率(这里启动了Spring的优化策略)。在这种事务下,只能执行查询操作,执行插入、删除、修改操作都会报错。