AOP核心概念
- (Aspect Oriented Programming)面向切面编程,在不改原有代码的前提下对其进行增强 — 代理模式
- 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
- 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
- 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
- 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
- 切面(Aspect):描述通知与切入点的对应关系
- 目标对象(Target):被代理的原始对象成为目标对象
- 通知类:定义通知的类
入门案例
环境准备
- 创建项目
- 添加spring依赖
- 添加Dao&DaoImpl类
- spring的配置类
- 编写app运行类
新增依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
|
定义通知类和通知 —> 定义切入点 —> 制作切面 —> 将通知类配给容器并标识其为切面类
@Component @Aspect
public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){}
@Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
|
开启注解格式AOP功能
@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }
|
运行程序
public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); **bookDao.update();** } }
|
AOP工作流程
(好像不重要)
Spring容器启动
- 加载bean
- 需要被增强的类,如:BookServiceImpl
- 通知类,如:MyAdvice
读取所有切面配置中的切入点
初始化bean
* 匹配失败,创建原始对象
* 匹配成功,创建原始对象(目标对象)的代理对象
获取bean执行方法
AOP配置管理
切入点表达式
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
|
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
|
- 书写技巧
1. 按标准规范开发
2. 查询操作的返回值建议使用匹配
3. 减少使用..的形式描述包
4. 对接口进行描述,使用表示模块名,例如UserService的匹配描述为Service
5. 方法名书写保留动词,例如get,使用表示名词,例如getById匹配描述为getBy*
6. 参数根据实际情况灵活调整
AOP通知类型
前置通知
`@Before("pt()")`
后置通知
`@After("pt()")`
环绕通知(重点)
`@Around("pt()")`
环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
环绕通知可以隔离原始方法的调用执行
环绕通知返回值设置为Object类型
环绕通知中可以对原始方法调用过程中出现的异常进行处理
返回后通知(了解) @AfterReturning("pt2()")
抛出异常后通知(了解) @AfterThrowing
业务层接口执行效率
AOP通知获取数据
AOP事务管理
- 在数据层或业务层保障一系列的数据库操作同成功同失败
转账案例-需求分析
环境搭建
- 准备数据库
create database spring_db character set utf8; use spring_db; create table tbl_account( id int primary key auto_increment, name varchar(35), money double ); insert into tbl_account values(1,'Tom',1000); insert into tbl_account values(2,'Jerry',1000);
|
- 创建项目导入jar包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency>
</dependencies>
|
- 根据表创建模型类
public class Account implements Serializable {
private Integer id; private String name; private Double money; }
|
- 创建Dao接口
public interface AccountDao {
@Update("update tbl_account set money = money + #{money} where name = #{name}") void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tbl_account set money = money - #{money} where name = #{name}") void outMoney(@Param("name") String name, @Param("money") Double money); }
|
- 创建Service接口和实现类
public interface AccountService {
public void transfer(String out,String in ,Double money) ; }
@Service public class AccountServiceImpl implements AccountService {
@Autowired private AccountDao accountDao;
public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); accountDao.inMoney(in,money); } }
|
- 添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql: jdbc.username=root jdbc.password=root
|
- 创建JdbcConfig配置类
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password;
@Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
|
- 创建MybatisConfig配置类
public class MybatisConfig {
@Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.itheima.domain"); ssfb.setDataSource(dataSource); return ssfb; }
@Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; } }
|
- 创建SpringConfig配置类
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
|
- 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest {
@Autowired private AccountService accountService;
@Test public void testTransfer() throws IOException { accountService.transfer("Tom","Jerry",100D); } }
|
最终项目结构如上
实现事务管理具体步骤
需要被事务管理的方法上添加注解@Transactional
public interface AccountService { public void transfer(String out,String in ,Double money) ; }
@Service public class AccountServiceImpl implements AccountService {
@Autowired private AccountDao accountDao; **@Transactional** public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); } }
|
在JdbcConfig类中配置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
|
在SpringConfig的配置类中开启事务注解 @EnableTransactionManagement
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class})
@EnableTransactionManagement public class SpringConfig { }
|
运行测试类
事务配置
转账业务追加日志
创建日志表
create table tbl_log( id int primary key auto_increment, info varchar(255), createDate datetime )
|
添加LogDao接口
public interface LogDao { @Insert("insert into tbl_log (info,createDate) values(#{info},now())") void log(String info); }
|
添加LogService接口与实现类
public interface LogService { void log(String out, String in, Double money); } @Service public class LogServiceImpl implements LogService {
@Autowired private LogDao logDao; @Transactional public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } }
|
在转账的业务中添加记录日志
public interface AccountService {
public void transfer(String out,String in ,Double money)throws IOException ; } @Service public class AccountServiceImpl implements AccountService {
@Autowired private AccountDao accountDao; @Autowired private LogService logService; @Transactional public void transfer(String out,String in ,Double money) { try{ accountDao.outMoney(out,money); accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } }
}
|
事务传播行为
- 事务协调员对事务管理员所携带事务的处理态度
- 转账失败后,所有的事务都回滚,导致日志没有记录下来 — 让log方法单独是一个事务
- 修改logService改变事务的传播行为
@Service public class LogServiceImpl implements LogService {
@Autowired private LogDao logDao; @Transactional(propagation = Propagation.REQUIRES_NEW) public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } }
|