AOP 底层是基于动态代理思想实现的 可以把核心业务业务逻辑看作是一个圆增强的业务逻辑也看成是一个圆,核心业务逻辑在增强的业务逻辑的中央,这样就形成了一个同心圆(切面), 这种编程思想的好处是 无侵入 即 不会对源码进行修改。以一个为核心业务逻辑增加日志的例子来逐步引出动态代理和AOP面向切面编程思想。
最原始的方式是利用代理类的方式为核心业务逻辑增添打印日志的功能,这就要求被代理类和增强类要同时实现一个接口来保证方法的一致性,但是还需要为每一个代理类都创建一个对象,当增强的方法都相同时就造成了代码的重复编写,造成了开发效率的降低。下面这种是修改源码的方式。
目前代码存在两个问题
代码耦合性高:业务代码和日志代码耦合在了一起
代码复用性低:日志代码在每个方法都要书写一遍
为了解决基本代理方式的不足,产生了JDK动态代理的方式,但是这样的方式还有一个不足,就是只能对接口的实现类进行代理,在此背景下,Cglib动态代理又出现了,Cglib动态代理可以为类动态创建代理类。
PS: 在创建代理对象时 jdk的速度要高于cglib
所以选择的时候:
当被代理类有接口的时候,使用jdk动态代理
当被代理类没有接口的时候,使用cglib动态代理
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
@Autowired
private DeptService deptService;
@Autowired
private Logger logger;
@Test
public void test1() {
InvocationHandler myHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.m1();
try {
method.invoke(deptService,args);
} catch (Exception e) {
logger.m3();
}
logger.m2();
return null;
}
};
DeptService deptServiceproxy = (DeptService) Proxy.newProxyInstance(
deptService.getClass().getClassLoader(),
new Class[]{DeptService.class},
myHandler
);
deptServiceproxy.save(null);
deptServiceproxy.findAll();
deptServiceproxy.findById(null);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
@Autowired
private DeptServiceImpl deptService;
@Autowired
private Logger logger;
@Test
public void test01() {
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.m1();
try {
method.invoke(deptService, args);
} catch (Exception e) {
logger.m3();
}
logger.m2();
return null;
}
};
DeptServiceImpl deptServiceProxy = (DeptServiceImpl) Enhancer.create(DeptServiceImpl.class, invocationHandler);
//注意: Enhancer有个硬伤,就是无法对已经封装过的targetclass,再继续封装多一次,无法实现多个interceptor的链式调用。
deptServiceProxy.findAll();
System.out.println("=============================");
deptServiceProxy.save(null);
System.out.println("=============================");
deptServiceProxy.findById(null);
}
}
切面类要使用两个注解 @Component 和 @Aspect 声明该类为一个切面类,并且将该切面类加入到Spring容器中,在切面类中可以定义切点 @ PointCut()和通知 (5种),下面会展开详细叙述
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@Around
@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回结果 Object result = pjp.proceed();
不同切面类中,默认按照切面类的全类名字母排序:
目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠前的后执行
相当于一个嵌套的效果一样,类比Filter中的过滤器链的执行顺序,也是按照全类名字母靠前的先执行
切入点表达式:描述切入点方法@PointCut()的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
execution(……):根据方法的签名来匹配,常与通配符(* 和 …)搭配使用 ,…用在参数表示0或n个参数 *模糊匹配
使用切入点表达式 需要指定 权限修饰符 返回值类型 方法名的全名(带全类名) 参数 ,因此excution常与通配符搭配用来定义方法名有规律的切入点
@annotation(……) :根据注解匹配,使用注解方式,需要自定义注解,常用来定义方法名无规律的切入点pointcut,自定义注解,如下所示:@annotation(注解全类名)
在切点配置自定义注解,用于识别对那些方法进行增强
配置切点注解
在Spring容器中使用事务时还需要在Spring配置类中配置@EnableTransactionMangement注解和@Bean配置事务管理器
位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
注解用在方法上,表示为方法开启事务,用在类上表示为类中所有方法开启事务,用在接口上表示为接口的所有实现类开启事务
默认情况下,只有出现 RuntimeException 才回滚异常,rollbackFor属性用于控制让非运行时异常也回滚。
因此,需要为 @Transactional的rollback属性指定异常类类型让其对所有异常都进行回滚
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。通俗点说就是:一个有事务注解修饰的方法在被另一个开启了事务的方法调用时,该方法的事务怎么办的问题。默认值是required
2中比较常用的是 required (默认) 有事务就加入,无事务就创建新的 和 requires_new 事务不影响
@Component
@Aspect
@PointCut
@annotation
@Before
@AfterReturning
@AfterThrowing
@After
@Around
@Oredr
@EnableTransactionMangement
@Transactional