aop:面向切面编程,跟上篇的IOC一样它也不是一种技术而是一种思想,解决:在不破坏源代码的情况下,实现对业务方法的增强.可以减少重复代码,提高代码重用性,让我们开发者只关心核心业务逻辑的代码
常见的应用场景:
aop思想底层实现技术:JDK、CGLIB
根据是否有接口选择使用其中一种技术.
相关术语
* target:目标对象
* proxy:代理对象
* joinPoint:连接点,目标对象的所有方法
* pointcut:切点,需要被增强的方法
* advice:通知 (增强的方法)
* aspect:切面, 切点+通知(声明对那些方法在什么时候执行那些操作)
* weaving:织入,动词,将通知织入到切点的过程
需求:在执行UserService中save方法之前,执行通知类中的beforeBeginTransaction方法
步骤分析:
代码实现:
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
UserServiceImpl:
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("执行了save方法");
}
@Override
public void findAll() {
System.out.println("执行了findAll方法");
}
}
TransactionManager:
//通知类
public class TransactionManager {
public void beforeBeginTransaction(){
System.out.println("开启事务");
}
}
applicationContext.xml:
格式为:
<aop:config>
<aop:aspect ref="指向通知类">
<aop:通知类型 method="通知类中方法名" pointcut="切入点表达式"/>
...
</aop:aspect>
</aop:config>
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置service-->
<bean id="userService" class="com.llz.service.impl.UserServiceImpl"/>
<!--配置通知类-->
<bean id="transactionManager" class="com.llz.aop.TransactionManager"/>
<!--aop配置-->
<aop:config>
<!--配置切面 在切入点执行的什么时候执行通知类中的那个方法-->
<aop:aspect ref="transactionManager">
<!-- 在save方法执行之前执行通知类中的beforeBeginTransaction方法-->
<aop:before method="beforeBeginTransaction" pointcut="execution(public void com.llz.service.impl.UserServiceImpl.save())"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
@ContextConfiguration("classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestAOP {
@Autowired
UserService userService;
@Test
public void testHello(){
userService.findAll();
System.out.println("-----");
userService.save();
}
}
作用:定位要增强的方法
execution([修饰符] 返回值 包名.类名.方法名(参数类型列表))
例:
execution(void com.llz.service.impl.AccountServiceImpl.transfer(String,String,int))
execution(public void com.llz.service.impl.UserServiceImpl.save())
提示: 表达式中可以使用 * 和 …
- :匹配任意字符
… :出现在包中和方法中
包: 表示当前包及其子包
方法:表示参数任意
例如:
before : 前置通知,在切点执行之前 执行
after-returning : 后置通知,在切点执行成功之后 执行
after-throwing : 异常通知,在切点执行出现异常之后 执行
after : 最终通知,在切点执行之后,无论成功与否,都会 执行
around : 环绕通知,以上通知的任意组合
note:
1.后置通知和异常通知不会同时出现。
2.开发中一般使用环绕通知,若添加通知类型超过了一个,请使用环绕通知。且一定注意环绕通知一定需要加上返回值一般用*代替。
通知切面类中方法:
ProceedingJoinPoint 正在执行的切入点(方法)
//通知类
public class TransactionManager {
public void beforeBeginTransaction(){
System.out.println("开启事务");
}
public void afterReturningCommit(){
System.out.println("提交事务");
}
public void afterThrowingRollbak(){
System.out.println("回滚事务");
}
public void afterRelease(){
System.out.println("释放资源");
}
//ProceedingJoinPoint 正在执行的切入点(方法)
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
try {
System.out.println("开启事务");
//目标方法执行
pjp.proceed();
System.out.println("提交事务");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("回滚事务");
throw e;
} finally {
System.out.println("释放资源");
}
}
}
applicationContext.xml:
<!--aop配置-->
<aop:config>
<!--配置切面 在切入点执行的什么时候执行通知类中的那个方法-->
<aop:aspect ref="transactionManager">
<aop:before method="beforeBeginTransaction" pointcut="execution(public void com.llz.service.impl.UserServiceImpl.save())"/>
<aop:after-returning method="afterReturningCommit" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
<aop:after-throwing method="afterThrowingRollbak" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
<aop:after method="afterRelease" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
</aop:aspect>
<!--环绕通知-->
<aop:aspect ref="transactionManager">
<aop:around method="aroundMethod" pointcut="execution(* com.llz.service.impl.*.find*(..))"/>
</aop:aspect>
</aop:config>
需求:在执行某个service中save方法的时候,添加环绕通知
步骤分析:
<!--组件扫描-->
<context:component-scan base-package="cn.it"/>
<!--aop注解支持-->
<aop:aspectj-autoproxy/>
@Before
@AfterReturning
@AfterThrowing
@After
@Around
注意:aop的注解有个小bug,使用aop注解的时候务必使用环绕通知即可.
步骤分析及代码实现:
/**
* 在切面中声明一个方法,一般用public修饰,返回值为void,无参数
* 使用@Pointcut 声明 切入点表达式
* 在通知注解上使用 方法名() 来引用切入点表达式即可
*/
@Pointcut("execution(* cn.it.service.impl.*.find*(..))")
public void pc_find(){}
@Before("pc_find()")
public void beforeBeginTransaction(){
System.out.println("开启事务");
}
@AfterReturning("pc_find()")
public void afterReturningCommit(){
System.out.println("提交事务");
}
编写一个spring配置类,替代beans.xml
因为springAOP底层逻辑是动态代理,接着聊聊动态代理。我之前的文章也写过JDK的动态代理,有兴趣可以翻看之前的文章。
public class TestJDKProxy {
@Test
public void test1(){
//创建被代理对象(目标对象)
UserService userService = new UserServiceImpl();
//使用jdk动态代理创建代理对象
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
TestJDKProxy.class.getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//只需要对save方法进行增强,
if ("save".equals(method.getName())) {
Object result = null;
try {
System.out.println("开启事务");
//目标方法执行
result = method.invoke(userService, args);
System.out.println("提交事务");
} catch (Exception e) {
e.printStackTrace();
System.out.println("回滚事务");
} finally {
System.out.println("释放资源");
}
return result;
}
//其他方法执行原来的操作
return method.invoke(userService, args);
}
}
);
userServiceProxy.findAll();
System.out.println("------");
userServiceProxy.save();
}
}
使用的时候就需要导入cglib的jar包(spring内置了)
public class TestCGLIB {
@Test
public void testProxy(){
//创建被代理对象
OrderService orderService = new OrderService();
//使用CGLIB技术创建service的代理对象
OrderService orderServiceProxy = (OrderService) Enhancer.create(OrderService.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//只需要对save方法进行增强,
if ("save".equals(method.getName())) {
Object result = null;
try {
System.out.println("开启事务");
//目标方法执行
result = method.invoke(orderService, args);
System.out.println("提交事务");
} catch (Exception e) {
e.printStackTrace();
System.out.println("回滚事务");
} finally {
System.out.println("释放资源");
}
return result;
}
//其他方法执行原来的操作
return method.invoke(orderService, args);
}
});
orderServiceProxy.findAll();
System.out.println("----");
orderServiceProxy.save();
}
}