Spring Framework

一、IOC/DI

IoC(Inversion of Control)控制反转

DI(dependency injection)依赖注入

IoC/DI指的是一个过程:对象的创建仅仅通过Spring容器负责,Spring容器可以通过对象的构造方法或工厂方法进行实例化对象。在创建对象过程中,如果对象需要依赖其他对象,也可以直接在Spring容器中注入到当前对象。

整个过程中对象本身在容器中控制自己的实例化(所以叫做控制反转),通过构造方法或setter方法把依赖对象注入到属性中(所以又叫做依赖注入)。

二、Bean创建的两种方式

1. BeanFactory

  通过读取、解析配置文件中Bean的配置信息,容器通过反射使用构造方法实例化对象,默认使用无参构造。对象由容器进行管理。

配置bean:

    <bean id="emp1" class="com.gsy.pojo.Emp">
    </bean>

2. FactoryBean

  配置自定义对象工厂的信息,容器从工厂获取创建的对象,对象由容器进行管理。

2.1 静态工厂

例如:

people工厂类:

public class PeopleStaticFactory {
    private static People peo = new People();

    public static People newInstance(){
        return peo;
    }
}

配置bean:

<bean id="peo3" class="com.gsy.factory.PeopleStaticFactory" factory-method="newInstance"/>
  • class:静态工厂类的全限定路径

  • factory-method:静态工厂中创建bean的静态方法。

由于静态方法可以直接通过类调用,所以不用创建工厂实例。

2.2 实例工厂

例如:

people工厂类:

public class PeopleFactory {
    People peo = new People();
    public People getInstance(){
        return peo;
    }
}

配置bean:

实例工厂需要先创建工厂的实例才能调用方法。

<!-- 创建工厂实例 -->
<bean id="factory" class="com.gsy.factory.PeopleFactory"></bean>
<!-- factory-bean:工厂对象的id    factory-method:创建当前bean的方法名称 -->
<bean id="peo2" factory-bean="factory" factory-method="getInstance"></bean>
  • factory-bean:工厂bean的id属性值。

  • factory-method:工厂中创建bean的方法。

三、属性注入

1. 手动注入

手动注入又分为构造注入和设值注入两种方式

1.1 构造注入

构造注入是通过构造方法将属性注入,即在创建对象时就完成属性的注入。

使用构造注入要提供对应参数的构造方法。

需要注意的是,在声明了有参构造方法后,需要显式声明无参构造,否则无参构造方法将被覆盖。

配置bean:

<bean id="peo4" class="com.gsy.pojo.People">
    <constructor-arg type="int" value="1"></constructor-arg>
    <constructor-arg type="java.lang.String" value="张三"></constructor-arg>
</bean>

参数说明:
  constructor-org:
  name:参数名
  index:参数索引
  type:参数类型
 以上三个参数不是都要指定,只要能确定参数即可。
  ref:引用类型
  value:参数值

注入引用类型的属性要求该类型的对象已经被spring容器托管,即配置了对应的bean,ref指定对应bean的id即可。

1.2 设值注入

设值注入通过setter方法实现,所以要先有对象才能进行设值注入。

设值注入一般是配合无参构造一起使用,所有要同时具备无参构造和对应的setter方法。

配置bean:

<bean id="peo5" class="com.gsy.pojo.People">
    <property name="id" value="2"></property>
    <property name="name" value="李四"></property>
</bean>

参数说明:
  name:属性名
  value:属性值
  ref:引用类型

ref用法同1.1 构造注入

两种方式可以混合使用(构造注入用于强制依赖的注入,setter注入用于可选依赖的注入)

<bean id="peo6" class="com.gsy.pojo.People">
    <constructor-arg index="0" value="3"></constructor-arg>
    <property name="name" value="王五"></property>
</bean>

1.3 其他类型属性的注入

无论是构造注入还是设值注入都提供了value和ref进行设置值。这两个属性只能给属性赋予简单数据类型或其他bean的引用。

1.3.1 Set类型

复制代码
<bean id="peo7" class="com.gsy.pojo.People">
    <property name="hover">
        <set>
            <value>abc</value>
            <value>efg</value>
        </set>
    </property>
</bean>
Set类型注入配置bean
复制代码

1.3.2 List类型

复制代码
<bean id="peo7" class="com.bjsxt.pojo.People">
    <property name="subjects">
        <list>
            <value>java</value>
            <value>前端</value>
        </list>
    </property>
</bean>
List类型注入配置bean
复制代码

1.3.3 Array类型

复制代码
<bean id="peo7" class="com.bjsxt.pojo.People">
    <property name="teachers">
        <array>
            <value>小明</value>
            <value>小花</value>
        </array>
    </property>
</bean>
Array类型注入配置bean
复制代码

1.3.4 Map类型

复制代码
<bean id="peo7" class="com.gsy.pojo.People">
    <property name="phone">
        <map>
            <entry key="姓名" value="gsy"></entry>
        </map>
    </property>
</bean>
Map类型注入配置bean
复制代码

1.3.5 Null类型

复制代码
<bean id="peo7" class="com.gsy.pojo.People">
    <property name="phone">
        <null></null>
    </property>
</bean>
null类型注入配置bean
复制代码

1.3.6 其他引用类型

如果需要引用其他Bean,直接在property标签中使用ref引用就可以。使用子标签ref也可以,但是没有直接用ref属性的方式简单。

2. 自动注入

自动注入是Bean之间的自动注入,使用自动注入的前提是Spring容器中必须有能被自动注入的Bean

在Spring中,允许Bean的自动注入,有两种方式进行配置。

