0%

SSM-3-AOP

SSM-3-AOP

  1. Spring AOP简介
  2. AOP术语

AOP简介

面向切面编程,比如日志记录,横向插入

动态代理

jdk动态代理

  1. 什么是静态代理
  2. 什么是动态代理

首先得先了解这两个是什么,静态代理的优缺点,为什么要引入动态代理

设计接口类

1
2
3
4
5
6
package com.itheima.jdk;

public interface UserDao {
public void AddUser();
public void DeleteUser();
}

实现接口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima.jdk;

public class UserDaoImpl implements UserDao {

@Override
public void AddUser() {
System.out.println("添加用户");
}

@Override
public void DeleteUser() {
System.out.println("删除用户");
}

}

实现切面类,实际就是代理的方法

1
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.aspect;

//切面类: 可以存在多个通知Advice(即增强的方法)
public class MyAspect {
public void check_Perminssions() {
System.out.println("模拟检查权限");
}

public void log() {
System.out.println("模拟记录日志");
}
}

实现JdkProxy

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
package com.itheima.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.itheima.aspect.MyAspect;
/*
* JDK的代理类
*/
public class JdkProxy implements InvocationHandler {
//声明目标类接口
private UserDao userDao;

//创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
//1.类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class<?>[] clazz = userDao.getClass().getInterfaces();
//3. 使用代理类,进行增强
return Proxy.newProxyInstance(classLoader, clazz, this);
}

/*
* 所有动态代理类的方法调用都会交由invoke()方法处理
* proxy 被代理后的对象
* method 将要被执行的方法信息
* args 执行方法需要的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyAspect myAspect = new MyAspect();
myAspect.check_Perminssions();
Object obj = method.invoke(userDao, args);
//后增强
myAspect.log();
return obj;
}

}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.itheima.jdk;

public class JdkTest {

public static void main(String[] args) {
//创建代理对象
JdkProxy jdkProxy = new JdkProxy();
// 创建目标对象
UserDao userDao = new UserDaoImpl();
UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);

userDao1.AddUser();
userDao1.DeleteUser();
}

}

缺点

  1. 必须有接口方法才能使用这种模式

思考

  1. 如何只代理其中的一种方法呢?
  2. 代码中类加载器为什么是ClassLoader classLoader = JdkProxy.class.getClassLoader();

结论:

  1. 只需在invoke里添加if (“xxx”.equals(method.getName())) 判断方法名就行
  2. 这里只要是非自定义的加载器就行,随意,可以用userDao,userImple等等

CGLIB代理

创建普通类

1
2
3
4
5
6
7
8
9
10
package com.itheima.cglib;

public class UserDao {
public void addUser() {
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}

创建动态代理类

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
package com.itheima.cglib;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.itheima.aspect.MyAspect;

public class CglibProxy implements MethodInterceptor {

public Object createProxy(Object target) {
//创建一个动态类对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/*
* proxy cglib是根据指定父类生成的代理对象
* method 拦截的方法
* args 拦截方法的参数
* methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 创建切面对象
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check_Perminssions();
//目标方法
Object object = methodProxy.invokeSuper(proxy, args);
//后增强
myAspect.log();
return null;
}

}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.itheima.cglib;

public class CglibTest {

public static void main(String[] args) {
//创建代理对象
CglibProxy cglibProxy = new CglibProxy();
//创建目标对象
UserDao userDao = new UserDao();
//获取增强后的目标对象
UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}

}

过程相对简单一点了

基于代理类的AOP实现

  1. spring的通知类型
  2. ProxyFactoryBean

直接实现aop的切面方法类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itheima.factorybean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
check_Permissions();
Object obj = mi.proceed();
log();
return null;
}

public void check_Permissions() {
System.out.println("模拟检查权限");
}
public void log() {
System.out.println("模拟记录日志");
}

}

写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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-4.3.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"></bean>
<!-- 2.切面类 -->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"></bean>
<!-- 3.使用Spring代理工厂定义一个名为userDaoProxy的代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口 -->
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"></property>
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao"></property>
<!-- 3.3 指定切面,织入环绕通知 -->
<property name="interceptorNames" value="myAspect"></property>
<!-- 3.4 指定代理方式, true:cglib, false(默认):jdk动态代理 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itheima.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.jdk.UserDao;

public class ProxyFactoryBeanTest {
public static void main(String[] args) {
String xmlPath = "com/itheima/factorybean/applicationContext.xml";

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
//从Spring容器中获得内容
UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");

userDao.AddUser();
userDao.DeleteUser();
}
}

这里直接用构造器实例化了,过程相对比较简单,写完在看下就可以了

AspectJ开发

基于XML的声明式AspectJ

配置切面

  1. id
  2. ref

全局跟局部

切入点表达式

execution(* com.itheima.jdk.*.*(…))

切入点表达式的基本格式

execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?

name-pattern(param-pattern)throws-pattern?)

配置通知

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知

名词解释:

  • pointcut 切入点

  • pointcut-ref 切入点引用

  • method 增强的方法

  • throwing 异常处理的名称

  • returning 返回值

测试

首先编写切面类

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
package com.itheima.aspectj.xml;

import org.aspectj.lang.JoinPoint;
/*
* 切面类,在此类中编写通知
*/
import org.aspectj.lang.ProceedingJoinPoint;


public class MyAspect {
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知:模拟执行权限检查");
System.out.println("目标类是: " + joinPoint.getTarget());
System.out.println("织入的增强处理目标方法为"+joinPoint.getSignature().getName());
}

public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("后置通知: 模拟记录日志");
System.out.println("被织入增强处理的目标方法为: " + joinPoint.getSignature().getName());

}

/*
* 环绕通知
* ProceedingJoinPoint时JoinPoint子接口,表示可以执行目标方法
* 1. 必须是Object类型的返回值
* 2. 必须接受一个参数,类型为ProceedingJoinPoint
* 3. 必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始: 执行目标方法之前,模拟开启事务...");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束");
return obj;
}

//异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知: " + "出错了" + e.getMessage());
}

//最终通知
public void myAfter() {
System.out.println("最终通知: 模拟方法结束后的释放资源");
}
}

接着编写配置文件,步骤

  1. 设置目标类
  2. 设置切面
  3. aop编程
    1. 配置切面
    2. 配置切入点
    3. 配置通知
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"></bean>
<!-- 2.切面 -->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect"></bean>
<!-- 3. aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))" id="myPointCut"/>
<!-- 3.2 关联通知Advice和切入点pointcut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 3.2.2 后置通知 -->
<!-- returning属性: 用于设置后 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
<!-- 3.2.3 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 3.2.4 异常通知,没异常则不执行 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 3.2.5 最终通知 相当于异常处理中final-->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>

</aop:aspect>
</aop:config>
</beans>

然后是测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itheima.aspectj.xml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.jdk.UserDao;

public class TestXmlAspectj {

public static void main(String[] args) {
String xmlPath = "com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
// 1. 从spring容器中获得内容
UserDao userDao = (UserDao)applicationContext.getBean("userDao");
userDao.AddUser();
// 2. 执行方法
}

}

基于注解的声明式AspectJ-new

由于误操作,笔记删了。。。随缘了

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/2527f84c/
版权声明:转载请注明出处!