AOP

AOP 面向切面编程

在原有纵向执行流程中添加横切面(不需要修改原有代码)

面向切面编程 特性:
1. 高扩展性
2. 原有代码释放了部分逻辑(横切面会分担一些功能)

面向切面编程:

在程序原有纵向执行流程中,针对某一个/某一些方法添加通知,形成横切面过程 -- 面向切面编程

面向切面编程概念图:

1. 原有功能: 切点(pointcut)
2. 前置通知: 切点之前执行的功能 (before advice)
3. 后置通知: 切点之后执行的功能 (after advice)
4. 异常通知: 切点执行过程中出现异常 (throws advice)
5. 切面: 所有功能总称
6. 织入: 将切面嵌入到原有功能的过程

两种AOP实现方式

1. Schema-based  
    1. 每个通知都需要实现接口/类
    2. 配置spring配置文件在<aop:config>配置

2. AspectJ  
    1. 每个通知不需要实现接口/类
    2. 配置spring配置文件在<aop:config>的子标签<aop:aspect>中配置

使用Schema-based方式

总体实现步骤

1.导入jar包
2.编写demo类(具有几个方法)
3.编写配置文件
    3.1 导入aop的xmls和location
    3.2 编写aop的相关信息(切点 前置和后置通知类的连接)
    3.3 前置通知类/后置通知类/demo类的bean方法
4.编写前置通知类(实现MethodBeforeAdvice接口)
5.编写后置通知类(实现AfterReturningAdvic接口)
6.编写测试类(IOC 控制反转)

具体实现代码

1.导入jar包

2.Demo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {

public void demo1(){
System.out.println("demo1");
}

public void demo2(){
System.out.println("demo2"); //切点是demo02方法
}

public void demo3(){
System.out.println("demo3");
}

}

3.配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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" <!-- 导入aop的xmlns -->
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop <!-- 导入aop的地址 -->
http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="mybefore" class="com.bjsxt.advice.MyBeforeAdvice"></bean> <!-- 前置通知类 -->
<bean id="myafter" class="com.bjsxt.advice.MyAfterAdvice"></bean> <!-- 后置通知类 -->

<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo02())" id="mypoint"/> <!-- 要给aop的pointcut切点编程 execution(* )的格式 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/> <!-- 将前置通知类加到上面的切点 -->
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/> <!-- 将后置通知类加到上面的切点 -->
</aop:config>

<bean id="demo" class="com.bjsxt.test.Demo"></bean> <!-- 找方法的bean 用于test类找 -->

</beans>

4.前置通知类

1
2
3
4
5
6
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("执行前置通知!");
}
}

5.后置通知类

1
2
3
4
5
6
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("执行后置通知!");
}
}

6.测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {

//原来的写法:
//Demo demo=new Demo();
//demo.demo1();
//demo.demo2();
//demo.demo3();

//因为我们是IOC 控制反转 将new和管理对象给了spring
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo",Demo.class); //通过找配置文件中的bean
demo.demo1();
demo.demo2();
demo.demo3();
}

}

总结

执行结果

前置通知实现的接口方法参数

1
2
3
4
5
6
7
8
9
10
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {

//agr0 切点方法对象 Method对象
//agr1 切点方法参数
//agr2 切点在那个对象中

}
}

后置通知实现的接口方法参数

1
2
3
4
5
6
7
8
9
10
11
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {

//agr0 切点方法返回值
//agr1 切点方法对象
//agr2 切点方法参数
//agr3 切点方法所在类的对象

}
}

配置异常通知(AspectJ方式)

只要切点报异常才能触发 异常通知(throws advice)
不实现接口(方法随便写) 然后配置在aop:aspect标签下面

准备类和异常通知方法

1
2
3
4
5
public class MtThrowAdvice {
public void myexception(Exception e) {
System.out.println("异常了!!!"+e.getMessage()); //自己写的报错的方法 输出错误原因
}
}

创建Demo类(切点方法)

1
2
3
4
5
6
7
8
public class Demo {

public void demo1(){
int i=5/0; //这里出异常
System.out.println("demo1");
}

}

更改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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">


<bean id="mythrow" class="com.bjsxt.advice.MtThrowAdvice"></bean> //给写的异常类写bean