2.1 在根标签<beans>中配置default-autowire属性,自动注入的策略,可取值有5个。

  1. default:默认值,不自动注入。(使用在bean标签中有其它含义)

  2. no:不自动注入。

  3. byName

    1. 通过名称自动注入。名称:属性名与bean的id属性值。

    2. 会自动寻找容器中与注入值属性名同名的id属性值的bean进行注入。

    3. 底层为setter注入,设值注入。

  4. byType

    1. 通过类型自动注入。类型:属性类型与bean的class类型。

    2. 会自动寻找容器中与注入值类型相同的bean进行注入。如果有多个相同类型的bean注入会出现异常。

    3. 底层为setter注入,设值注入。

  5. constructor

    1. 通过构造方法进行注入。调用有参构造方法完成对象创建及属性自动注入。

    2. 寻找bean的有参构造方法中只包含需要注入属性参数的构造方法。自动属性注入。

    3. 底层为构造注入。

    4. 先通过属性name找bean,找不到再通过属性的type

2.2 在<bean>标签中配置autowire属性,和default-autowire取值相同。

    • default:使用上级标签<beans>的default-autowire属性值。

    • default-autowire表示全局的配置。如果autowire和default-autowire同时存在,autowire生效。

四、bean标签的scope属性

Spring中<bean>的scope控制的是Bean的有效范围。

有6个取值,官方文档如下:

  singleton:默认值。bean是单例的,每次获取Bean都是同一个对象。

  prototype:bean是原型的,每次获取bean都重新实例化。

  request:每次请求重新实例化对象,同一个请求中多次获取时单例的。

  session:每个会话内bean是单例的。

  application:整个应用程序对象内bean是单例的。

  websocket:同一个websocket对象内对象是单例的。

singleton、prototype可以直接使用,其他的需要web环境才能使用。

1. singleton

单例设计模式。

scope的默认值,表示该bena是单例的,属于单例设计模式中的饿汉式(在类加载时就创建对象且只创建一次)

多线程情况下,公用一个对象,线程不安全。

2. prototype

原形设计模式。

表示该bean是原形的,在获取该对象时才创建且每次获取都会重新创建。

衍生问题:Spring 中 Bean是否是线程安全的?

  1. 如果bean的scope是单例的,bean不是线程安全的。

  2. 如果bean的scope是prototype,bean是线程安全的。

3. 补充:单例设计模式

单例设计模式:保证某个类在整个应用程序中只有一个对象。

单例写法分为两种:饿汉式、懒汉式。

1.1 饿汉式

复制代码
/*
    核心思想:
        1. 构造方法私有
        2. 对外提供一个能够获取对象的方法。
    饿汉式:
        优点:实现简单
        缺点:无论是否使用当前类对象,加载类时一定会实例化。
 */
public class Singleton {
    // 之所以叫做饿汉式:因为类加载时就创建了对象
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }
}
复制代码

1.2 懒汉式

复制代码
/**
 * 核心思想:
 * 1. 构造方法私有。
 * 2. 对外提供一个能够获取对象的方法。
 *
 * 懒汉式优点和缺点:
 *  优点:
 *      按需创建对象。不会在加载类时直接实例化对象。
 *  缺点:
 *      写法相对复杂。
 *      多线程环境下,第一次实例化对象效率低。
 */
public class Singleton2 {
    //懒汉式:不会立即实例化
    private static Singleton2 singleton2;
    private Singleton2() {}
    public static Singleton2 getInstance() {
        if (singleton2 == null) {// 不是第一次访问的线程,直接通过if判断条件不成立。直接return
            synchronized (Singleton2.class) {
                if(singleton2==null) {// 防止多个线程已经执行到synchronized
                    singleton2 = new Singleton2();
                }
            }
        }
        return singleton2;
    }
}
复制代码

第一个非空判断放在锁外面可以保证不用每个线程都要先等待获得锁在进行是否非空的判断,这样在该对象不为空时能提高效率;

第二个非空判断用于解决多线程问题,例如Thread1拿到了锁,Thread2执行完外层的非空判断阻塞,Thread1创建了对象,释放锁,此时Thread2获得锁,但没有非空判断,再次创建对象

所以第二个非空判断也是有必要的。

五、循环注入

1. 概念

循环依赖即 多个类相互依赖形成了闭环。

2. 循环依赖的场景

多例(原型模式)setter注入:每次获取对象都会新建,A创建后需要B,创建了B需要注入A,去获得A对象时就会新建,再去获得B也会新建。

构造注入:创建对象时就要完成注入,A的创建需要B,B的创建需要A,无法解决

3. 解决循环依赖

如果两个类都使用设值注入 且 scope为singleton就不会出现问题,因为单例默认有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的bean。

3.1 单例模式创建对象步骤

Spring的单例对象初始化分为三步:

1. 实例化:调用对象的构造方法创建对象;

2. 注入:填充属性,对bean的依赖属性进行填充;

3. 初始化:属性注入后,执行自定义初始化操作。

3.2 循环依赖问题

例如:B的实例对象属于A的一个属性,而A的实例对象又是B的一个属性,这就造成了循环依赖。

3.3 解决循环依赖问题

  • 三级缓存(singletonFactories):存放创建bean的工厂,便于扩展创建代理对象
  • 二级缓存(earlySingletonObjects):存放早期暴露出来的对象,属性还没有填充完整。
  • 一级缓存(singletonObjects):存放实例化,属性注入,初始化完成的完整对象。

其实根据上面的原理使用二级缓存就可以解决循环依赖问题,spring使用三级缓存是为了方便以后进行扩展。

六、BeanFactory和ApplicationContext

