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-250 | Spring官方提供的 | 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-aop
和aspectjweaver
依赖包
-
前置增强
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:如果当前存在事务,则在一个嵌套的事务中执行方法;如果没有事务,则创建一个新的事务。嵌套事务是当前事务的一部分,如果嵌套事务回滚,只会回滚到该事务的保存点,而不会影响到外部事务。