AOP编程
第一章、 静态代理设计模式
1. 为什么需要代理设计模式
1.1 问题
- 在javaEE分层开发中,哪个层对我们来讲最重要
DAO --> Service --> Controller
在javaEE分层开发中,最重要的是Service层
- Service层包含了哪些代码
Service层 = 核心功能(几十行上百行) + 额外功能(附加功能)
1. 核心功能
业务运算
DAO调用
2. 额外功能
1. 不属于业务
2. 可有可无
3. 代码量很小
如:事务、性能、日志...
- 额外功能书写在Service层中好不好
Service层的调用者角度来说(Controller):需要在Service层中书写这些功能(事务)
设计角度:Service层中不需要这些额外功能
- 现实生活中解决案例
2.代理设计模式
1.1 概念
通过代理类,为原始类(目标类)增加新功能
好处:利于原始类的维护
1.2 名词解释
1. 目标类,原始类:指的是业务类(核心功能 --> 业务运算、DAO调用)
2. 目标类(原始类)中的方法就是目标方法(原始方法)
3. 额外功能(附加功能),如:事务、日志、性能监控...
1.3 代理开发的核心要素
代理类 = 原始类(目标类) + 额外功能 + 与原始类实现相同接口
public intertface UserService(){
m1
m2
}
UserServiceImpl implements UserService{
m1 ---> 完成核心功能(业务运算、DAO调用)
m2
}
UserServiceproxy implements UserService{
m1
m2
}
1.4 编码
静态代理:为每一个原始类,需要手动编写代理类
1.5 静态代理存在的问题
1. 静态代理文件过多,不利于项目管理
UserServiceImpl UserServiceProxy
OrderServiceImpl OrderServiceProxy
2. 额外功能维护性差
代码中,额外功能修改复杂(麻烦)
第二章、Spring的动态代理开发
1.Spring动态代理的概念
概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护
2.搭建开发环境
pom依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
3.Spring动态代理开发步骤
1. 创建原始(目标)对象
public class UserServiceImpl implements UserService{
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
<bean id="userService" class="xxx.UserServiceImpl"/>
2. 额外功能
MethodBeforeAdvice接口
额外功能书写在接口的实现中,会运行在原始方法运行之前执行
public class Before implements MethodBeforeAdvice {
/**
* 把要在原始方法运行之前执行的方法,写在before中
* @param method
* @param objects
* @param o
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("method before advice log");
}
}
<bean id="before" class="com.xxc.dynamic.Before"/>
3.定义切入点
切入点:额外功能加入的位置
目的:由程序员根据需要,决定额外功能加入给哪个原始方法
简单测试:让所有方法都做切入点,都加上额外功能
<aop:config>
<aop:pointcut id="pc" expression="exection(* *(..))"/>
</aop:config>
4.组装(2、3整合)
将所有的方法都加入before的额外功能
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
5.调用
获得Spring工厂创建的动态代理对象,并调用
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
注意:
1. Spring的工厂通过原始对象的id值获取的是代理对象
2. 获得代理对象后,可以通过接口类型,进行对象存储
UserService userService = (UserService) ctx.getBean("userService");
4.动态代理细节分析
- Spring创建的代理类在哪?
Spring框架在运行时,通过动态字节码技术,在JVM中创建的,运行在JVM内部,等程序结束后,会和JVM一起消失
什么叫动态字节码技术?
通过第三方鼎泰字节码框架,在JVM创建对应的字节码,进而创建对象,当虚拟机结束,字节码也跟着消失
结论:动态代理不需要定义类文件,都是JVM运行过程中创建的,因而不会造成静态代理类文件过多,影响项目管理的问题
- 动态代理编程简化代理开发
在额外功能不改变的情况下,创建其他目标类的代理对象时,只需要指定原始对象即可
- 动态代理维护性大大增强
第三章、Spring动态代理详解
1.额外功能的详解
- MethdBeforeAdvice分析
1. MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作
public class Before1 implements MethodBeforeAdvice {
/**
* 把要在原始方法运行之前执行的方法,写在before中
* @param method 所增加给的原始方法
* @param objects 额外功能所增加给的那个原始方法的参数
* @param o 原始对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("----- new method before advice log -----");
}
}
2. before方法三个参数在实战中,该如何使用
before方法的参数在实战中,会根据需要进行使用,不一定都会用到,有时候也可能都不使用
- MethodInterceptor(方法拦截器)
MethodBeforeAdvice ---> 原始方法执行之前
MethodInterceptor ---> 前 后 前后
public class Arround implements MethodInterceptor {
/**
* 作用:将额外功能书写在invoke中,额外功能可以运行在原始方法 前 后 前后
*
* @param invocation 额外功能所增加的那个方法
* @return 原始方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("------- 额外功能 log ------");
//运行原始方法
Object ret = invocation.proceed();
System.out.println("------- 额外功能 log ------");
return ret;
}
}
额外功能运行在原始方法之后
public Object invoke(MethodInvocation invocation) throws Throwable {
//运行原始方法
Object ret = invocation.proceed();
System.out.println("------- 额外功能运行在原始方法之后 log ------");
return ret;
}
额外功能运行在原始方法之后、之后
什么样的额外功能,运行在原始方法之前之后
如:事务
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("------- 额外功能运行在原始方法之前 log ------");
//运行原始方法
Object ret = invocation.proceed();
System.out.println("------- 额外功能运行在原始方法之后 log ------");
return ret;
}
额外功能运行在原始方法抛出异常时
public Object invoke(MethodInvocation invocation) throws Throwable {
//运行原始方法
Object ret = null;
try {
ret = invocation.proceed();
} catch (Throwable throwable) {
System.out.println("原始方法抛出异常之后,执行的额外功能");
throwable.printStackTrace();
}
return ret;
}
MethodInterceptor影响原始方法的返回值
直接将原始方法的返回值,直接作为invoke方法的返回值,那么就不会影响原始方法的返回值
MethodInterceptor影响原始方法的返回值
invoke方法的返回值,不返回原始方法的返回值即可
2.切入点详解
切入点决定额外功能加入的位置
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..)) ---> 匹配所有方法
1. execution() ---> 切入点函数
2. * *(..) ---> 切入点表达式
2.1 切入点表达式
* *(..) ---> 所有方法
* ---> 修饰符,返回值
* ---> 方法名
() ---> 参数表
.. ---> 对于参数没有要求(类型、个数均无要求)
- 定义login方法为切入点
* login(..)
# register作为切入点
* register(..)
- 定义login方法且有两个字符串类型的参数作为切入点
* login(String,String)
# 注意:非java.lang包下的类型必须写全限定名
* register()
# ..可以和具体的类型连用
* login(String,..)
- 精准的限定方法切入点
* com.xxc.proxy.UserServiceImpl.login(String,..)
2. 类切入点
指定特定的类作为切入点,将这个类下的所有方法都加上了这写额外功能
- 语法1
# 这个类下的所有方法都加上了额外功能
* com.xxc.proxy.UserServiceImpl.*(..)
- 语法2
# 忽略包
1. 类中只存在一级包
* *.UserServiceImpl.*(..)
2. 类中存在多级包
* *..UserServiceImpl.*(..)
3. 包切入点
指定包作为额外功能加入的位置,包中的所有方法都会加入额外功能
- 语法1
# 注意:切入包中的所有类,必须在xxc包中,不能再其子包中
* com.xxc.*.*(..)
- 语法2
# 切入点为当前包及其子包
* com.xxc..*.*(..)
2.2 切入点函数
执行切入点表达式
- execution
最重要的切入点函数,功能最全
可以执行方法切入点表达式、类切入点表达式、包切入点表达式
弊端:execution执行切入点打表达式书写麻烦
注意:其他的切入点函数,只简化切入点函数,功能上完全一致
- args
作用:主要用于函数(方法)、参数的匹配
例:方法参数是两个String类型的
execution(* *(String,String))
args(String,String)
- within
作用:主要用于进行类、包切入点的匹配
例: UserServiceImpl这个类
execution(* *..UserServiceImpl.*(..))
within(*..UserServiceImpl)
- @annotation
作用:为具有特殊注解的方法加入额外功能
/**
* @author xiangcheng
* @version 2022/1/6 20:42
* @since JDK8
*
* 标志要添加切入点的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
<aop:pointcut id="pc" exprossion="@annotation(com.xxc.Log)"/>
- 切入点函数的逻辑运算
指的是整合多个切入点函数,一起进行配合工作,进而完成更为复杂的功能
- and 操作
例:login 并 参数为两个
execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数
- or 操作
例:login或region方法
execution(* login(..)) or execution(* region(..))