1. BeanFactory接口

BeanFactory接口是spring中的顶级接口,定义了Spring容器最基本的功能,是SpringIoC的最核心接口。

getBean方法其实就是BeanFactory的方法。

BeanFactory最常用实现类是XmlBeanFactory。

FileSystemResource fsr = new FileSystemResource("C:\IdeaWS\spring_day01\src\main\resources\application.xml");
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(fsr);
Object teacher = xmlBeanFactory.getBean("teacher");
System.out.println(teacher);

但是从Spring 3.1 版本开始,使用DefaultListableBeanFactory和XMLBeanDefinitionReader替代了上面写法。

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xbdr = new XmlBeanDefinitionReader(factory);
xbdr.loadBeanDefinitions(new FileSystemResource("C:\IdeaWS\spring_day01\src\main\resources\application.xml"));
Object teacher = factory.getBean("teacher");

无论是使用哪个写法,BeanFactory是在调用getBean方法的时候才实例化Bean。(懒汉式)

2. ApplicationContext

ApplicationContext是BeanFactory的子接口,拥有更多的功能,除了有BeanFactory的功能,还包含了:

  • AOP 功能

  • 国际化(MessageSource)

  • 访问资源,如URL和文件(ResourceLoader)

  • 消息发送机制(ApplicationEventPublisher)

  • Spring集成Web时的WebApplicationContext

 在使用时ApplicationContext时多使用ClassPathXmlApplicationContext

ApplicationContext applicationContext =
        new ClassPathXmlApplicationContext("applicationContext.xml");
Object teacher = applicationContext.getBean("teacher");

ApplicationContext是在加载配置文件后立即实例化Bean。(饿汉式)

也 可以通过bean标签的lazy-init属性进行控制,让bean懒加载。

七、Spring整合MyBatis

在pom.xml中引入依赖:

复制代码
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
        <!-- DataSource的实现类DriverManageDataSource实现所在的包 -->
        <!-- 把数据库连接操作交给Spring,后面事务才能使用Spring的声明式事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.16</version>
        </dependency>
        <!-- Spring整合MyBatis的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
相关依赖
复制代码

创建spring配置文件:

①spring管理数据源(连接池)

复制代码
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ssm?userUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="gsy666"/>

    </bean>
spring管理数据源
复制代码
id="dataSource" 是固定写法
class="org.springframework.jdbc.datasource.DriverManagerDataSource": spring提供的数据源
class="com.alibaba.druid.pool.DruidDataSource": druid提供的数据源
更换数据源只需要更换class

②spring管理sqlSessionFactory

复制代码
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>

        <property name="typeAliasesPackage" value="com.gsy.pojo"/>
    </bean>
spring管理sqlSessionFactory
复制代码

sqlSessionFactory是顶级接口,但sqlSessionFactoryBean不是SqlSessionFactory的直接实现类。
sqlSessionFactoryBean提供的getObject()方法可以返回一个SqlSessionFactory的实现类,
然后通过SqlSessionFactory的实现类的openSession()方法可以获得SqlSession。

typeAliases:为每一个类单独定义别名
typeAliasesPackage:扫描整个包中的类定义别名(别名为类名)

③spring管理接口绑定

复制代码
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--必须设置接口的包名-->
        <property name="basePackage" value="com.gsy.mapper"/>
        <!--设置使用的sqlSessionFactoryBean的name-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
spring管理接口绑定
复制代码
1.扫描指定包的接口
2.根据接口名找到映射文件(接口和映射文件同名且编译后在同一目录)
3.将每一个接口中的方法和映射文件中的sql进行绑定

八、Spring整合Web

Spring 集成Web环境是通过Listener实现的,在ServletContext对象创建时加载Spring容器。

Spring已经在spring-web.jar包中提供了ContextLoaderListener实现加载Spring配置文件的代码。

我们只需要在web.xml配置<listener>标签让ContextLoaderListener生效,并且告诉ContextLoaderListener加载Spring配置文件的路径即可。

在pom.xml引入依赖

复制代码
 <!-- Spring 核心模块的依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
        <!-- Spring 和web集成必须的依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.16</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
            <scope>provided</scope>
        </dependency>
相关依赖
复制代码

配置监听器:

在web.xml中配置监听器,让web项目启动时自动创建Spring容器对象(WebApplicationContext)。

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 监听ServletContext对象,对象创建时会加载Spring的配置文件,创建Spring容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>
web.xml
复制代码

上下文参数名param-name的值是固定的contextConfigLocation。这个值是监听器需要获取的值。

上下文参数值需要带有classpath,表示加载类路径内容。target/classes目录内容。

classpath*:表示当前项目依赖的jar中资源也会寻找。

classpath:后面的值支持星号。例如 applicationContext-*.xml 表示加载所有以applicationContext-开头的xml文件。

编写Servlet:

复制代码
@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
        People peo = wac.getBean("peo", People.class);
        System.out.println(peo);
    }
}
复制代码

九、注解支持

使用注解要在配置文件中配置扫描路径:

    <!--多个包用 , 隔开,也可以写上一级的包-->
    <context:component-scan base-package="com.gsy"/>

1. IoC/DI相关注解

注解名称解释
@Component 实例化Bean,默认名称为类名首字母变小写(类名不要出现类似AServiceImpl)。支持自定义名称
@Repository @Component子标签,作用和@Component一样,用在持久层。
@Service @Component子标签,作用和@Component一样,用在业务层。
@Controller @Component子标签,作用和@Component一样,用在控制器层。
@Configuration @Component子标签,作用和@Component一样,用在配置类。
@Autowired 自动注入。默认byType,如果多个同类型bean,使用byName(默认通过属性名查找是否有同名的bean,也可以通过@Qualifier("bean名称"),执行需要注入的Bean名称)
@Resource 非Spring注解。默认byName,如果没找到,使用byType。