<aop:config>
<aop:aspect ref="mythrow"> //相比于schema-based方式它可以自动找是前置还是后置通知 我们的aspectJ需要明确ref跳到哪个异常类
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo01())" id="mypoint"/> //这行主要写切点: 是哪个包哪个类的哪个方法 然后给切点一个id
<aop:after-throwing method="myexception" pointcut-ref="mypoint" throwing="e"/> //异常通知: 方法是刚才那个类的哪个 然后切点是哪个 扔出来的信息是方法的哪个对象
</aop:aspect>
</aop:config>

<bean id="demo" class="com.bjsxt.test.Demo"></bean> //给切点的类写bean

</beans>

添加测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {

//原来的写法:
//Demo demo=new Demo();
//demo.demo1();
//demo.demo2();
//demo.demo3();

//因为我们是IOC 控制反转 将new和管理对象给了spring
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo",Demo.class);
demo.demo1(); //测试方法
}

}

异常通知(Schema-based)

实现接口(方法必须叫afterThrowing) 然后配置在aop:config下面

准备异常类

1. 必须实现throwsAdvice接口
2. 实现的方法有两种 (1个参数 / 4个参数)
3. 异常类型必须要和切点报错的异常一致(一般就写最大的Exception!!!!)
1
2
3
4
5
6
7
8
  public class MyThrow implements ThrowsAdvice {

//第一种
public void afterThrowing(Exception ex) throws Throwable{ //里面用最大的Exception
System.out.println("aaaa");
}

}

准备配置文件(展示细节)

只在aop:config标签里面去写

1
2
3
4
<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo01())" id="mypoint"/>
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"/>
</aop:config>

对比两种方法的区别

1
2
3
4
5
6
7
1.AspectJ:
1.1 不实现接口(显示异常的方法随便取名字)
1.2 配置文件结构 aop:config -- aop:aspect(ref写跳转的异常类位置) -- aop:pointcut(切点) /aop:after-throwing(异常通知)

2.schema-based:
2.1 实现throwsAdvice接口(显示异常的方法必须叫afterThrowing)
2.2 配置文件结构 aop:config -- aop:pointcut(切点) / aop :advisor (前置/后置/异常通知都在这)

环绕通知(Schema-based)

前置和后置通知 –> 一个通知(环绕)

创建类实现MethodInterceptor接口

1
2
3
4
5
6
7
8
9
10
11
public class MyArround implements MethodInterceptor{

@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕-前置");
Object result = arg0.proceed(); //放行 调用切点方式
System.out.println("环绕-后置");
return result;
}

}

配置文件(展示细节)

1
2
3
4
5
6
7
8
<bean id="myarround" class="com.bjsxt.advice.MyArround"></bean>

<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo01())" id="mypoint"/> //配置切点
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint" /> //配置环绕的信息
</aop:config>

<bean id="demo" class="com.bjsxt.test.Demo"></bean>

环绕通知(AspectJ)

创建MyAdvice类(不实现接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public class MyAdvice {
public void mybefore(String name1,int age1) {
System.out.println("前置"+name1+" "+age1);
}

public void myafter1() {
System.out.println("后置1");
}

public void myafter2() {
System.out.println("后置2");
}

public void mythrow() {
System.out.println("异常");
}

public Object myarround(ProceedingJoinPoint p) throws Throwable { //环绕的类 ProceedingJoinPoint就是需要的类
System.out.println("执行环绕");
System.out.println("环绕-前置");
Object result = p.proceed(); //和另外一种方法一样的执行
System.out.println("环绕-后置");
return result;
}

}

创建Demo类(切点方法)

1
2
3
4
5
6
7
8
public class Demo {

public void demo1(String name,int age){ //现在切点传入的是name 和 age (后面会在配置和自己写的方法内的属性不同!!!)
//int i=5/0;
System.out.println("demo1"+name+" "+age);
}

}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<bean id="myarround" class="com.bjsxt.advice.MyArround"></bean>
<bean id="myadvice" class="com.bjsxt.advice.MyAdvice"></bean> //自己写的环绕的类

<aop:config>
<aop:aspect ref="myadvice"> //弹到我们写的类(环绕 前置 后置等方法)
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo01(String,int)) and args(name1,age1)" id="mypoint"/> //切点信息
<aop:before method="mybefore" pointcut-ref="mypoint" arg-names="name1,age1" /> //前置通知信息

//有两种:
1.after 异常也可以执行
2.after-returning 异常就不会执行

<aop:after method="myafter1" pointcut-ref="mypoint"/>
<aop:after-returning method="myafter2" pointcut-ref="mypoint"/>

