Spring-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 interface UserService{
m1
m2
}
UserServiceImpl implements UserService{
m1 ---> 业务运算 DAO调⽤
m2
}
UserServiceProxy implements UserService
m1
m2
1.4 编码
静态代理:为每⼀个原始类,⼿⼯编写⼀个代理类 (.java .class)
1.5 静态代理存在的问题
1. 静态类⽂件数量过多,不利于项⽬管理
UserServiceImpl UserServiceProxy
OrderServiceImpl OrderServiceProxy
2. 额外功能维护性差
代理类中 额外功能修改复杂(麻烦)
第二章、Spring的动态代理
1、Spring动态代理的概念
概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类的维护)
2、搭建开发环境
<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.1、创建原始类
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务处理");
}
@Override
public boolean login(User user) {
System.out.println("UserServiceImpl.login");
return false;
}
}
- 1.2、配置文件注册
<?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:p="http://www.springframework.org/schema/p"
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/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="com.xiaohe.proxy.dynamicProxy.jdk.UserServiceImpl"/>
<bean id="before" class="com.xiaohe.proxy.dynamicProxy.before.Before"/>
</beans>
-
1.3、额外功能
-
MethodBeforeAdvice接口
额外的功能书写在接⼝的实现中,运⾏在原始⽅法执⾏之前运⾏额外功能。
public class Before implements MethodBeforeAdvice {
/**
* 作用:需要把运行在原始方法执行之前运行的额外功能,书写在Before方法中
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--------- method before advice ----------");
}
}
- 1.4、定义切入点
# 切入点: 额外功能加入的位置
目的: 由程序员根据自己需要,决定额外功能加入给哪个原始方法
<aop:config>
<!--定义切⼊点 所有的方法都作为切入点 加入额外功能-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>
- 1.5、组装切面
<!--组装:将额外功能个切入点整合-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
- 1.6、调用
⽬的:获得Spring⼯⼚创建的动态代理对象,并进⾏调⽤
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
注意:
1. Spring的⼯⼚通过原始对象的id值获得的是代理对象
2. 获得代理对象后,可以通过声明接⼝类型,进⾏对象的存储
UserService userService=(UserService)ctx.getBean("userService");
userService.login("")
userService.register()
4、动态代理细节分析
- 1、 Spring创建的动态代理类在哪⾥?
Spring框架在运⾏时,通过动态字节码技术,在JVM创建的,运⾏在JVM内部,等程序结束后,会和JVM⼀起消失
# 什么叫动态字节码技术:
通过第三个动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。
结论:动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态
代理,类⽂件数量过多,影响项⽬管理的问题。
- 2、 动态代理编程简化代理的开发
在额外功能不改变的前提下,创建其他⽬标类(原始类)的代理对象时,只需要指定原始(⽬标)对象即可。
第三章、Spring动态代理详解
- Spring Aop开发的四大步骤
1、原始对象
2、额外功能
3、切入点
4、组装
1、额外功能详解
- MethodBeforeAdvice
public class Before implements MethodBeforeAdvice {
/**
* 作用:需要把运行在原始方法执行之前运行的额外功能,书写在Before方法中
* method:额外功能所增加的给那个原始方法
* args:额外功能增加给的那个方法的参数
* target:原始对象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("--------- method before advice ----------");
}
}
- MethodInterceptor(方法拦截器) 额外功能运行在原始对象之前、之后、异常
MethodBeforeAdvice---------------》原始方法之前
MethodInterceptor-------------------》原始方法之前、之后都行
public class Arround implements MethodInterceptor {
/**
* invoke 方法的作用:额外功能书写在invoke
* 额外功能 之前
* 之后
* 前后
*MethodInvocation:额外功能增加给的那个原始方法
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("---------------- log -----------------");
Object ret = methodInvocation.proceed();
return ret;
}
}
2、切入点详解
切入点决定额外功能加入位置(方法)
<!--定义切⼊点 所有的方法都作为切入点 加入额外功能-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..)) ----------->匹配所有的方法
execution()切入点函数
* *(..) 切入点表达式
2.1、切入点表达式
方法切入点表达式
* *(..) 匹配所有的方法
* -------------->修饰符 返回值
* --------------->方法名
()---------------->参数表
.. ---------------->对于参数没有要求(参数有没有,有几个都行)
- 定义login方法作为切入点
# 定义login作为切入点
* login(..)
# 定义register作为切入点
* register(..)
- 定义Login方法有两个字符串类型的参数作为切入点
* login(String,String)
精准方法切入点限定
类切入点
* com.xiaohe.proxy.UserService.*(..)
2.2、切入点函数
切⼊点函数:⽤于执⾏切⼊点表达式
- execution
最为重要的切⼊点函数,功能最全。
执⾏ ⽅法切⼊点表达式 类切⼊点表达式 包切⼊点表达式
弊端:execution执⾏切⼊点表达式 ,书写麻烦
execution(* com.xiaohe.proxy..*.*(..))
注意:其他的切⼊点函数 简化是execution书写复杂度,功能上完全⼀致
- args
作⽤:主要⽤于函数(⽅法) 参数的匹配
切⼊点:⽅法参数必须得是2个字符串类型的参数
execution(* *(String,String))
args(String,String)
- within
作⽤:主要⽤于进⾏类、包切⼊点表达式的匹配
切⼊点:UserServiceImpl这个类
execution(* *..UserServiceImpl.*(..))
within(*..UserServiceImpl)
execution(* com.xiaohe.proxy..*.*(..))
within(com.xiaohe.proxy..*)
- @annotation
作⽤:为具有特殊注解的⽅法加⼊额外功能
<aop:pointcut id="" expression="@annotation(com.xiaohe.Log)"/>
第四章、AOP编程
1、AOP概念
AOP (Aspect Oriented Programing) ⾯向切⾯编程 = Spring动态代理开发
以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建
切⾯ = 切⼊点 + 额外功能
OOP (Object Oritened Programing) ⾯向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建
POP (Producer Oriented Programing) ⾯向过程(⽅法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建
AOP的概念:
本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
注意:AOP编程不可能取代OOP,OOP编程有意补充。
2、AOP编程的开发步骤
1. 原始对象
2. 额外功能 (MethodInterceptor)
3. 切⼊点
4. 组装切⾯ (额外功能+切⼊点)
3、切⾯的名词解释
切⾯ = 切⼊点 + 额外功能
⼏何学
⾯ = 点 + 相同的性质
第五章、AOP底层实现
1、核心问题
1. AOP如何创建动态代理类(动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象
通过原始对象的id值,获得的是代理对象
2、动态代理的创建
2.1、JDK动态代理
- Proxy.newInstance()方法详解
- 编码
public class TestJdk {
public static void main(String[] args) {
//原始对象
final UserService userService = new UserServiceImpl();
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJdk.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-------------- proxy log ------------------");
//原始功能执行
Object ret = method.invoke(userService, args);
return ret;
}
});
userServiceProxy.login(new User());
}
}
2.2、CGlib动态代理
CGlib创建动态代理的原理:⽗⼦继承关系创建代理对象,原始类作为⽗类,代理类作为⼦类,这样既可以保证2者⽅法⼀致,同时在代理类中提供新的实现(额外功能+原始⽅法)
- 编码
public class TestCglib {
public static void main(String[] args) {
//1、原始类
final UserService userService = new UserService();
/**
* 通过cglib方式创建对象
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("-------- log --------");
Object ret = method.invoke(userService, args);
return ret;
}
});
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("zhangsan","123");
}
}
# JDK动态代理 Proxy.newProxyInstance() 通过接⼝创建代理的实现类
# Cglib动态代理 Enhancer 通过继承⽗类创建的代理类
- Spring工厂对代理的加工
public class ProxyPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(bean, args);
return ret;
}
};
return Proxy.newProxyInstance(ProxyPostProcessor.class.getClassLoader(),new Class[]{UserService.class},handler);
}
}
第六章、基于注解的AOP编程
1、基于注解的 AOP编程开发步骤
- 1、原始对象
- 2、额外功能
- 3、切入点
- 4、组装切面
@Aspect //声明为切面类
public class MyAspect {
/**
* 切入点
*/
@Around("execution(* com.xiaohe.aspect.UserServiceImpl.*(..))")//切入点
public Object arround(ProceedingJoinPoint joinPoint){//ProceedingJoinPoint joinPoint 额外功能
Object proceed = null;
try {
System.out.println("--------------- log ------------");
proceed = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
return proceed;
}
}
- 配置文件配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.xiaohe.aspect.UserServiceImpl"/>
<!--
切面:
1、额外功能
2、切入点
3、组装切面
-->
<bean id="aspect" class="com.xiaohe.aspect.MyAspect"/>
<!--告诉Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy/>
</beans>
2、切入点复用
//切⼊点复⽤:在切⾯类中定义⼀个函数 上⾯@Pointcut注解 通过这种⽅式,定义切⼊点表达式,后续更加有利于切⼊点复⽤。
@Aspect //声明为切面类
public class MyAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.xiaohe.aspect.UserServiceImpl.*(..))")
public void pointCut(){}
@Around(value = "pointCut()")//切入点
public Object arround(ProceedingJoinPoint joinPoint){
Object proceed = null;
try {
System.out.println("--------------- log ------------");
proceed = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
return proceed;
}
}
3、动态代理的创建⽅式
AOP底层实现 2种代理创建⽅式
1. JDK 通过实现接⼝ 做新的实现类⽅式 创建代理对象
2. Cglib通过继承⽗类 做新的⼦类 创建代理对象
默认情况 AOP编程 底层应⽤JDK动态代理创建⽅式
如果切换Cglib
1. 基于注解AOP开发
<aop:aspectj-autoproxy proxy-target-class="true" />
2. 传统的AOP开发
<aop:config proxy-target-class="true">
</aop>