@Repository、@Service、@Controller、@Configuration都是@Component注解的子注解,作用相同。

主要的区别是语义上的区别。不同的注解放在不同层的类中。

虽然不按照语义去做,非把@Service用在持久层,也是有效果的。但为了规范不建议这样做。

1.1 @AutoWired

@AutoWired注解用法十分灵活,可以使用在构造方法、普通方法、属性、方法的参数上

其底层是通过反射实现的自动注入,先byType,如果没有直接报错,如果有多个相同类型,再byName。

如果没有声明无参构造而是自动注入属性的有参构造,则会通过有参构造实现自动注入;

如果有无参构造,会使用反射进行自动注入;

当然也可以指定使用setter方法进行设值注入:只需在对应的set方法上加上@AutoWired注解。

也可以指定有参构造进行注入,也是在对应的构造方法上加@AutoWired注解

例:

1. 底层使用反射直接操作属性完成属性值的自动注入

@Service
public class UserServiceImpl {
    @Autowired
    UserMapperImpl userMapper;
}

2. 底层使用set方法完成属性值的自动注入。

复制代码
@Service
public class UserServiceImpl {
    UserMapperImpl userMapper;

    @Autowired
    public void setUserMapper(UserMapperImpl userMapper) {
        this.userMapper = userMapper;
    }
}
复制代码

3. 底层使用构造方法完成属性值的自动注入。

复制代码
@Service
public class UserServiceImpl {
    UserMapperImpl userMapper;

    public UserServiceImpl() {
    }
    @Autowired
    public UserServiceImpl(UserMapperImpl userMapper) {
        this.userMapper = userMapper;
    }
}
复制代码

 

十、Spring Test模块

Spring Test 模块整合了一些常见的单元测试工具,例如Junit。

整合后可以在测试类中直接使用Spring容器中的内容,把测试类也放入到Spring容器中,测试类里面可以直接使用注解注入容器中的bean对象。

同时也可以通过@ContextConfigration注解指定配置文件路径,让测试方法在启动的时候直接加载配置文件。

1. 添加依赖

  1. spring-context核心依赖无论使用哪个模块都必须导入的。

  2. junit依赖需要导入的,现在使用的就是Spring Test模块整合的junit功能。

  3. spring-test 表示spring test模块的依赖,导入后才有@ContextConfiguration这些注解。

复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.16</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.16</version>
        <scope>test</scope>
    </dependency>
</dependencies>
相关依赖
复制代码

2. 测试

测试类的名字不能为Test,否则会与@Test注解冲突

复制代码
// 使用Spring整合Junit4的类启动当前测试类(通过配置文件创建spring容器,将测试类交给spring管理)
@RunWith(SpringJUnit4ClassRunner.class)
// 启动时加载的配置文件,里面要包含classpath
@ContextConfiguration(locations = "classpath:applicatonContext.xml")
public class MyTest {
    @Autowired
    PeopleService peopleService;

    @Test
    public void test2(){
        peopleService.test();
    }
}
复制代码

十一、SpringAOP

1. 代理设计模式

代理模式是Java常见的设计模式之一。

所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。

代理设计模式包括:静态代理和动态代理。

静态代理:手动编写代理类。

动态代理:分为JDK动态代理和Cglib动态代理。

  其中JDK动态代理是基于接口实现,底层是反射。也就是代理对象和真实对象需要实现相同的接口,JDK动态代理是Java官方提供的技术。

  Cglib动态代理是基于继承实现的,底层通过修改字节码文件实现。也就是代理对象需要继承真实对象,Cglib动态代理是第三方的技术,使用的时候需要导入jar包。

1.1 JDK动态代理

接口:

public interface EmpService {
    List<Emp> allEmp();
}

实现类:

复制代码
@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private EmpMapper empMapper;
    
    @Override
    public List<Emp> allEmp() {
        List<Emp> emps = empMapper.allEmp();
        return emps;
    }
}
复制代码

jdk动态代理实现为接口创建代理对象:

复制代码
    @Test
    public void test03() {
        EmpService empService = (EmpService) Proxy.newProxyInstance(TestEmp.class.getClassLoader(), new Class[]{EmpService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("动态代理对象实现了接口的方法" + method.getName());
                        return null;
                    }
                });
        empService.allEmp();
    }
jdk动态代理为接口创建代理对象
复制代码
jdk动态代理为类创建代理对象(要求实现接口的类,因为jdk动态代理是基于接口的),实现目标对象的功能增强:
复制代码
 @Test
    public void test04() {
        ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpService empServiceImpl = (EmpService) classPathXmlApplicationContext.getBean("empServiceImpl");
        EmpService empService = (EmpService) Proxy.newProxyInstance(
                TestEmp.class.getClassLoader(),
                EmpServiceImpl.class.getInterfaces(),
                new InvocationHandler() {
                    /*
                    * 参数说明:
                    *    参数1:代理类
                    *    参数2:目标方法(被代理的方法)
                    *    参数3:方法参数
                    * */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("前置功能增强");
                       // method.invoke()就是调用被代理对象的方法      
                        return method.invoke(empServiceImpl);
                    }
                });
        List<Emp> emps = empService.allEmp();
    }    
jdk动态代理为类创建代理对象(要求该类为接口的实现类)
复制代码

1.2 Cglib动态代理

类:

public class TrueClass {
    public void testMethod(){
        System.out.println("真实类的方法");
    }
}

