1.回顾AOP底层
jdk动态代理
cglib的动态代理
2.AOP的概述
AOP术语
3.AOP的配置
全xml的配置
半xml,半注解
纯注解的配配置
4.使用spring的AOP技术,做转账的事务增强。—声明式事务的底层
一、动态代理:在不改变源代码的情况下对方法增强
- jdk动态代理(基于接口)
-
- 条件: 要有接口
-
- 生成的动态代理对象和被代理对象是兄弟关系
-
- 工具类-Proxy
public class Demo_JDK {
public static void main(String[] args) {
final AccountService accountService = new AccountServiceImpl();
//String value = accountService.save("aaaaa");
//System.out.println(value);
// 在不修改方法源码的基础上,对方法进行增强 ---save()
// 动态代理:JDK提供的动态代理 CGLIB提供的动态代理
// JDK提供的动态代理
// 参数1: 和目标对象一样的类加载器
// 参数2:和目标对象一样的接口
AccountService proxy =(AccountService)
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
new Class[]{AccountService.class},
// 匿名内部类
new InvocationHandler() {
// 增强业务 代理对象调用方法的时候就执行 调用一次执行一次
// 参数1 :代理对象的引用 (谨慎用)
// 参数2 : 目标方法(save)
// 参数3: 目标方法执行过程中需要的参数(aaaa)
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if("save".equals(method.getName())){
try {
// 增强
System.out.println("之前增强。。。");
// 调用原方法
// 参数1:本身应该执行原方法的对象
// 参数2:原方法需要的参数
Object value = method.invoke(
accountService, args); //原来方法
System.out.println(value);
System.out.println("之后增强。。。");
}catch (Exception e){
System.out.println("异常增强...");
}finally {
System.out.println("最终增强....");
}
// invoke是谁调用的就返回给谁内容
return "我知道是你代理对象调用的,我把数据返回给你";
}else
{
// 调用你原来的方法 我不给你增强
method.invoke(accountService, args);
return null;
}
}
});
// 代理对象调用方法
String value=proxy.save("aaaaa"); //只要代理对象调用方法 invoke就会
//执行 invoke执行的是对调用方法(save)的增强
System.out.println(value);
//proxy.delete();
}
}
- cglib的动态代理(基于类)
- 代理对象与对象的 关系-------父子关系
- 条件:
-
- 目标对象的类不需要接口但也不能被final修饰
-
- 导入cglib的jar包—通过工具类-----Enhancer生成的动态代理对象是被代理对象的子类。
public class Demo_CGLIB {
public static void main(final String[] args) {
final AccountServiceImpl2 accountServiceImpl2 = new
AccountServiceImpl2();
//String value = accountServiceImpl2.save("bbbbb");
//System.out.println(value);
// 不改变方法源码的前提下,对 save 进行增强 --动态代理
// 第三方提供的动态代理--cglib动态代理
//cglib动态代理:目标对象不需要接口,也可以做增强
// 工具类--Enhancer
//参数1:目标对象的字节码文件
//参数2:增强业务类 InvocationHandler===MethodInterceptor
AccountServiceImpl2 proxy=(AccountServiceImpl2)Enhancer.create(
accountServiceImpl2.getClass(),
// 匿名内部类
new MethodInterceptor() {
// 参数1 :代理对象的引用 (谨慎用)
// 参数2 : 目标方法(save)
// 参数3: 目标方法执行过程中需要的参数(aaaa)
// 参数4:方法对象的代理对象
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy)
throws Throwable {
if("save".equals(method.getName())){
try {
// 增强
System.out.println("之前增强。。。");
// 调用原方法
// 参数1:本身应该执行原方法的对象
// 参数2:原方法需要的参数
Object value = method.invoke
(accountServiceImpl2, objects); //原来方法
System.out.println(value);
System.out.println("之后增强。。。");
}catch (Exception e){
System.out.println("异常增强...");
}finally {
System.out.println("最终增强....");
}
// invoke是谁调用的就返回给谁内容
return "我知道是你代理对象调用的,我把数据返回给你";
}else
{
// 调用你原来的方法 我不给你增强
method.invoke(accountServiceImpl2, objects);
return null;
}
}
});
String value = proxy.save("bbb");
System.out.println(value);
}
}
- AOP的底层会自动选择(有接口,选jdk)
二、 AOP的概述
2.1 AOP:面向切面编程
- 作用: 在不修改源码的基础上,对方法进行增强
- 底层:jdk动态代理,cglib动态代理(自动抉择)
- 关注点1:自己创建的方法---- AOP做好了事务的方法
- 关注点2:告诉spring需要在哪些方法上使用增强方法-用动态代理
- 一句话:自己写增强方法,然后在配置文件中告诉spring,自己在哪些方法上进行了增强
-
- spring:自动使用动态代理技术实现方法的增强
2.2 AOP的相关术语
- Target(目标对象): 要增强的对象
- Proxy(代理对象):对目标对象的增强封装
- JoinPoint(连接点):目标对象的所有方法
- Advice(通知/增强):增强的那段代码方法
4.1. 一个通知就是一个增强方法
- 前置通知:在切入点(要被增强的方法)之前的增强方法
- 后置通知:在切入点(要被增强的方法)之后的增强方法
- 议程通知:在切入点(要被增强的方法)发生异常执行的增强方法
- 最终通知:在切入点(要被增强的方法)执行完毕的增强方法
- 环绕通知:代替上面四个
- (Aspect)切面 :切入点+通知 = 切面
目标方法和增强方法合成在一起 - Weaving(织入): 将切入点集成到切面的这个过程
底层是用到动态代理
三、AOP的配置
3.1 AOP的xml方式:
- 导入AOP的坐标
- 定义一个类,自己在类中编写增强方法----ioc管理这个切面类。
- 通过配置文件告诉spring使用自己的增强方法在哪些方法上做前置,后置,异常,最终增强。
3.2 基于xml的配置
1. 声明AOP配置
< aop:config>…</aop:config>
- 配置切入点:
- id:切入点的唯一标识
-
- expression:切入点表达式
-
- 完整写法:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))
<aop:pointcut id="pt" expression="execution(* com.itheima..AccountServiceImpl.save(..))">
</aop:pointcut>
<!-- 支持通配符的写法:
* : 标识任意字符串
.. : 任意重复次数
1. 方法的修饰符可以省略:void cn.itcast.service.impl.AccountServiceImpl.saveAccount()
2. 返回值可以使用*号代替:标识任意返回值类型
* cn.itcast.service.impl.AccountServiceImpl.saveAccount()
3. 包名可以使用*号代替,代表任意包(一个包使用一个*)
4. 使用..配置包名,标识此包以及此包下的所有子包
5. 类名可以使用*号代替,标识任意类
6. 方法名可以使用*号代替,表示任意方法
7. 可以使用..配置参数,任意参数
-->
2. 配置切面
- ref:切面类的唯一标识
<aop:aspect ref="logger">
- 配置通知类型:前置,后置,异常,最终,环绕通知
- method:切面类中的方法
- pointcut-ref:切入点唯一标识
<aop:before method="before" pointcut-ref="pt"></aop:before>
//此为前置通知,后置,异常,最终通知类似
- 环绕通知
<aop:around method="around" pointcut-ref="pt"></aop:around>
4. 测试类
@RunWith(value = SpringJUnit4ClassRunner.class)
//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:bean.xml")
//声明spring的配置信息+
public class SpringJunit {
@Autowired //默认按照 类型(接口)从容器中查找对象并注入
//如果该容器中有多个该接口的对象
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
3.3 基于注解结合XML的方式
- 半注解: 自己的资源
- 半XML: 第三方的资源
- 注解:
- IOC的注解
- AOP的注解
- 开启注解们的支持
- IOC包扫描
- 开启对AOP注解的支持
-
-
- 找切面类
-
-
-
- 在切面类的通知上配置切入点表达式
-
3. 注解:
- @Aspect: 声明切面类
- @PointCut: 定义工作的切入点。
-
-
- 配置到空方法上,value:切入点表达式
-
-
-
- 引用:方法名()
-
-
- 配置通知类型:
-
-
- @Before:前置通知
-
-
-
- @AfterReturnint : 后置通知
-
-
-
- @AfterThrowing :异常通知
-
-
-
- @After :最终通知
-
-
-
- @Around :环绕通知
-
- 步骤(在bean.xml中):
- 开启ioc扫描器
<!--开启ioc扫描类-->
<context:component-scan base-package="com.itheima">
</context:component-scan>
- 到Service层
@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
//切入点
public void save() {
System.out.println("save...");
}
}
- 开启AOP注解扫描器
<!--开启AOP扫描类-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
-
- 找切面类
-
- 用注解的方式配置前置,后置异常等.
//切面类---里面都是增强方法(通知)
@Component(value = "myaspect")
@Aspect//声明当前类为切面类
public class MyAspect {
/**
* 使用注解的方式把表达式抽取出来
*
* 要求:
* 1.需要有一个无参无返回值无内容的方法
* 2.在该方法上添加注解@Pointcut
* 3.使用:谁用谁调用方法名
*/
/*
* Pointcut:定义为公共的切入点,配置到空方法上,value:切入点表达式
* 引用:方法名
* */
@Pointcut(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
public void aaa(){
}
//注解的方式前置通知
//@Before(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
// @Before(value = "aaa()")
public void before() {
System.out.println("前置通知。。");
}
//环绕通知.
// @Around(value = "aaa()")
@Around(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) {
try {
//前置通知
System.out.println("前置通知。。");
//原方法执行
proceedingJoinPoint.proceed();//invoke
//后置通知
System.out.println("后置通知...");
} catch (Throwable throwable) {
//异常通知
System.out.println("异常通知....");
} finally {
//最终通知
System.out.println("最终通知");
}
}
}
- 执行顺序:
- 测试层
@RunWith(value = SpringJUnit4ClassRunner.class)
//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:bean.xml")
//声明spring的配置信息+
public class SpringJunit {
@Autowired //默认按照 类型(接口)从容器中查找对象并注入
//如果该容器中有多个该接口的对象
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
- 加载 配置文件,然后按照第4步执行。
3.4 纯注解的方式
- @EnableAspectJAutoProxy : 开启对AOP注解的支持
- 不同点:
- 在测试类中,加载配置类信息。
@RunWith(value = SpringJUnit4ClassRunner.class)
//声明spring提供的单元测试环境
@ContextConfiguration(classes = SpringConfig.class)
//声明spring的配置信息+
public class SpringJunit {
@Autowired //默认按照 类型(接口)从容器中查找对象并注入
//如果该容器中有多个该接口的对象
private AccountService accountService;
@Test
public void t1(){
accountService.save();
}
}
- 用配置类代替配置文件bean.xml
-
- 在配置类中开启ioc,aop扫描
@ComponentScan(basePackages = "com.itheima")//开启ioc扫描类
@EnableAspectJAutoProxy//开启aop扫描类
public class SpringConfig {
}
其他地方与半xml半注解方式一致。