Spring配置

JavaConfig 配置类

Java Config 工作模式 就是 将所有 需要管理的 对象 在 配置类中 进行管理 。这个配置类的作用 类似于 Spring 容器。

  • @Configuration : 该注解 标记一个类 是 配置 类
  • @Bean : 该注解 应用在 配置类中对应的方法上, 用来标记 该方法 返回的结果 作为 Spring 管理的对象 , 能够 实现 控制反转。
  • @Import : 可以导入 其他 配置类 , 被导入的配置类 上 可以 不添加 @Configuration 注解
  • @ImportResource(“classpath:spring-context.xml”) : 读取 指定的 Spring 容器
  • @ComponentScan(“com.haredot”) : 替代 <context:component-scan /> 标签 ,负责扫描包
  • @PropertySource(value = “classpath:jdbc.properties”, encoding = “UTF-8”) : 读取 properties 配置文件

Aspectj 切面编程

  • 添加 aspectj 相关依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.27</version>
    </dependency>
    
    <!--  添加 aop aspectj 切面的支持 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.19</version>
    </dependency>
    
  • 启用 Aspectj 注解的支持

    @EnableAspectJAutoProxy(proxyTargetClass = false)
    

    proxyTargetClass 默认值为 false, 代表 采用 JDK 动态代理 , 如果设置为 true , 代表 采用 CGLIB 动态代理

    jdk 动态代理: 要代理的目标对象必须实现接口, 且 返回的 代理 对象 和 目标对象是 兄弟关系

    cglib 动态代理 : 要代理的目标对象可以不用实现任何借口 , 且 返回的 代理对象 是 目标对象 的 子类对象。

  • 编写 增强类

    @Aspect
    @Component
    public class TimerAspectj {
    
    
        @Pointcut("execution(* com.haredot.service..*.*(..))")
        public void before() {}
    
        @Before("before() && this(service)")
        public void before(JoinPoint joinPoint, Object service) {
            System.out.println("目标对象是:" + service);
            String sign = joinPoint.getSignature().toString();
            System.out.println("准备执行" + sign + "-----------");
        }
        @AfterReturning(value = "before()", returning = "ret")
        public void after(JoinPoint joinPoint, Object ret) {
            System.out.println("方法执行完成,返回结果是:" + ret);
        }
    
        @AfterThrowing(value = "before()", throwing = "e")
        public void exception(JoinPoint joinPoint, Throwable e) {
            System.out.println("产生了异常,异常信息是:" + e.getMessage());
        }
    
        @After(value = "before()")
        public void after(JoinPoint joinPoint) {
            System.out.println("无论成功还是失败、都会执行的代码...............");
        }
    
        @Around(value = "before()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            // 前置
            System.out.println("around before");
            Object ret = joinPoint.proceed();
            // 后置
            System.out.println("around after");
            return ret ;
        }
    }
    

控制反转创建注解

  • @Controller : 应用于 控制层
  • @Service : 应用于 业务路基层
  • @Repository : 应用于 持久层 (如果使用的是 mybatis框架, 则该注解用不上)
  • @Component : 通用注解,可以用在 任意层, 但推荐用在 除 控制层、业务层、持久层 之外的 其他层
  • @Named : JSR-330 标准 注解

上述的四个注解作用是等价的、都是注解到类上的。 只是 开发人员为了区分 不同的层、提供了不同的注解而已。

  • @Scope : 设置 bean 的作用范围 , 默认是 单例 模式, 如果要使用多例模式,需要设置 为 @Scope(“prototype”)

  • @Primary : 是否是 主要的 bean

  • DependsOn : 设置 该 对象 依赖与 某一个 对象

  • InitializingBean 接口 , 该接口 中提供的方法 afterPropertiesSet 会在 所有的属性 被依赖注入完成后 执行的代码, 可以做一些 属性校验 等工作 , 类似于 XML 中 init-method 配置

  • DisposableBean 接口 , 该接口 中提供的方法 destroy 会在 对象 销毁前 执行 一段 逻辑 、 类似于 XML 中 的 destroy-method 配置

  • @PostConstruct : 基于 JSR-250 规范 、等价于 init-method , 需要添加 javax.annotation-api 依赖库

  • @PreDestroy : 基于 JSR-250 规范, 等价于 destroy-method