Cglib比jdk动态代理多了一个基于继承创建代理对象的功能

演示:

复制代码
    @Test
    public void test05(){
        //创建增强器
        Enhancer enhancer = new Enhancer();
        //设置被代理类,(代理类继承该类)
        enhancer.setSuperclass(TrueClass.class);
        //设置回调方法
        enhancer.setCallback(new MethodInterceptor() {
            /*
            * 参数说明:
            *    参数1:o是目标对象(被代理对象)
            *    参数2:method是目标方法(被代理对象的方法)
            *    参数3:objects是目标方法的参数
            *    参数4:methodProxy是代理方法
            * 动态代理对象会自动调用intercept()方法
            * */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("前置消息");
                methodProxy.invokeSuper(o,objects);
                System.out.println("后置消息");
                return null;
            }
        });
        //获取代理对象
        TrueClass trueClass = (TrueClass) enhancer.create();
        trueClass.testMethod();
    }
Cglib为类创建代理对象(基于继承)
复制代码

1.3 动态代理总结

  1. JDK动态代理机制是委托机制,只能对实现了接口的类生成代理,底层通过反射机制实现。

  2. CGLIB动态代理机制是接口或继承机制,针对类生成代理,被代理类和代理类是继承关系,底层通过字节码处理框架asm,修改字节码生成子类。

2. SpringAOP

2.1 含义(面)

面向切面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。

OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是切面。

切面能够实现跨越多种类型和对象的关注点(例如事务管理)的模块化。(这种关注点在 AOP 文献中通常被称为“横切”关注点。)

Spring 的关键组件之一是 AOP 框架。虽然 Spring IoC 容器不依赖于 AOP(意味着如果您不想使用 AOP,则不需要使用 AOP),但 AOP 补充了 Spring IoC 以提供一个非常强大的中间件解决方案。

强调:

  1. AOP 叫做面向切面编程。

  2. AOP 是对OOP的补充。

  3. AOP的核心是切面。

  4. AOP是对IoC的补充。

专业术语:

  Aspect:切面。为方法添加增强功能的过程。(是 point cut 与 advice 的组合)

  join point:切入点、连接点。就是我们平时说的目标方法,或说对哪个方法做扩展,做增强。

  Advice:通知。就是增强内容。

  Pointcut:切点。就是表达式(是对join point的描述,通过切点表达式可以找到需要增强功能的方法)

  AOP Proxy:代理。Spring支持JDK动态代理和cglib动态代理两种方式,可以通过proxy-target-class=true把默认的JDK动态代理修改为Cglib动态代理。

  Weaving:织入。织入就是把Advice添加到join point的过程。

该部分可以参考博文:https://blog.csdn.net/anurnomeru/article/details/79798659

执行流程:

  首先根据PointCut切点找到连接点joinpoint,即这就是需要增强的方法,那就创建代理对象Proxy,将Advice通知weaving织入到joinpoint连接点

 AOP主要用于Service层

 

问:什么是AOP?

答:AOP叫做面向切面编程,属于对OOP的扩展。其实现是基于动态代理设计模式,在IoC基础上实现的。

AOP就是对某个切入点做了通知进行增强扩展,形成横切面。可以实现在不修改原有代码的情况下,做额外扩展。

2.2 实现AOP的两种方式

在Spring中提供了两种方式实现AOP:

  • Schema-based:所有的通知都需要实现特定类型的接口实现通知。

  • AspectJ:可以使用普通Java类结合特定的配置实现通知。

2.3 AOP底层代理模式

SpringAOP底层默认为JDK动态代理,如果需要Cglib动态代理

需要引入Cglib的依赖以及在配置文件中开启Cglib动态代理

<aop:aspectj-autoproxy proxy-target-class="true"/>

true代表可以使用Cglib动态代理。

注意:AOP只对被spring托管的类对象有效。

十二、Schema-based方式实现AOP

使用Schema-based方式要先引入相关依赖:

复制代码
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
复制代码

1. Schema-based中通知的分类(面)

  • 前置通知:在切入点之前执行的增强功能。通知需要实现MethodBeforeAdvice接口。

  • 后置通知:在切入点之后执行的增强功能。通知需要实现AfterReturningAdvice接口。

  • 环绕通知:一个方法包含了前置通知和后置通知的功能。通知需要实现MethodInterceptor接口。

  • 异常通知:如果切入点中出现了异常(绝对不能try...catch解决了异常)就会触发异常通知。通知需要实现ThrowsAdvice接口。

2. 前置通知

前置通知是在切入点(目标方法)之前执行的增强。

新建通知类:MyBefore.java

@Component
public class MyBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知");
    }
}

参数说明:

  • method:切入点方法对象。

  • args:切入点方法参数。

  • target:切入点方法所在的对象。

配置切面(见本小节末)

3. 后置通知

后置通知是在切入点之后执行的增强。

新建通知类:MyAfter.java

@Component
public class MyAfter implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("目标方法返回值:"+returnValue);
        System.out.println("后置通知");
    }
}

参数说明:

  • returnValue:返回值

  • method:切入点方法对象

  • args:切入点方法参数

  • target:切入点方法所在的对象

配置切面(见本小节末)

4. 环绕通知

环绕通知可以实现前置通知和后置通知两种功能。

新建通知类:MyAround.java

复制代码
@Component
public class MyAround implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕-前置通知");
        Object proceed = invocation.proceed();
        System.out.println("环绕-后置通知");
        System.out.println("返回值:"+proceed);
        return proceed;
    }
}
复制代码

参数说明:

  • invocation:方法调用器。通过invocation可以proceed()方法调用执行点。

