假如有这样一个场景,我们业务的核心代码和一些日志相关的非核心代码都在一个类里面,会显得非常冗余
因此我们可以通过代理模式来解决附加功能代码对核心代码的干扰和不方便维护的问题
代理模式包括静态代理和动态代理(jdk,dglib)
但是需要自己编写代理工厂等,工作比较繁琐
而我们可以使用 Spring AOP框架来简化动态代理的实现
AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP,可以在不修改原来代码的基础上添加新功能。
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
@Configuration
@ComponentScan("com.triticale")
@EnableAspectJAutoProxy //开启aspectj的注解 同等与
public class JavaConfig {
}
/**
* 1.定义方法-增强代码
* 2.使用注解指定对应的位置
* 3.配置切点表达式选中方法
* 4.切面和ioc的配置
* 5.开启aspectj注解的支持
*
* TODO:增强方法中获取目标方法信息
* 1.全部增强方法中,获取目标方法的信息(方法名,参数,访问修饰符,所属的类的信息)
* 直接在形参列表里面添加JoinPoint对象
* 2.返回的结果
* 形参里面(Object result)
* 在注解里面加上 returning = "result"
* 3.异常的信息
* 形参里面(Throwable throwable)
* 在注解里面加上 throwing = "throwable"
*/
@Component //保证切面类能够放入IOC容器
@Aspect //表示这个类是一个切面类
public class MyAdvice {
@Before("execution(* com.triticale.*.*(..))")
public void start(JoinPoint joinPoint){
//获取方法属于类的信息
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
//获取方法名称
String name = joinPoint.getSignature().getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
}
@AfterReturning(value = "execution(* com.triticale.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
}
@After("execution(* com.triticale.*.*(..))")
public void after(JoinPoint joinPoint){
}
@AfterThrowing(value = "execution(* com.triticale.*.*(..))",throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint,Throwable throwable){
}
}
/**
* 使用注解配置,指定插入目标方法的位置
* 前置 @Before
* 后置 @AfterReturning
* 异常 @AfterThrowing
* 最后 @After
* 环绕 @Around
*/
测试类
@SpringJUnitConfig(value = JavaConfig.class)
public class SpringAopTest {
@Autowired
//aop代码 -如果实现了接口,必须用接口对象才行
private Calculator calculatorPure;
@Test
public void test(){
int add = calculatorPure.add(1, 1);
System.out.println(add);
}
}
固定语法 execution(1 2 3.4.5(6))
1、访问修饰符
public / private
2、方法的返回参数类型
String int void …
如果不考虑访问修饰符和返回值,这两位合成一起写 *
如果要是不考虑,必须是两个都不考虑!不能出现 * String这种
3、包的位置
具体包如 com.triticale.service.impl
单层模糊:com.triticale.service.*
多层模糊:com…impl
4、类的名称
具体:CalculatorPureImpl
模糊:*
部分模糊:*Impl
5、方法名 语法和类名一致
6、形参列表
没有参数()
有具体参数 (String) (String,int)
模糊参数(…)
部分模糊(String…) ,String后面有没有无所谓
(…int),最后一个参数是int
(String…int)
在同一类内部引用
提取:
// 切入点表达式重用
@Pointcut("execution(public int com.atguigu.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}
引用:
@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {
在不同类中引用
不同类在引用切点,只需要添加类的全限定符+方法名即可!
@Before(value = "com.atguigu.spring.aop.aspect.LogAspect.declarPointCut()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {
@Aspect
@Component
public class TxAroundAdvice {
@Pointcut("execution(* com.triticale.*.*(..))")
public void pc(){
}
@Around("pc()")
public Object transaction(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("开启事务");
Object proceed = joinPoint.proceed(args);
System.out.println("结束事务");
} catch (Throwable e) {
System.out.println("事务回滚");
throw new RuntimeException(e);
}
return result;
}
}
使用 @Order 注解可以控制切面的优先级:
优先级越高的前置先执行,后置后执行
一个接口的唯一实现类应用了切面后,真正放在IoC容器中的是代理类的对象
目标类并没有被放到IoC容器中,所以根据目标类的类型从IoC容器中是找不到的,根据接口可以找到
如果一个类没有接口,应用切面后可以根据该类的id获取bean,因为aop底层应用了cglib技术