依赖注入 常用的注解

  • @Value : 给 字面量 对应的 属性 注入 值 , 可以 应用在 Field (属性上) 、 setter 方法 (Property属性上) 、 构造方法的 参数 上 , 还可以使用 ${key} 的语法 从 配置文件中 获取值 并注入到对应的 属性中 。

  • @Autowired :

    • 如果写在 构造方法上, 用来标记 该构造方法 作为 Spring 管理对象 调用的 构造方法 。 如果 一个类中 没有提供任何构造方法,那么 会默认使用 无参构造方法创建对象, 如果一个类中 提供了 无参构造 和 有参构造 , 但没有提供 @Autowired,

      那么 会使用 无参构造创建对象, 如果 类中 有且只有 一个 有参构造, 那么 会使用 该 构造方法 尝试 对象。 如果有多个有参,建议 使用 @Autowired 进行标记

    • 可以 用来 注入 引用 对象 , 可以写在 属性上 、setter 方法上 。 默认 是 按照 注入属性的 类型 从 Spring容器中查找的, 如果 Spring容器中 没有该对象, 会直接抛出异常,可以通过 required 属性设置为 false 解决该错误 。 如果 容器中 有多个该类型的对象, 且没有使用 primary 进行 标记, 也会 报错。 可以 通过 @Qualifier 注解 通过 指定的 名字 注入 对象 。

  • @Resource : 该注解不是 Spring 提供的注解、 该注解是 JSR-250 规范中提供的注解

    • 该注解 不能写在构造方法 和 方法的参数上, 所以 不能实现 构造注入
    • @Resource 注解 默认 按照 名字 注入 对象, 可以 通过 name 设置要注入的 bean的 ID, 如果 设置的 name , 则只能按照 name 进行注入, 如果容器中找不到,则会抛出异常。 如果 没有设置 name , 那么 首先 会 根据 注释的 属性名 从 容器中 查找对象, 如果找到 ,则 直接注入 , 如果找不到 , 此时 会根据 类型 查找 , 如果 该类型 在容器中有多个, 则 抛出异常, 如果找不到,也会抛出异常 。

@Resource VS @Autowired VS @Inject

@Resource@Autowired@Inject
使用范围属性、 Setter 方法上构造方法上、属性上、 Setter方法上、 构造方法的参数上构造方法、属性、 Setter方法上
构造注入不能实现构造注入可以使用 构造注入可以使用 构造注入
注入方式默认按照名字注入,当名字找不到,按照类型注入。默认按照 类型查找 、可以配置 @Qualifier 实现按照名字查询默认按照名字查找,但却不能指定名字,默认名为注释的属性名,找不到,按类型查找
可以 配合 @Named 按照 指定名字查找
必须注入找不到,或者找到多个,均会抛出异常。默认必须注入 , 但可以通过 @Autowired(required=false) 设置,找不到也不会报错。找不到,或者找到多个,均会抛出异常。
提供方JSR-250Spring官方提供的JSR-330

Aspectj 切面编程

  • 添加 aspectj 相关依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.27</version>
    </dependency>
    
    <!--  添加 aop aspectj 切面的支持 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.19</version>
    </dependency>
    
  • 启用 Aspectj 注解的支持

    <aop:aspectj-autoproxy proxy-target-class="false" />
    
        proxyTargetClass 默认值为 false,  代表 采用 JDK 动态代理 ,  如果设置为  true , 代表 采用 CGLIB 动态代理 
    
       jdk 动态代理:  要代理的目标对象必须实现接口, 且 返回的 代理 对象 和 目标对象是 兄弟关系 
    
      cglib 动态代理 : 要代理的目标对象可以不用实现任何借口 , 且 返回的 代理对象  是 目标对象 的 子类对象。
    
  • 编写 增强类

    @Aspect
    @Component
    public class TimerAspectj {
    
    
        @Pointcut("execution(* com.haredot.service..*.*(..))")
        public void before() {}
    
        @Before("before() && this(service)")
        public void before(JoinPoint joinPoint, Object service) {
            System.out.println("目标对象是:" + service);
            String sign = joinPoint.getSignature().toString();
            System.out.println("准备执行" + sign + "-----------");
        }
        @AfterReturning(value = "before()", returning = "ret")
        public void after(JoinPoint joinPoint, Object ret) {
            System.out.println("方法执行完成,返回结果是:" + ret);
        }
    
        @AfterThrowing(value = "before()", throwing = "e")
        public void exception(JoinPoint joinPoint, Throwable e) {
            System.out.println("产生了异常,异常信息是:" + e.getMessage());
        }
    
        @After(value = "before()")
        public void after(JoinPoint joinPoint) {
            System.out.println("无论成功还是失败、都会执行的代码...............");
        }
    
        @Around(value = "before()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            // 前置
            System.out.println("around before");
            Object ret = joinPoint.proceed();
            // 后置
            System.out.println("around after");
            return ret ;
        }
    }
    