配置切面(见本小节末)

5. 异常通知

异常通知只有在切入点出现异常时才会被触发。如果方法没有异常,异常通知是不会执行的。

复制代码
@Component
public class MyThrow implements ThrowsAdvice {
    //没要要求必须实现什么方法,但是必须提供下面的方法,
    // public void afterThrowing 必须相同
    //必须有异常对象参数
    public void afterThrowing(Exception e){
        System.out.println("异常通知:"+e.getMessage());
    }
}
复制代码

6. 配置切面

<!--    <aop:aspectj-autoproxy proxy-target-class="true"/>-->
    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(* com.gsy.service.EmpServiceImpl.allEmp())"/>
        <aop:advisor advice-ref="myBefore" pointcut-ref="mypoint"/>
        <aop:advisor advice-ref="myAfter" pointcut-ref="mypoint"/>
        <aop:advisor advice-ref="myAround" pointcut-ref="mypoint"/>
        <aop:advisor advice-ref="myThrow" pointcut-ref="mypoint"/>
    </aop:config>

切点表达式固定写法:execution(返回值类型  包名.类名.方法名(参数类型,参数类型))

可以使用通配符 * 参数类型可以使用.. 代表任意参数

十三、AspectJ方式实现AOP

1. AspectJ方式通知类型(面)

  • 前置通知:before。

  • 后置通知:after。

    • after是否出现异常都执行的后置通知。

    • after-returning切入点不出现异常时才执行的后置通知。

  • 环绕通知:around。

  • 异常通知:after-throwing。

2. 创建通知类

Aspectj方式实现AOP的通知类不需要实现任何的接口,直接声明一个普通java类即可,然后在类中直接定义通知方法即可,方法名随意,但是建议方法名见名知意。

复制代码
@Component
public class MyAdvice {
    //前置通知方法
    public void before(){
        System.out.println("我是前置通知方法...");
    }
    //后置通知方法
    public void after(){
        System.out.println("我是后置通知方法...");
    }
    //环绕通知方法
    public Object round(ProceedingJoinPoint pp) throws Throwable {
        System.out.println("环绕---前");
        //放行
        Object proceed = pp.proceed();
        System.out.println("环绕---后");
        return proceed;
    }
    //异常通知方法
    public  void  myThrow(Exception e){
        System.out.println("我是异常通知......"+e.getMessage());
    }
}
自定义通知类
复制代码

3. 配置切面

复制代码
    <!--AspectJ方式-->
        <aop:config>
            <aop:aspect ref="myAdvice">
                <aop:pointcut id="mypoint" expression="execution(* com.gsy.service.EmpServiceImpl.allEmp())"/>
                <aop:before method="before" pointcut-ref="mypoint"/>
                <!-- 配置后置通知(没有异常,执行的后置通知 )-->
                <aop:after-returning method="after" pointcut-ref="mypoint"/>
                <!--配置后置通知(不管有没有异常都执行后置通知)-->
                <aop:after method="after" pointcut-ref="mypoint"/>
                <aop:around method="round" pointcut-ref="mypoint" />
                <!-- 配置异常通知 throwing="异常参数名"-->
                <aop:after-throwing method="myThrow" pointcut-ref="mypoint" throwing="e"/>
            </aop:aspect>
        </aop:config>
复制代码

4. Schema-based和Aspectj的区别

Schema-based:基于接口实现的。每个通知都需要实现特定的接口类型,才能确定通知的类型。由于类已经实现了接口,所以配置起来相对比较简单。尤其是不需要在配置中指定参数和返回值类型。

AspectJ方式:是基于配置实现的。通过不同的配置标签告诉Spring通知的类型。AspectJ方式对于通知类写起来比较简单。但是在配置文件中参数和返回值需要特殊进行配置。

因为Schame-based是运行时增强,AspectJ是编译时增强。所以当切面比较少时,性能没有太多区别。但是当切面比较多时,最好选择AspectJ方式,因为AspectJ方式要快很多。

5. AspectJ处理有返回值方法及方法参数

如果需要获取切入点方法,建议把通知方法的参数写成和切入点方法一致。

配置文件中的切点表达式需要加 and args(参数名,参数名)参数名和通知方法参数名保持一致

十四、注解实现AOP

注解方式只支持替换AspectJ的XML配置。

注意:

  1. 配置Spring容器注解扫描的路径。

  2. 配置AOP注解生效。

 <!--配置注解扫描路径-->
    <context:component-scan base-package="com.bjsxt"/>
    <!--配置AOP注解生效-->
    <aop:aspectj-autoproxy expose-proxy="true"/>

@Component

作用:

  相当于配置文件的bean标签,将某个类的对象扫描到Spring容器中。此注解一般在普通Java类上用。

注意:

  默认类名的首字母小写即为bean对象的ID,也可以使用注解的value属性声明自定义的ID,value可以省略不写。

使用:

  声明在类上。

@Aspect

作用:声明该类为通知类。

使用:结合@Component在通知类上使用,Spring扫描到容器中并作为通知类。

@pointcut

作用:声明切点。

使用:方法上使用。

@Before

作用:声明方法为前置通知方法。

使用:在前置通知方法上声明。

注意:需要在其中声明对应的切点的路径,非同包中需要指定全限定路径。

@After

作用:声明方法为后置通知方法。

使用:在后置通知方法上声明。

注意:需要在其中声明对应的切点的路径,非同包中需要指定全限定路径。

@Around

作用:声明方法为环绕通知方法。

使用:在环绕通知方法上声明。

注意:需要在其中声明对应的切点的路径,非同包中需要指定全限定路径。

