后端:Spring(IOC、AOP)
文章目录
- 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 {
@Test
public 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 {
@Test
public 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;
}
@Override
public 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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
</beans>
之后需要在xml配置文件中加载properties文件,最后修改其中Bean的一些配置即可。propertis文件配置如下:
spring.application.name=demo_spring_24_1
jdbc.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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://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;
}
@Override
public 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.xsd
http://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.xsd
http://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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://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.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.xsd
http://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.xsd
http://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 {
@Test
public 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 {
@Autowired
private 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 {
@Autowired
private 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;
@Override
public 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 {
@Bean
public 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> {
@Override
public boolean isSingleton() {
// return FactoryBean.super.isSingleton();
return true;
}
// 默认是单例的
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public 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;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 自定义输入的日期格式
return sdf.parse(date_str);
}
@Override
public 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 参数赋值...");
}
// 初始化Bean
public void init(){
System.out.println("3. 初始化Bean。。。");
}
// 销毁 Bean
public 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 {
@Test
public void test3(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-2025-3.xml");
User3 user3 = ctx.getBean("user", User3.class);
System.out.println("4. 使用Bean"+user3);
// 使用Bean
ctx.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 {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行 Bean后处理器 的before方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public 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 参数赋值...");
}
// 初始化Bean
public void init(){
System.out.println("4. 初始化Bean。。。");
}
// 销毁 Bean
public void destory1(){
System.out.println("7. 销毁Bean。。。");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("Bean的类加载器:"+classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("Bean的 工厂对象是:"+beanFactory);
}
@Override
public void setBeanName(String s) {
System.out.println("Bean的名字是:"+s);
}
@Override
public void destroy() throws Exception {
System.out.println("实现了DisposableBean接口。。。");
}
@Override
public 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 {
@Test
public 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;
}
@Override
public 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;
}
@Override
public 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;
}*/
@Override
public 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;
}*/
@Override
public 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;
}
@Override
public 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 {
@Test
public 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 {
@Test
public 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 {
@Override
public 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;
}
@Override
public 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 {
@Test
public 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;
}
@Override
public 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 {
@Test
public 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 {
@Override
public 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 {
@Test
public 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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://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 {
@Test
public 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的优化策略)。在这种事务下,只能执行查询操作,执行插入、删除、修改操作都会报错。