一、切面的说明

切面就是允许在某个方法执行之前、执行之后、return后、抛异常后等等…执行一些操作,例如日志记录等等。。用下面的一段代码说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public int add(int i, int y) {
//在执行某个function前,可能要执行某些操作...比如
System.out.println("---执行add()前干的事----"); // <-- 切面干的事

int sum = i+y;

//执行某个function后,可能也要执行某些操作...比如
System.out.println("----执行add()后干的事----");// <-- 切面干的事

return sum;
//返回值后,可能又要执行某些后续操作....
//...
}
}

二、切面的实现方式

首先模拟一个dao类

接口类

UserDao.java
1
2
3
public interface UserDao {
public boolean login(String username, String password);
}

实现类

UserDaoImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao {
@Override
public boolean login(String username, String password) {
if ("crazykid".equals(username) && "123456".equals(password)) {
//为了看出切面的作用,直接在dao里输出一句话
System.out.println("登录成功! AQA");
return true;
}
System.out.println("登录失败! QAQ");
return false;
}
}

2.1 通过 jdk 动态代理 (jdkProxy) 实现

编写代理类

UserDaoProxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class jdkProxy implements InvocationHandler {
private Object target; //被代理的对象

public Object createProxy(Object target) {
this.target = target;
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return proxy;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
// 写日志、权限处理、事务处理...等
System.out.println(target.getClass().getName() + " 的 " + method.getName() + " 将被调用...");
// 调用目标对象对应的方法,返回值是目标方法的返回值
Object result = method.invoke(target, args);
System.out.println(target.getClass().getName() + " 的 " + method.getName() + " 调用完毕,目标方法返回值是:" + result);
return result;
}
}

编写测试方法

1
2
3
4
5
6
@Test
public void test01 () {
UserDao userDao = (UserDao) new jdkProxy().createProxy(new UserDaoImpl());
userDao.login("crazykid","123456");
System.out.println("登录dao执行完了..");
}

执行结果

net.crazykid.bean.aop.dao.UserDaoImpl 的 login 将被调用…
登录成功
net.crazykid.bean.aop.dao.UserDaoImpl 的 login 调用完毕,目标方法返回值是:true
登录dao执行完了..

2.2 通过cglib实现

导入cglib的包

pom.xml
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

编写代理类

CglibProxy.java
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
28
29
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();

public Object createProxy(Class clazz) {
// 生成指定类对象的子类,也就是重写类中的业务函数,在重写中加入intercept()函数而已。
enhancer.setSuperclass(clazz);
// 这里是回调函数,enhancer中肯定有个MethodInterceptor属性。
// 回调函数是在setSuperclass中的那些重写的方法中调用
enhancer.setCallback(this);
// 创建这个子类对象
return enhancer.create();
}


@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(method.getName() + "执行方法之前做一些内容...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("执行方法返回的结果:" + result);
System.out.println(method.getName() + "执行方法之后做一些内容...");
return result;
}
}

编写测试方法

1
2
3
4
5
6
@Test
public void test02 () {
UserDao userDao = (UserDao) new CglibProxy().createProxy(UserDaoImpl.class);
userDao.login("crazykid","123456");
System.out.println("登录dao执行完了..");
}

执行结果:

login执行方法之前做一些内容…
登录成功
执行方法返回的结果:true
login执行方法之后做一些内容…
登录dao执行完了..

2.3 通过 spring 自带的 aoc 实现

导入相关包

pom.xml
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>

2.3.1 非注解方式实现

假设一个业务类
Hello.java
1
2
3
4
5
6
7
public class Hello {
private String msg;
//省略构造方法、getter/setter、toString()
public void say(){
System.out.println("-----------"+msg);
}
}
Spring配置文件配置切面
aop_config.xml
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
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"
xmlns:aop="http://www.springframework.org/schema/aop">

<!-- 启用动态代理 -->
<aop:aspectj-autoproxy/>

<!-- 配置切面类 -->
<bean id="myAspect" class="net.crazykid.bean.aop.springaop.MyAspect"></bean>