@AfterThrowing

作用:声明方法为异常通知方法。

使用:在异常通知方法上声明。

注意:需要在其中声明对应的切点的路径,非同包中需要指定全限定路径。

复制代码
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.gsy.service.EmpServiceImpl.allEmp())")
    public void pointcut(){}
    @Before("pointcut()")
    //前置通知方法
    public void before(){
        System.out.println("我是前置通知方法...");
    }
    @After("pointcut()")
    //后置通知方法
    public void after(){
        System.out.println("我是后置通知方法...");
    }
    @Around("pointcut()")
    //环绕通知方法
    public Object round(ProceedingJoinPoint pp) throws Throwable {
        System.out.println("环绕---前");
        //放行
        Object proceed = pp.proceed();
        System.out.println("环绕---后");
        return proceed;
    }
    @AfterThrowing(value = "pointcut()",throwing = "e")
    //异常通知方法
    public  void  myThrow(Exception e){
        System.out.println("我是异常通知......"+e.getMessage());
    }
}
注解实现AOP(AspectJ)
复制代码

如果有返回值、参数

复制代码
@Component
@Aspect
public class MyAspectJ {
    @Autowired
    private LoginLogService loginLogService;
    @AfterReturning(value = "execution(* com.gsy.service.Impl.UserServiceImpl.login(String,String)) && args(uname,pwd)",returning = "user")//没有异常执行后置通知
    public void after(String uname,String pwd,User user){
        if(user!=null){
            loginLogService.addLog(new LoginLog(0,uname,new Date(),"登录成功"));
            System.out.println("后置通知:登陆成功");
        }else {
            loginLogService.addLog(new LoginLog(0,uname,new Date(),"登录失败"));
            System.out.println("后置通知:登陆失败");
        }
    }
}
有返回值和参数
复制代码

 十五、Spring声明式事务

Spring框架发现既然都是固定性代码,就由Spring帮助封装起来。

封装后对外让程序员只需进行简单的XML配置就可以完成事务管理,不再编写事务管理代码。这就是Spring非常重要的功能之一:声明式事务。

1.底层实现

声明式事务是基于AOP实现的。

程序员只需要编写调用持久层代码和业务逻辑代码。把开启事务的代码放在前置通知中,把事务回滚和事务提交的代码放在了后置通知中

2. 使用声明式事务

首先要要引入依赖spring-tx。

        <!-- 事务管理包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.16</version>
        </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:tx="http://www.springframework.org/schema/tx"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.gsy"/>
    <!--spring管理数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ssm?userUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="gsy666"/>
     </bean>
    <!--spring 管理sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.gsy.pojo"/>
    </bean>
    <!--spring 管理接口绑定-->
    <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.gsy.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
    <!--配置声明式事务-->
    <!--
        前置通知:开启事务
        后置通知:提交事务
        异常通知:事务回滚
    -->
    <!--使用注解配置事务,id必须为transactionManager 否则要在注解中手动指定-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务通知配置-->
    <tx:advice id="tx" transaction-manager="transactionManager">
        <!--配置方法名,AOP才会织入事务通知-->
        <tx:attributes>
            <!--
                name的值支持通配符
                    add*:add开头的方法
                    *:所有方法
            -->
            <tx:method name="*"  />
        </tx:attributes>
    </tx:advice>
<!--    配置AOP
        根据切点表达式获取连接点(目标对象的目标方法),为目标对象创建代理对象
        将通知和目标方法织入。-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.gsy.service.*.*(..))"/>
        <aop:advisor advice-ref="tx" pointcut-ref="pt"/>
    </aop:config>
</beans>
applicationContext.xml
复制代码

3. 使用注解@Transactional

使用注解可以省略事务通知和AOP的配置,但是必须要配置数据源事务管理器

开启注解扫描

<context:component-scan base-package="com.gsy"/>

配置数据源事务管理器、开启事务注解

 <!--使用注解配置事务,id必须为transactionManager 否则要在注解中手动指定-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务注解生效-->
    <tx:annotation-driven/>

重要提示:

 

  • @TransactionManager 默认寻找叫做transactionManager的事务管理器。如果没有找到会报异常NoSuchBeanDefinitionException。所以,如果希望配置注解时简单点直接写@Transactional就生效,就必须在XML配置事务管理器时,id必须叫做transactionManager。

 

  • 如果在XML配置事务管理器时,id不叫transactionManager,需要在@Transactional(transactionManager="XML配置时id值")。
  • @Transactional用在类上,整个类中方法都生效。
  • @Transactional用在方法上,该方法生效。用在方法上优先级更高。

十六、声明式事务四个基础属性

<tx:method>标签有下面属性的配置,@Transaction注解也支持部分属性(如果有则含义与相同,如果没有则表示在注解中不需要配置)。

1. name属性

  配置哪些方法需要有事务控制,支持*通配符

2. readonly属性(true|false)

是否为只读事务。

true:告诉数据库此事务为只读事务。底层支持查询的代码逻辑,不走提交事务和回滚事务的代码,会对性能有一定提升,所以只要是查询的方法,建议设置readonly="true"。

false(默认值):需要控制的事务。新增,删除,修改,不设置readonly属性或设置readonly="false"。

3. rollback-for属性

异常类型全限定路径,表示出现什么类型的异常进行数据回滚。

默认运行时异常及子类异常回滚,检查时异常不回滚。

4. no-rollback-for属性

异常类型全限定路径,当出现什么异常的时候不进行数据回滚。

十七、事务传播行为(面)

事务传播行为:当出现service的方法调用另一个service方法时(这些方法都被声明式事务管理),这些方法如何进行事务管理。

