面向切面编程(AOP)
作用: 在程序运行期间,不修改源码对已有方法进行增强
优势: 减少重复代码、提高开发效率、维护方便
AOP 的实现方式:使用动态代理技术
Spring 中 AOP 的相关术语
1. JoinPoint 连接点:指那些被拦截到的点(方法)
2. pointcut 切入点:指那些被增强的连接点(方法)
注:所有的切入点都是连接点,但连接点不一定是切入点
3. Advice 通知:增强的方法
4. Aspect 切面:由切入点和通知组成
5. Weaving 织入:指把增强应用到目标对象,创建新的代理对象的过程
AOP的 xml 配置
1. Spring 中基于 xml 的 AOP 配置步骤
1. 把通知 bean 交给 Spring 来管理
2. 使用 aop:config 标签来表明开始 AOP 的配置
3. 在 aop:config 标签内,使用 aop:aspect 标签配置切面
id 属性:切面的唯一标识
ref 属性:指定通知类的 bean 对象
4. 配置切入点表达式(aop:pointcut)
id 属性用于指定表达式的唯一标识,expression 属性用于指定表达式内容
此标签写在 aop:aspect 标签内部只能当前切面使用,在其外部则所有切面均可用
5. 在 aop:aspect 标签的内部使用对应标签配置通知的类型
aop:before 标识前置通知
method 属性:用于指定类中哪个方法是前置通知
pointcut 属性:用于指定切入点表达式,即对哪些方法增强
2. Spring 常用通知类型
Spring 常用通知类型
前置通知(aop:before):在切入点方法执行之前执行
后置通知(aop:after-returning):在切入点方法正常执行之后执行,它和异常通知只能执行一个
异常通知(aop:after-throwing):在切入点方法执行产生异常之后执行,它和后置通知只能执行一个
最终通知(aop:after):无论切入点方法是否正常执行,它都会在其后面执行
环绕通知:用户自定义增强代码的位置
3. 切入点表达式的书写规则
切入点表达式的写法:
关键字:execution (表达式)
表达式的标准写法:访问修饰符 + 返回值 + 包名.类名.方法名(参数列表)
举例:public void com.demo.service.impl.IAccountServiceImpl.saveAccount()
全通配写法:* * ..*.*(..)
全通配的书写规则:
1. 访问修饰符可以省略
2. 返回值可以使用通配符,表示任意返回值
3. 包名可以使用通配符,表示任意包,但是有几级包就需要写几个 *.
4. 包名可以使用 .. 表示当前包和子包
5. 类名和方法名都可以使用 * 来实现通配
6. 参数列表:
6.1 可以直接使写数据类型:
基本类型直接写名称(如 int )
引用类型写包名.类名的方式 (如 java.lang.String )
6.2 可以使用通配符 * 表示任意类型,但是方法必须有参数
6.3 可以使用 .. 表示有无参数即可,有参数可以是任意类型
实际开发中切入点表达式的通常写法:
业务层类下的所有方法:* com.demo.service.impl.*.*(..)
4. AOP 的 xml 配置示例
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut id="pt1" expression="execution(* com.demo.service.impl.*.*(..))"></aop:pointcut>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!-- 配置环绕通知:环绕通知需要用户在代码中定义增强的内容 -->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
JdbcTemplate 简介
jdbcTemplate 是用来操作关系型数据库的模板,位于 spring-jdbc-5.0.2.RELEASE.jar 中,一般还需导入 spring-tx-5.0.2.RELEASE.jar 来进行事务管理。
使用 jdbc 模板时需要进行的配置
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源:Spring 自带数据源 DriverManagerDataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
事务控制
Spring 框架为我们提供了一组事务控制的接口,这组接口在 spring-tx-5.0.2.RELEASE.jar中。PlatformTransactionManager 就是 Spring 提供的事务管理器的接口,该接口提供了我们常用的操作事务的方法,它的实现类是 org.springframework.jdbc.datasource.DataSourceTransactionManager
---------------------------------------事物属性-------------------------------------
1. 隔离级别(isolation):默认是数据库的隔离级别
2. 传播行为(propagation):介绍两种经常使用的传播行为: REQUIRED 和 SUPPORTS
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
3. 回滚规则:
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚;产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
4. 事务超时(timeout):
int getTimeOut() 默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
5. 是否只读(read-only):是否是只读事务。默认值 false,读写型。
基于 xml 的声明式事务控制的基本案例
1. 转账的 dao 层接口
public interface IAccountDao {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
2. 转账的 dao 层实现类
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()) {
return null;
}
if(accounts.size() > 1) {
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
3. 转账的 service 层接口
public interface IAccountService {
Account findAccountById(Integer accountId);
void transfer(String sourceName, String targetName, Float Money);
}
4. 转账的 service 层实现类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
5. account 账户的实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
6. 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
7. bean.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
<!-- 配置 service -->
<bean id="accountService" class="com.demo.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.demo.dao.impl.AccountDaoImpl">
<!-- 注入 dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver">
</property> <property name="url" value="jdbc:mysql://localhost:3306/eesy">
</property> <property name="username" value="root">
</property> <property name="password" value="1234"></property>
</bean>
<!-- 配置一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入 DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务的配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性 -->
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 配置 aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* com.demo.service.impl.*.*(..))" id="pt1"/>
<!-- 建立事务的通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
8. pom 文件
<!--添加依赖-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-text</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>