<aop:after-throwing method="mythrow" pointcut-ref="mypoint"/> //异常通知信息
<aop:around method="myarround" pointcut-ref="mypoint"/> //环绕的具体标签 aop:around
</aop:aspect>
</aop:config>

<bean id="demo" class="com.bjsxt.test.Demo"></bean> //Demo类信息

测试Test类

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
//因为我们是IOC 控制反转 将new和管理对象给了spring
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo",Demo.class); //通过bean去找
demo.demo1("zhangsan",123); //传入参数执行方法
}
}

关于配置文件中的细节

1. 关于后置的标签(执行顺序和配置顺序有关):
    1.1 after 异常也可以执行   
    1.2 after-returning 异常就不会执行

2. 关于前置标签的属性和使用问题
        <aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo01(String,int)) and args(name1,age1)" id="mypoint"/>
        <aop:before method="mybefore" pointcut-ref="mypoint" arg-names="name1,age1" />
    注:     2.1 execution(* com.bjsxt.test.Demo.demo01()) 在后面加 and args()
         2.2 args()有几个参数 下面的arg-names就要有几个参数
         2.3 arg-names="" 里面的参数名称必须和通知方法参数名对应(和你写的前置方法的参数一样!!)

使用注解(基于Aspect)

告诉spring哪些类下的包有注解(默认不会自动找)

配置文件(引入xmlns:context)

1
2
3
4
5
6
7
8
9
10
<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"
xmlns:context="http://www.springframework.org/schema/context" // 1. 引入这一行 xmlns:context
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
http://www.springframework.org/schema/context // 2. 引入相关的location地址
http://www.springframework.org/schema/context/spring-context.xsd">

@Component

1. 相当于bean标签(引入相关类)
2. 相当于bean id=""  @Component(没有参数) -- 将类名首字母变小写(Demo -- demo)  
3. @Component(可以随意自定义名称)

实现步骤

配置文件注解在哪些包

1
2
//多个包就用逗号隔开
<context:component-scan base-package="com.bjsxt.advice,com.bjsxt.test"></context:component-scan>

配置Demo切片类

1
2
3
4
5
6
7
@Component   //加这个注解 表明现在是给切片类写了id="demo"
public class Demo {
@Pointcut("execution(* com.bjsxt.test.Demo.demo01())") //demo01是切片
public void demo1(){
System.out.println("demo01");
}
}

配置前置/后置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component  //要引入Demo切片类
@Aspect //告诉我是一个切片类
public class MyAdvice {

@Before("com.bjsxt.test.Demo.demo01()") //方法前面要用是哪个切片
public void mybefore() {
System.out.println("前置");
}

@After("com.bjsxt.test.Demo.demo01()")
public void myafter() {
System.out.println("后置");
}

}

配置文件更改代理

1
2
3
4
5
6
7

<!--
true 使用cglib动态代理(注解)
false 使用jdk动态代理
-->

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. AOP 面向切面编程
    1. 1.1. 两种AOP实现方式
  2. 2. 使用Schema-based方式
    1. 2.1. 总体实现步骤
    2. 2.2. 具体实现代码
    3. 2.3. 总结
      1. 2.3.1. 执行结果
      2. 2.3.2. 前置通知实现的接口方法参数
      3. 2.3.3. 后置通知实现的接口方法参数
  3. 3. 配置异常通知(AspectJ方式)
    1. 3.1. 准备类和异常通知方法
    2. 3.2. 创建Demo类(切点方法)
    3. 3.3. 更改配置文件
    4. 3.4. 添加测试类
  4. 4. 异常通知(Schema-based)
    1. 4.1. 准备异常类
    2. 4.2. 准备配置文件(展示细节)
    3. 4.3. 对比两种方法的区别
  5. 5. 环绕通知(Schema-based)
    1. 5.1. 创建类实现MethodInterceptor接口
    2. 5.2. 配置文件(展示细节)
  6. 6. 环绕通知(AspectJ)
    1. 6.1. 创建MyAdvice类(不实现接口)
    2. 6.2. 创建Demo类(切点方法)
    3. 6.3. 配置文件
    4. 6.4. 测试Test类
    5. 6.5. 关于配置文件中的细节
  7. 7. 使用注解(基于Aspect)
    1. 7.1. 配置文件(引入xmlns:context)
    2. 7.2. @Component
    3. 7.3. 实现步骤
      1. 7.3.1. 配置文件注解在哪些包
      2. 7.3.2. 配置Demo切片类
      3. 7.3.3. 配置前置/后置类
      4. 7.3.4. 配置文件更改代理
,