Spring 声明式事务管理

  • 引入 依赖包 spring-tx, spring-jdbc 依赖包 或者 引入 spring-orm 替代

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.27</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.27</version>
    </dependency>
    
  • 配置 事务管理器

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
  • 启用 事务注解的支持

    <tx:annotation-driven transaction-manager="transactionManager"/>
    
  • 在 业务层 使用 @Transactional 注解

    @Service
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    public class UserServiceImpl implements UserService {
    
        @Override
        @Transactional
        public int save() {
            System.out.println("正在插入用户....");
            return 1 ;
        }
    }
    

    当类上i提供了@Transactional, 那么所有 公开的方法上都会自动添加注解支持, 方法上也可以使用该注解, 如果类和方法上都有,则方法会覆盖类上的注解特点

Spring 声明式事务管理

  • 引入 相关依赖包

  • 配置 事务管理器

    @Configuration
    public class TransactionalAutoConfig {
        @Bean
        public TransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
    
  • 启用 注解支持

    @EnableTransactionManagement
    public class TransactionalAutoConfig {
    
        @Bean
        public TransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
  • 在业务层使用@Transactional注解

    @Service
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    public class UserServiceImpl implements UserService {
    
        @Override
        @Transactional
        public int save() {
            System.out.println("正在插入用户....");
            return 1 ;
        }
    }
    

Spring框架-基于XML配置

Spring 是一个 轻量级 容器 , Spring 可以管理 应用程序中 的 对象 。

Spring 的 核心 技术 是 IoC (Inversion of Control 控制反转) , DI (dependency injection 依赖注入) 、 AOP (切面编程)


IoC 控制反转

Spring 可以管理 应用程序中 的 对象 , 在 拥有Spring 环境的项目中, 很多对象 的创建 不需要 由开发人员 进行 new 创建 , 而是 将 对象的创建权限 交给 Spring 框架 去 处理。


控制反转 具体的用法

  • 在 resources 资源目录下 编写一个 spring-context.xml 文件 (文件名可随意) 、 该文件属于 Spring的 核心配置文件

    <?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 : 设置要管理对象的 类的 全名
                id :  设置 对象的 唯一标识
          -->
        <bean id="user" class="com.haredot.entity.User" scope="prototype" />
    
    </beans>
    

    一个 bean 标签 代表 Spring 管理的 一个对象 , 通过 id 设置 对象的唯一标记、 通过 scope 设置 对象的作用范围, 默认 singleton(单例) , 支持 proptotype (多例)

    • id : 设置 bean 的唯一标记、在整个Spring容器中不能重复
    • name : 给对象设置别名, 可以多个,多个用 逗号分割 , 整个容器 不能重复。
    • scope : Spring管理的 bean 默认采用 单例模式 ,scope = “singleton” 。 有状态的对象应该采用 多例模式 进行管理对象, 无状态对象 采用 单例模式 管理对象
    • abstract : 默认是 false, 如果设置为 true , Spring 不会创建该对象
    • depends-on : 用来控制对象的创建顺序, 如果一个 bean 配置了 depends-on 属性,那么 就要求 该 bean 的创建 必须在 depends-on 指定的对象构建完成后,在进行该对象的创建 。
    • init-method : 在构造方法调用后 执行的 初始化方法
    • destroy-method : 在对象销毁的时候 调用的方法,销毁的机制由 Spring容器 负责。
    • primary : 是否是主要的对象,如果设置为 true , 则 当容器中 该类型有多个对象, 优先返回 被 标记为 primary 的 对象
  • 获取 Spring 管理的 对象

    // 获取 Spring 容器 ApplicationContext
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml") ;
    
    // 方式一   根据ID 获取容器中的对象,返回Object , 使用的时候需要强制转换
    Object user = ctx.getBean("user");
    // 方式二    根据ID 和类型获取对象,能够进行自动转换
    User user1 = ctx.getBean("user", User.class);
    // 方式三    根据类型 获取对象
    User bean = ctx.getBean(User.class);
    

使用方式三 根据类型 获取Spring管理的对象时候, 如果容器中 找到该类型的多个对象,那么就会抛出 NoUniqueBeanDefinitionException 。

DI 依赖注入

通过 IoC 将对象的创建权限 交给 Spring框架, 那么 在 创建对象的同时 , 可以 给 对象中的 属性 注入(赋) 值。 给属性注入值的操作就被成为 依赖注入。


依赖注入的方式

  • 属性(Setter)注入

属性注入 是 Spring 提供的最强大的注入方式,也是 最灵活的注入, 也是 最常用的注入方式。

属性注入 要求 管理的 对象应该 提供 无参 构造方法。

<bean id="anl" class="com.haredot.entity.Animal">
    <!--
        通过 property 标签 给 属性 注入值, 一个 property 标签 代表 调用 一个 setter方法
          name : 设置 set方法对应的属性名
          value :  给字面量类型注入值
     -->
    <property name="id" value="1" />
    <property name="typeName" value="哈巴狗" />
</bean>
  • 字面量 value
    <property name="id" value="1" />
    
  • 引用对象 ref

    注入一个 Spring管理的对象

    <!-- public void setUser(User  user) {...} -->
    <!-- 使用 ref 配置 要注入的 对象的 beanID   -->
    <property name="owner" ref="user" /> 
    
    <bean id="user"  class="com.haredot.entity.User" />
    
  • List
    <!-- public void setHibbies(List<String> hobbies) {...} -->
    <property name="hobbies">
          <list>
              <value>学Java</value>
              <value>唱跳</value>
          </list>
      </property>
    
  • Array
    <!-- public void setHibbies(String[] hobbies) {...} -->
    <property name="hobbies">
        <array>
            <value>学Java</value>
            <value>唱跳</value>
        </array>
    </property>
    
  • Set
    <!-- public void setHobbies(Set<String> hobbies) {...} -->
    <property name="hobbies">
        <set>
            <value>学Java</value>
            <value>唱跳</value>
        </set>
    </property>
    
  • Map
    <!-- public void setOther(Map<String, Object> other) {...} -->
    <property name="other">
        <map>
            <entry key="card" value="411123453443534534" value-type="java.lang.Long"/>
            <entry key="dog" value-ref="anl"></entry>
        </map>
    </property>
    
    • key : 定义 map 中的键, 类型为 字符串
    • key-ref : 定义map 中的键, 类型由 bean 的类型决定 (不常用)
    • value: 注入 字面量类型的值
    • value-ref : 注入 Spring管理的 对象,值为 对应的 beanID
    • value-type : 如果 使用 value 注入值, 可以 通过 value-type 设置 值得类型, 如果没有 设置 value-type, 则默认为 String
  • Properties
    <!-- public void setAttr(Properties attr) {...} -->
    <property name="attr">
        <props>
            <prop key="XXX">123</prop>
            <prop key="YYY">111</prop>
            <prop key="ZZZ">222</prop>
        </props>
    </property>
    
  • null
    <property name="hobbies">
        <null />
    </property>
    
  • 特殊字符
    <![CDATA[ ... ]]> 是 XML 中一个特殊的标记,写在该标记中的内容 不会被解析,主要用来防止 特殊字符 拥有特殊含义问题 , 例如 " < > 等
    <property name="name">
        <value><![CDATA[张"三]]></value>
    </property>
    
  • 构造 注入

    通过 构造方法 注入数据, 应该在 类中 提供 有参构造 , 构造注入 不灵活,但可以 限定 某个属性 必须 传入 。

    public Animal(Long id, String typeName, User owner) {
        this.id = id;
        this.typeName = typeName;
        this.owner = owner;
    }
    
    
    <bean id="anl" class="com.haredot.entity.Animal">
        <!--
            一个 constructor-arg 标签 代表 一个构造方法的参数
             index : 代表 参数的 位置 , 从 0 开始
             type : 用来设置参数的类型
             name : 用来设置参数的 变量名 (不推荐)
    
             value : 用来注入 字面量类型的值
             ref : 注入引用对象 
          -->
        <constructor-arg index="0" name="id" value="1" />
        <constructor-arg index="1" type="java.lang.String" value="哈巴狗" />
        <constructor-arg index="2" ref="user" />
        
    </bean>
    

    构造注入 的参数如果是 list , set 等其他类型,请参考 属性注入的方式, 例如 1111

  • 静态工厂注入

    通过 静态方法 的注入 ,实现 对象的 管理

    // 添加一个工厂类、并提供一个 静态方法,负责产生 对象
    public class AnimalFactory {
    
        public static Animal getInstance() {
            return new Animal();
        }
    }
    
    <!--
    
    factory-method 指的是 工厂中提供的 静态方法, 该方法 不能有任何的参数, 且 必须 返回 目标 对象
    静态 方法 返回的 对象 会被 Spring 进行 管理 
    -->
    
    <bean id="animal" class="com.haredot.factory.AnimalFactory" factory-method="getInstance" />
    
  • 实例工厂注入
    // 添加一个工厂类、并提供一个 成员方法,负责产生 对象
    public class AnimalFactory {
    
        public  Animal getInstance() {
            return new Animal();
        }
    }
    
    <bean id="animalFactory" class="com.haredot.factory.AnimalFactory" />
    <bean id="animal" factory-bean="animalFactory" factory-method="getInstance" />
    

Spring 整合 MyBatis 框架

  • 添加 依赖包
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.27</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

<!--    mybatis 整合 spring 依赖包    -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.1.1</version>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>

<!--
    druid  阿里巴巴数据库连接池 -> 日志监控

    hikariCP : 是SpringBoot 内置的数据库连接池 -> 性能高

    C3P0 , DBCP , JNDI  (老牌的)
-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.18</version>
</dependency>
  • 配置数据源
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/rss" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <!--     最大连接数   -->
        <property name="maxActive" value="20" />
        <!--  最小空闲数      -->
        <property name="minIdle" value="2" />
        <!-- 初始化连接数 -->
        <property name="initialSize" value="5" />
        <!--        获取连接 的最大等待时间,单位毫秒 -->
        <property name="maxWait" value="5000" />
        <!--        用于验证连接是否有效的SQL查询 -->
        <property name="validationQuery" value="select 1" />
    
    </bean>
    
  • 配置 SqlSessionFactory 对象
     <!--  整合 mybatis   -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
        <!--   整合数据源     -->
        <property name="dataSource" ref="dataSource" />
        <!--    配置 mybatis 配置位置的 位置, 该步骤 可以省略(mybatis配置文件中的东西全部可以交给Spring管理,也可以部分交给Spring管理)    -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    
        <!--  管理映射文件  -->
        <property name="mapperLocations" value="classpath*:com/**/mapper/*.xml" />
    
    </bean>
    
  • 扫描持久层接口
    <!-- 方式一  -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.haredot.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
    
    <!-- 
      方式二 mybatis标签库
      mybatis:scan 进行扫描的时候,会和 <context:property-override /> 冲突
    -->
     <mybatis:scan base-package="com.haredot.dao" />
    
    

AES 加解密 (Hutool 工具包)

public class AESUtils {

    private static final String KEY = "odpVBu893FjowwYB7zXoAg==" ;

    private static final SymmetricCrypto aes ;
    static {
        aes = new SymmetricCrypto(SymmetricAlgorithm.AES, Base64.getDecoder().decode(KEY));
    }
    /**
     * 加密技术
     * @param rawText
     * @return
     */
    public static String encrypt(String rawText) {
        byte[] roots = aes.encrypt(rawText.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(roots) ;
    }

    public static String decrypt(String secureText) {
        byte[] decrypt = aes.decrypt(Base64.getDecoder().decode(secureText));
        return new String(decrypt, StandardCharsets.UTF_8) ;
    }

    public static void main(String[] args) {
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
        // 使用 Base64 进行编码
        System.out.println("密钥:" + Base64.getEncoder().encodeToString(key));

        System.out.println("root:" +encrypt("root"));
        System.out.println("123456:" + encrypt("123456"));

        System.out.println(decrypt("v2TMipYnOaNvlgJ9xY5wkQ=="));
        System.out.println(decrypt("wm273H7ywp6tkQjpvt6DQQ=="));

    }
}

读取 properties 配置文件

  • 方式一
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="fileEncoding" value="UTF-8" />
        <property name="location" value="classpath:jdbc.properties" />
    
        <!--
              默认情况下,环境变量和自己定义属性如果重复,优先使用的环境变量中的配置
              如果要解决改问题:设置 localOverride 覆盖系统内置的环境变量。
         -->
        <property name="localOverride" value="true"/>
        <!--    下面两个选项是默认值    -->
        <!--  <property name="placeholderPrefix" value="${" />-->
        <!--  <property name="placeholderSuffix" value="}" />-->
    </bean>
    
    // 配置文件中的加密内容如下
    // 账号  AES::v2TMipYnOaNvlgJ9xY5wkQ==
    // db.username = AES::v2TMipYnOaNvlgJ9xY5wkQ==
    // 密码
    // db.password = AES::wm273H7ywp6tkQjpvt6DQQ==
    
    // 配置 文件 敏感信息加密  (思路: 重写 PropertySourcesPlaceholderConfigurer 提供的 processProperties 方法
    // 此时 读取配置文件的类 使用 自定义的 类 替代掉 官方提供的 PropertySourcesPlaceholderConfigurer 即可。
    public class AESPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
    
        private static final String AEC_PREFIX = "AES::" ;
    
        protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
                                         final ConfigurablePropertyResolver propertyResolver) throws BeansException {
    
            propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
            propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
            propertyResolver.setValueSeparator(this.valueSeparator);
    
            StringValueResolver valueResolver = strVal -> {
                String resolved = (this.ignoreUnresolvablePlaceholders ?
                        propertyResolver.resolvePlaceholders(strVal) :
                        propertyResolver.resolveRequiredPlaceholders(strVal));
                if (this.trimValues) {
                    resolved = resolved.trim();
                }
                // 如果 resolved 不为 nullValue
                if (!resolved.equals(this.nullValue)) {
    
                    // 如果 以 AES_PREFIX 开头
                    if (resolved.startsWith(AEC_PREFIX)) {
                        // 截取字符串
                        resolved = resolved.substring(AEC_PREFIX.length()) ;
                        // 解密
                        resolved = AESUtils.decrypt(resolved);
                    }
                }
                return (resolved.equals(this.nullValue) ? null : resolved);
            };
    
            doProcessProperties(beanFactoryToProcess, valueResolver);
        }
    
    }
    
  • 方式二
    <context:property-placeholder file-encoding="UTF-8"
                                  location="classpath:jdbc.properties"
                                  local-override="true"/>
    
  • 方式三
    <!--
        负责读取 Properties 配置文件,  配置文件的键 必须 是 <beanId>.<propertyName> 格式
     -->
    <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
        <property name="fileEncoding" value="utf-8" />
        <property name="location" value="classpath:jdbc.properties" />
        <property name="localOverride" value="true" />
    </bean>
    
    <!--  配置数据源   -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" />
    

    使用PropertyOverrideConfigurer来读取配置文件,那么配置文件中的 键 必须满足的格式为 <BeanID>.<PropertyName>, 对应的 bean 不需要再进行依赖注入,而是会在解析配置的时候自动注入。

  • 方式四
    <context:property-override file-encoding="utf-8" 
                              local-override="true" 
                              location="classpath:jdbc.properties"/>
    <!--  配置数据源   -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" />
    

SqlSessionTemplate 的应用

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" c:_0-ref="sqlSessionFactory"/>

AOP 面向切面编程

AOP 是 面向切面编程、 是 Spring 框架 提供的 一套 切面技术 , 作用 是为了 给 面向 对象 进行 补充 和 增强

AOP 是 切面技术 、实现手段 通过 动态代理 , 默认采用 JDK 动态代理 。 也支持 CGLIB 代理

AOP 通常可以实现 权限拦截 、 日志处理 、事务管理 等操作 。

Spring 中 AOP 实现方式 可以通过 advice 和 aspectj 实现 。

Advice (通知)

  • MethodBeforeAdvice : 前置通知 , 进入 方法前执行的代码
  • AfterReturningAdvice : 后置通知 , 方法调用 正常结束 才会执行的代码
  • ThrowsAdvice : 异常抛出通知 , 当产生异常 才会执行对应的代码
  • MethodInterceptor : 环绕通知 , 可以替代 上述三个接口 。

aspectj (切面)

和 advice 相比 , aspectj 不需要 实现任何的接口 (普通的java) 、需要配合 切点表达式 将 切面 和 目标类 进行关联。

必须提供 spring-aopaspectjweaver 依赖包

  • 前置增强

    public class StartupTimerAspectj {
        /**
         * 前置增强方法
         */
        public void before(JointPoint jointPoint) {
            System.out.println("执行的时间是:" + LocalDateTime.now());
        }
    }  
    
    <bean id="startupTimerAspectj" class="com.haredot.aspectj.StartupTimerAspectj" />
    
    <aop:config proxy-target-class="false">
        <aop:aspect ref="startupTimerAspectj">
             <aop:pointcut id="startupTimer" expression="execution(public * com..dao.*Mapper.*(..))"/>
             <aop:before method="before" pointcut-ref="startupTimer"/>
        </aop:aspect>
    </aop:config>
    

    proxyTargetClass 默认值为 false, 代表 采用 JDK 动态代理 , 如果设置为 true , 代表 采用 CGLIB 动态代理

    jdk 动态代理: 要代理的目标对象必须实现接口, 且 返回的 代理 对象 和 目标对象是 兄弟关系

    cglib 动态代理 : 要代理的目标对象可以不用实现任何借口 , 且 返回的 代理对象 是 目标对象 的 子类对象。

  • 后置增强

    public class StartupTimerAspectj {
        /**
         *  JointPoint 默认只能作为 第一个参数 
         *  service  :   在配置增强类的时候,  this(service) 中绑定的对象 
         *  ret:  在 配置增强类的时候, returning="ret" 配置的 目标对象返回的 结果 
         */
        public void afterReturning(JoinPoint joinPoint, Object service, Object ret) {
            System.out.println("方法执行完成、时间为:" + LocalDateTime.now());
            System.out.println(joinPoint);
            System.out.println("方法执行完成、结果为:" + ret);
        
        }
    }
    
    <bean id="startupTimerAspectj" class="com.haredot.aspectj.StartupTimerAspectj" />
    
    <aop:config>
        <aop:aspect ref="startupTimerAspectj">
             <aop:pointcut id="startupTimer" expression="execution(public * com..service..*.*(..)) and this(service)"/>
            
            <aop:after-returning method="afterReturning" pointcut-ref="startupTimer" returning="ret"/>
        </aop:aspect>
    </aop:config>
    
  • 异常抛出增强

    public class StartupTimerAspectj {
        /**
         * 异常抛出增强
         */
        public void afterThrowing(JoinPoint joinPoint, Throwable e) {
            //System.out.println(joinPoint);
            System.out.println("方法产生了异常.........................." + e.getMessage());
        }
    }
    
    <bean id="startupTimerAspectj" class="com.haredot.aspectj.StartupTimerAspectj" />
    
    <aop:config>
        <aop:aspect ref="startupTimerAspectj">
            <aop:after-throwing method="afterThrowing"  pointcut="execution(public * com..service..*.*(..))" throwing="e"/>
        </aop:aspect>
    </aop:config>
    
  • 最终增强

    public class StartupTimerAspectj {
    
        public void after(JoinPoint joinPoint) {
            //System.out.println(joinPoint);
            System.out.println("无论成功 或者失败 均会执行的 代码............................" + joinPoint);
        }
    }
    
    <bean id="startupTimerAspectj" class="com.haredot.aspectj.StartupTimerAspectj" />
    
    <aop:config>
        <aop:pointcut id="pt" expression="execution(public * com..service..*.*(..))"/>
    
        <aop:aspect ref="startupTimerAspectj">
            <aop:after method="after" pointcut-ref="pt" />
        </aop:aspect>
    </aop:config>
    
  • 环绕增强

    public class StartupTimerAspectj {
        public Object around(ProceedingJoinPoint jp)throws Throwable {
    
            try {
                // 前置增强
                System.out.println("around before....");
    
                // 手动调用 目标对象中的方法,并获取方法的结果
                Object obj = jp.proceed() ;
    
                // 在jp.proceed 后面写的代码 叫 后置增强
                System.out.println("around afterReturing....结果是:" + obj);
    
                return obj ;
            } catch (Throwable e) {
                // 异常抛出增强
                System.out.println("around afterThrowing ...." + e.getMessage());
                throw e ;
            } finally {
                // 最终增强
                System.out.println("around after ....");
            }
    
        }
    }
    
    <aop:config>
        <aop:pointcut id="pt" expression="execution(public * com..service..*.*(..))"/>
    
        <aop:aspect ref="startupTimerAspectj">
             <aop:pointcut id="startupTimer"
                           expression="execution(public * com..service..*.*(..)) and this(service)"/>
    
            <aop:around method="around" pointcut-ref="pt" />
        </aop:aspect>
    </aop:config>
    
JointPoint 连接点

是 前置/后置增强/异常抛出增强 所特有的一个参数, 可以通过该参数 获取一些 信息 ,该参数如果需要,在增强方法中必须作为第一个参数出现。

  • getThis() : 获取 本身
  • getTarget() : 获取目标对象
  • getArgs() : 获取 目标方法的参数
  • getSignature() : 获取 目标方法签名信息

切点表达式

将 切面类 和 目标类 进行 绑定 , 需要依赖 aspectjweaver依赖包

切点函数
  • execution : 比较常用的切点函数、可以将切面织入到 目标类的方法上
  • within : 织入到类上
  • args : 织入到 指定方法 对应参数
  • target : 绑定目标对象
  • this : 绑定当前对象
  • @within : 织入到 包含某个注解的类
  • @args : 织入到 包含某个注解的方法参数中
模式

格式: [修饰符] 返回值类型 包名.类名.方法名(参数列表)

  • * : 匹配 0 ~ N 个 字符 (不匹配 .)
  • .. : a) 如果 … 出现在 表示 包的位置,代表 匹配 多级包路径 , b) 如果 … 出现在 参数列表 , 代表 参数个数、类型 任意
  • + : 该通配符 只能出现在表示 类的位置,而且只能下在类的尾部, 表示 切面能够注入匹配的类 及其它的子类

Spring 事务管理

  • 编程式 事务管理 : 通过 编写 代码的 方式 管理 事务,由程序员 自己 进行 提交 、回滚事务 。
  • 声明式 事务管理 : (重点掌握) 通过 配置的手段 进行 事务管理 。默认只能对 运行时异常和 Error 进行 回滚。

声明式事务管理

底层采用的是 AOP 切面 技术 、采用 动态代理 实现 。

  • 添加 和 事务相关的依赖 包 spring-tx , 如果 引入了 spring-orm , 则自动就引入了 spring-tx 包。
  • 配置 事务管理器
    • DataSourceTransactionManager : 基于 JDBC 的事务管理器
    • HibernateTransactionManager : 基于 Hibernate 的事务管理器
    • JpaTransactionManager : 基于 JPA 的事务管理器
    • JtaTransactionManager : 分布式 事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
  • 配置 事务的通知

    <!--  配置 事务的特征
        a) 配置 事务管理器, 如果 Spring中的事务管理器的 名字为 transactionManager, 则可以省略。
        b) 配置 事务的属性
            tx:method 用来 配置 事务 处理的方法
                name="query*" :    所有以query 开头的方法
                propagation= "" :  配置 事务的传播特性, 默认值是 REQUIRED
                isolation = ""  :  配置 事务的 隔离级别 ,默认级别是 数据库的默认级别
     -->
    <tx:advice transaction-manager="transactionManager" id="txAdvice">
        <tx:attributes>
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
    
  • 将 事务通知 和 目标对象 进行绑定

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.haredot.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>
    

事务的传播特性 Propagation

  • REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有事务,则创建一个新的事务。这是最常用的传播特性,适用于大多数情况。
  • REQUIRES_NEW : 创建一个新的事务,并挂起当前的事务(如果存在)
  • SUPPORTS: 如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行
  • NOT_SUPPORTED : 以非事务方式执行方法 , 并挂起当前的事务(如果存在)
  • MANDATORY:要求当前存在事务,并加入该事务;如果没有事务,则抛出异常。
  • NEVER:以非事务方式执行方法;如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则在一个嵌套的事务中执行方法;如果没有事务,则创建一个新的事务。嵌套事务是当前事务的一部分,如果嵌套事务回滚,只会回滚到该事务的保存点,而不会影响到外部事务。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值