注意:

  1. 默认情况下都认为每个方法都是没有事务的(事务自动提交)。

  2. 整个调用最终都是在调用者里面统一提交回滚。

  3. 在声明式事务中,如果是同一个类的多个方法相互调用,属于同一个事务。

  4. 如果希望测试效果,必须把方法放入到多个不同的业务类中进行测试。

第三条原因:

  因为声明式事务是基于AOP实现的,AOP是基于动态代理实现的,为同一个对象创建一个代理对象,所以实现出来的效果只有对第一个调用的方法添加上了声明式事务管理,其他方法都是普通的方法调用。

可以通过进行配置tx:method或@Transactional中的propagation属性来进行传播行为的设置

propagation属性的可选值有:

  REQUIRED(默认值):如果当前有事务则加入到事务中。如果当前没有事务则新增事务。

  NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错。

  NESTED:必须在事务状态下执行。如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务(子事务)。

  REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起, 重新建个事务。(调用者统一提交回滚)

  SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。

  NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起。

  MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错。(可以配置在入口方法)

十八、事务隔离级别

多个事务同时操作数据库时,允许多个事务操作的方式就是事务隔离级别。

事务隔离级别主要是通过添加锁操作实现的。事务隔离级别主要是解决高并发下脏读、幻读、不可重复读问题的。

事务隔离级别出现场景:高并发场景。

Java主要做的就是Web项目(服务端项目),每次客户端发送的都叫请求。运行多人同时请求,每个请求运行同一个方法实际上是多个事务。

脏读:

  事务A没有提交事务,事务B读取到事务A未提交的数据,这个过程称为脏读。读取到的数据叫做脏数据。

不可重复读:

  当事务A读取到表中一行数据时,同时另一个事务修改这行数据,事务A读取到的数据和表中真实数据不一致。

幻读:

  事务A对表做查询全部操作,事务B向表中新增一条数据。事务A查询出来的数据和表中数据不一致,称为幻读。

可以在tx:method或@Transactional中设置属性isolation的值来进行配置。(select @@transaction_isolation查询数据库支持的事务隔离级别)

isolation可取值分别为:

DEFAULT:

表示用数据库的隔离级别,MySQL8默认的事务隔离级别REPEATABLE_READ。

READ_UNCOMMITTED:

读未提交(脏读,幻读,不可重复读)。

READ_COMMITTED:

读已提交(幻读,不可重复读)。

REPEATABLE_READ:

可重复读(幻读)。

MySQL采用了MVCC版本控制:

  1. 不添加锁的读,快照读(读取的数据状态进行临时存储,快照读读取临时数据),解决幻读

  2.加锁读,必须读取数据库中的数据,出现幻读

MySQL中锁机制:

1. 显示锁:
  1. 执行查询默认不使用锁

  2. 查询时使用锁

    查询sql for update; 添加排他锁

      查询sql lock in share mode; 添加了共享锁

2.自动锁:添加,修改,删除 自动添加排它锁

SERIALIZABLE

串行读来通过牺牲性能解决脏读、不可重复度、幻读问题。

十九、属性配置文件

现在我们在配置applicationcontext.xml的时候是直接将数据源参数在配置文件中直接配置的,就是数据库的链接参数。spring也提供了一种解耦合的配置方式,就是将数据源中的数据库链接参数单独的写在一个配置文件中。

在xml引入外部配置文件

<!--配置参数配置文件路径-->
 <context:property-placeholder location="classpath:db.properties"/>

使用${}获取数据

<!--配置数据源bean-->
<bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <property name="driverClassName" value="${driver}"></property>
     <property name="url" value="${url}"></property>
     <property name="username" value="${user}"></property>
     <property name="password" value="${password}"></property>
 </bean>

获取属性文件中的值

@Value

作用:用来替换配置文件中的属性注入的。

使用:在属性上声明,值为${“键名”}

注意:使用此注解的注入,无需提供get/set方法。

复制代码
@Component("u")
public class User {
    @Value("1")
    private Integer uid;
    @Value("${driver}")
    private String uname;
    private String pwd;
    ......
}
复制代码

二十、Bean的生命周期

Spring中Bean的生命周期就是指Bean从初始化到销毁的过程。Bean最简单的实现就是直接使用<bean>标签定义。

1. 生命周期流程图

在这种情况下会调用类的构造方法进行实例化。

  1. 通过标签的init-method和destory-method自定义初始化和销毁方法。

  2. 实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等,可以获取bean名字信息,bean工厂信息,容器信息。

  3. 通过InitializingBean,DisposableBean实例化Bean和销毁Bean。

  4. 通过BeanFactoryPostProcessor,BeanPostProcessor进行增强。

但是当前类不能实现BeanFactoryPostProcessor和BeanPostProcessor接口,且不能同一个类同时实现BeanFactoryPostProcessor,BeanPostProcessor。

Bean生命周期:
  1.编写bean的定义信息(xml,注解)
  2.通过BeanDefinitionReader 读取bean的定义信息
  3.解析出bean的定义信息
  4.可以通过BeanFactoryPostProcessor接口实现类,操作bean的定义信息
  5.实例化bean对象
  6.属性注入
  7.可以使用相关的Aware接口,可以获取bean的相关信息,容器信息...
  8.可以使用BeanPostProcessor接口中before方法操作对象
  9.可以使用init-method调用自定义的初始化方法
  10.可以使用BeanPostProcessor接口中after方法操作对象
  11.存储到单例池(一级缓存中)

 

 

 

 

 

 

 

 

 

 

 

posted @   ygdgg  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示