<!-- 配置aop -->
<aop:config>
<!--定义要拦截的类或方法-->
<!--execution(方法修饰符+方法返回值 完整类名+方法名(参数类型))-->
<aop:pointcut id="myPointcut" expression="execution(* net.crazykid.bean..*(..))"/>

<!--声明切面-->
<aop:aspect id="aspect" ref="myAspect">
<aop:before method="before" pointcut-ref="myPointcut"/>
<!--也可以把 pointcut 的表达式直接拿下来,就不用另外去定义 pointcut 了-->
<aop:after method="after" pointcut="execution(* net.crazykid.bean..*(..))"/>
<aop:around method="Around" pointcut-ref="myPointcut"/>
<aop:after-returning method="AfterReturning" pointcut-ref="myPointcut" returning="result"/>
<aop:after-throwing method="AfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
</aop:aspect>
</aop:config>

<!-- 声明业务类 -->
<bean id="hello" class="net.crazykid.bean.Hello">
<property name="msg" value="自定义信息..."/>
</bean>
</beans>
编写切面类
MyAspect.java
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {
//方法在执行前执行的内容
public void before(JoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法将被执行 ----");
}

//方法在执行结束后执行的内容
public void after(JoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束 ----");
}

//方法在执行前被拦截
public void Around(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法调用前被拦截了 ----");

try {
joinPoint.proceed(); //调用目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}

//方法执行结束并返回值
public void AfterReturning(JoinPoint joinPoint, String result) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束,返回值是 "+result+" ----");
}

//方法执行结束且抛出异常
public void AfterThrowing(JoinPoint joinPoint, Exception e) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束,抛出异常 "+e.getMessage()+" ----");
}
}
编写测试方法
1
2
3
4
5
6
 @Test
public void test03 () {
ApplicationContext context = new ClassPathXmlApplicationContext("aop_config.xml");
Hello hello = (Hello) context.getBean("hello");
hello.say();
}

运行结果

—- net.crazykid.bean.Hello 的 say 方法调用前被拦截了 —-
—- net.crazykid.bean.Hello 的 say 方法将被执行 —-
———–自定义信息…
—- net.crazykid.bean.Hello 的 say 方法执行结束 —-

2.3.2 通过注解方式实现

因为使用了注解的方式,我们在刚刚假设的业务类上加上注解

Hello.java
1
2
3
4
@Component //<---- 向 spring 声明这个 bean
public class Hello {
..
}
Spring 配置文件
aop_zhujie_config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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:context="http://www.springframework.org/schema/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 http://www.springframework.org/schema/context/spring-context.xsd"
xmlns:aop="http://www.springframework.org/schema/aop">

<!-- 开启spring的扫描注入 -->
<context:component-scan base-package="net.crazykid.bean"/>

<!-- 启用aop注解 -->
<aop:aspectj-autoproxy/>
</beans>
切面类
MyAspect_zhujie.java
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component // <-- 向 spring 声明这个 bean
@Aspect // <-- 告诉 spring 这是一个切面
public class MyAspect_Zhujie {

@Pointcut("execution(* net.crazykid..*(..))")
public void myMethod(){
}

@Before("myMethod()")
public void before(JoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法将被执行 ----");
}

@After("myMethod()")
public void after(JoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束 ----");
}

@Around("myMethod()")
public void Around(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法调用前被拦截了 ----");

try {
joinPoint.proceed(); //调用目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}

@AfterReturning(pointcut = "myMethod()", returning = "result")
public void AfterReturning(JoinPoint joinPoint, String result) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束,返回值是 "+result+" ----");
}

@AfterThrowing(pointcut = "myMethod()", throwing = "e")
public void AfterThrowing(JoinPoint joinPoint, Exception e) {
String name = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
System.out.println("---- "+name+" 的 "+method+" 方法执行结束,抛出异常 "+e.getMessage()+" ----");
}
}
编写测试方法

测试方法跟上面一样,这里就不写了。。。