1、静态代理设计模式
1.1、为什么需要代理设计模式
1.1.1、问题
-
在JavaEE分层开发中,哪个层次对于我们来讲最重要
DAO ----> Service ----> Controller JavaEE分层开发中,最为重要的是Service层 -
Service层中包含了哪些代码?
Service层中 = 核心功能(几十行 上百代码) + 额外功能(附加功能) 1. 核心功能 业务运算 DAO调用 2. 额外功能 1. 不属于业务 2. 可有可无 3. 代码量很小 事务、日志、性能... -
额外功能书写在Service层好不好?
Service层的调用者的角度(Controller):需要在Service层书写额外的功能。 软件设计者:Service层不需要额外的功能 -
现实生活中的解决方法
1.2、代理设计模式
1.2.1、概念
通过代理类,为原始类(目标)增加额外的功能
好处:利于原始类(目标)的维护
1.2.2、名词解释
1. 目标类 原始类
指的是 业务类 (核心功能----> 业务运算 DAO调用)
2. 目标方法,原始方法
目标类(原始类)中的发发,就是目标方法(原始方法)
3. 额外功能(附加功能)
日志、事务、性能
1.2.3、代理开发的核心要素
代理类 = ⽬标类(原始类) + 额外功能 + 原始类(⽬标类)实现相同的接⼝
房东 ---> public interface UserService{
m1
m2
}
UserServiceImpl implements UserService{
m1 ---> 业务运算 DAO调⽤
m2
}
UserServiceProxy implements UserService
m1
m2
1.2.4、编码
静态代理:为每一个原始类,手工编写一个代理类(.java .class)
1.2.5、静态代理存在问题
1. 静态类⽂件数量过多,不利于项⽬管理
UserServiceImpl UserServiceProxy
OrderServiceImpl OrderServiceProxy
2. 额外功能维护性差
代理类中 额外功能修改复杂(麻烦)
2、Spring的动态代理开发
2.1、Spring动态代理的概念
概念:通过代理类为原始类(⽬标类)增加额外功能
好处:利于原始类(⽬标类)的维护
2.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>
2.3、Spring代理的开发步骤
2.3.1、创建原始对象(目标对象)
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
<bean id="userService" class="cn.edu.njtech.proxy.UserServiceImpl"></bean>
2.3.2、额外功能 MethodBeforeAdvice接口
额外的功能书写在接口的实现中,运行在原始方法执行之前运行额外功能
package cn.edu.njtech.dynamic;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author Tim
* @date 2021/11/2 23:05
*/
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="cn.edu.njtech.dynamic.Before"></bean>
2.3.3、定义切入点
切⼊点:额外功能加⼊的位置
⽬的:由程序员根据⾃⼰的需要,决定额外功能加⼊给那个原始⽅法
register
login
简单的测试:所有⽅法都做为切⼊点,都加⼊额外的功能。
<aop:pointcut id="pc" expression="execution(* *(..))"/>
2.3.4、组装
<aop:advisor advice-ref="before" pointcut-ref="pc"></aop:advisor>
2.3.5、调用
目的:获得Spring工厂创建的动态代理对象,并进行调用。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.register(new User());
userService.login("tim","123");
3、动态代理细节分析
3.1、Spring创建的动态代理类在哪里?
Spring框架在运⾏时,通过动态字节码技术,在JVM创建的,运⾏在JVM内部,等程序结束后,会和JVM⼀起消失
什么叫动态字节码技术:通过第三放动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。
结论:动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态代理类⽂件数量过多,影响项⽬管理的问题
3.2、动态代理编程简化代理的开发
在额外功能不改变的前提下,创建其他⽬标类(原始类)的代理对象时,只需要指定原始(⽬标)对象即可。
3.3、动态代理额外功能的维护性大大增强
4、Spring动态代理详解
4.1、额外功能的详解
-
MethodBeforeAdvice分析
package cn.edu.njtech.dynamic; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * @author Tim * @date 2021/11/2 23:05 */ public class Before implements MethodBeforeAdvice { /** * 需要把运行在原始方法之前运行的额外功能,书写在before方法中 * @param method 额外功能所增加给的那个原始方法 login方法 register方法 showOrder方法 * @param objects 额外功能所增加给的那个原始方法的参数 User name password * @param o 原始对象 userService orderService * @throws Throwable */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("-----method before advice log------"); } } -
MethodInterceptor(方法拦截器)
methodInterceptor接口:额外功能可以根据需要运行在原始方法执行 前、后、前后package cn.edu.njtech.dynamic; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.lang.reflect.Method; /** * @author Tim * @date 2021/11/2 23:56 */ public class Arround implements MethodInterceptor { /** * invoke 方法的作用:额外功能写在invoke * 额外功能 原始方法之前 之后 或者环绕 * * 确定:原始方法怎么运行 * * 参数:MethodInvocation(Method) 额外功能所增加给的那个原始方法 * * 返回值:Object 原始方法的返回值 * * @param methodInvocation * @return * @throws Throwable */ @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object[] arguments = methodInvocation.getArguments(); Object object = methodInvocation.getThis(); Method method = methodInvocation.getMethod(); System.out.println("----额外功能之前 log----"); Object proceed= null; try { proceed = methodInvocation.proceed(); }catch (Exception e){ System.out.println("----额外功能抛出异常 log----"); e.printStackTrace(); } System.out.println("----额外功能之后 log----"); return proceed; } } -
MethodInterceptor影响原方法的返回值
原始⽅法的返回值,直接作为invoke⽅法的返回值返回,MethodInterceptor不会影响原始⽅法的返回值 MethodInterceptor影响原始⽅法的返回值 Invoke⽅法的返回值,不要直接返回原始⽅法的运⾏结果即可。 @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("------log-----"); Object ret = invocation.proceed(); return false; }
4.2、切入点详解
切入点决定额外功能加入位置(方法)
<aop:pointcut id = "pc" expression="execution( * * (..))"/>
execution(* *(..)) -----> 匹配了所有方法 a b c
1. execution() 切入点函数
2. * * (..) 切入点表达式
4.2.1、切入点表达式
- 方法切入点表达式
* * (..) --> 所有方法
* ---> 修饰符 返回值
* ---> 方法名
() ---> 参数表
.. ---> 对于参数没有要去(参数有没有,参数有几个都行,参数是什么类型的都行)
-
定义login方法作为切入点
* login(..) ## 定义rreeggiisstteerr作为切⼊点 * register(..) -
定义login方法且login方法有两个字符串类型的参数作为切入点
* login(String,String) # 注意:⾮jjaavvaa..llaanngg包中的类型,必须要写全限定名 * register(cn.edu.njtech.proxy.User) # ....可以和具体的参数类型连⽤ * login(String,..) --> login(String),login(String,String),login(String,cn.edu.njtech.proxy.User) -
精准方法切入点限定
修饰符 返回值 包.类.⽅法(参数) * com.baizhiedu.proxy.UserServiceImpl.login(..) * com.baizhiedu.proxy.UserServiceImpl.login(String,String)
-
类切入点
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。-
语法1
##类中的所有⽅法加⼊了额外功能 * com.baizhiedu.proxy.UserServiceImpl.*(..) -
语法2
# 忽略包 1. 类只存在⼀级包 com.UserServiceImpl * *.UserServiceImpl.*(..) 2. 类存在多级包 com.baizhiedu.proxy.UserServiceImpl * *..UserServiceImpl.*(..)
-
-
包切入点表达式 实战
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。-
语法1
# 切⼊点包中的所有类,必须在pprrooxxyy中,不能在pprrooxxyy包的⼦包中 * com.baizhiedu.proxy.*.*(..) -
语法2
# 切⼊点当前包及其⼦包都⽣效 * com.baizhiedu.proxy..*.*(..)
-
4.2.2、切入点函数
切入点函数:用于执行切入点表达式
-
execution
最为重要的切⼊点函数,功能最全。 执⾏ ⽅法切⼊点表达式 类切⼊点表达式 包切⼊点表达式 弊端:execution执⾏切⼊点表达式 ,书写麻烦 execution(* com.baizhiedu.proxy..*.*(..)) 注意:其他的切⼊点函数 简化是execution书写复杂度,功能上完全⼀致 -
args
作⽤:主要⽤于函数(⽅法) 参数的匹配 切⼊点:⽅法参数必须得是2个字符串类型的参数 execution(* *(String,String)) args(String,String) -
within
作⽤:主要⽤于进⾏类、包切⼊点表达式的匹配 切⼊点:UserServiceImpl这个类 execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl) execution(* com.baizhiedu.proxy..*.*(..)) within(com.baizhiedu.proxy..*) -
@annotation
作⽤:为具有特殊注解的⽅法加⼊额外功能 <aop:pointcut id="" expression="@annotation(com.baizhiedu.Log)"/>package cn.edu.njtech; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author Tim * @date 2021/11/3 14:58 */ /** * 用到哪上面 什么事实生效 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }package cn.edu.njtech.proxy; import cn.edu.njtech.Log; /** * @author Tim * @date 2021/11/2 22:17 */ public class UserServiceImpl implements UserService { @Log @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO"); // throw new RuntimeException("测试异常"); } @Override public boolean login(String name, String password) { System.out.println("UserServiceImpl.login"); return true; } } -
切入点函数的逻辑运算
指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求-
and 与 操作
案例:login 同时 参数 2个字符串 1. execution(* login(String,String)) 2. execution(* login(..)) and args(String,String) 注意:与操作不同⽤于同种类型的切⼊点函数 案例:register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..)) -
or 或 操作
案例:register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..))
-
5、AOP编程
5.1、AOP概念
AOP(Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能
OOP(Object Oriented Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建
POP(Producer Oriented Programing) 面向过程(方法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建
5.2、AOP编程的开发步骤
1. 原始对象
2. 额外功能(MethodInterceptor)
3. 切入点
4. 组装切面(额外功能+切入点)
5.3、切面的名次解释
切面 = 切入点 + 额外功能
几何学:
面 = 点 + 相同的性质
6、AOP的底层实现原理
6.1、核心问题
1. AOP如何创建动态代理类(动态字节码技术)
2. Spring工厂如何加工创建代理对象
通过原始对象的id值,获得的是代理对象
6.2、动态代理类的创建
6.2.1、JDK的动态代理
- Proxy.newProxyInstance方法参数详解
-
编码
package cn.edu.njtech.jdk; import cn.edu.njtech.proxy.User; import cn.edu.njtech.proxy.UserService; import cn.edu.njtech.proxy.UserServiceImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author Tim * @date 2021/11/4 19:50 */ public class TestJDKProxy { public static void main(String[] args) { // 1.创建原始对象 UserService userService = new UserServiceImpl(); // 2. JDK创建动态对象 /* */ UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----proxy log-----"); return method.invoke(userService, args); } }); userServiceProxy.login("tim", "123456"); userServiceProxy.register(new User()); } }
6.2.2、CGlib的动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证两者方法一直,同时在代理类中提供新的实现(原始方法+额外功能)
-
CGlib编码
package cn.edu.njtech.cglib; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @author Tim * @date 2021/11/4 20:35 */ public class TestCglib { public static void main(String[] args) { // 1.创建原始对象 UserService userService = new UserService(); // 2.通过cglib方式创建动态代理对象 /** * Proxy.newProxyInstance(classloader,interface,invocationhandler) * * Enhancer.setClassLoader * Enhancer.setSuperClass * Enhancer.setCallback(); --> MethodInterceptor(cglib) * Enhancer.create() -> 代理 */ Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(TestCglib.class.getClassLoader()); enhancer.setSuperclass(userService.getClass()); enhancer.setCallback(new MethodInterceptor() { // 等同于 InvocationHandler -- > invoker @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("-----cglib-----"); Object ret = method.invoke(userService, objects); return ret; } }); UserService service = (UserService) enhancer.create(); service.login("tim", "123"); } } -
总结
- JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
- CGlib动态代理 Enhancer 通过继承父类创建的代理类
6.3、Spring工厂如何加工原始对象
-
思路分析
-
编码
package cn.edu.njtech.factory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author Tim * @date 2021/11/4 20:59 */ public class ProxyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override /** * Proxy.newProxyInstance(); */ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----log-----"); return method.invoke(bean, args); } }); } }<bean id="beanPostProcessor" class="cn.edu.njtech.factory.ProxyBeanPostProcessor"></bean> <bean id="userService" class="cn.edu.njtech.factory.UserServiceImpl"></bean>
7、基于注解的AOP编程
7.1、基于注解的AOP编程的开发步骤
-
原始对象
-
额外功能
-
切入点
-
组装切面
package cn.edu.njtech.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; /** * @author Tim * @date 2021/11/4 21:12 */ /** * 1. 额外功能 * public class MyAround implements MethodInterceptor * 2. 切入点 */ @Aspect public class MyAspect { @Pointcut("execution(* login(..))") public void myPointCut() { } @Around(value = "myPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("----aspect log----"); Object proceed = point.proceed(); return proceed; } @Around(value = "myPointCut()") public Object around1(ProceedingJoinPoint point) throws Throwable { System.out.println("----aspect tx---"); Object proceed = point.proceed(); return proceed; } }<bean id="userService" class="com.baizhiedu.aspect.UserServiceImpl"/> <!-- 切⾯ 1. 额外功能 2. 切⼊点 3. 组装切⾯ --> <bean id="arround" class="com.baizhiedu.aspect.MyAspect"/> <!--告知Spring基于注解进⾏AOP编程--> <aop:aspectj-autoproxy />
7.2、动态代理的创建方式
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>
8、AOP开发中的一个坑
// 坑:在同⼀个业务类中,进⾏业务⽅法间的相互调⽤,只有最外层的⽅法,才是加⼊了额外功能(内部的⽅法,通过普通的⽅式调⽤,都调⽤的是原始⽅法)。如果想让内层的⽅法也调⽤代理对象的⽅法,就要AppicationContextAware获得⼯⼚,进⽽获得代理对象。
public class UserServiceImpl implements UserService,ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Log
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
//throw new RuntimeException("测试异常");
//调⽤的是原始对象的login⽅法 ---> 核⼼功能
/*
设计⽬的:代理对象的login⽅法 ---> 额外功能+核⼼功能
ApplicationContext ctx = new
ClassPathXmlApplicationContext("/applicationContext2.xml");
UserService userService = (UserService)
ctx.getBean("userService");
userService.login();
Spring⼯⼚重量级资源 ⼀个应⽤中 应该只创建⼀个⼯⼚
*/
UserService userService = (UserService)
ctx.getBean("userService");
userService.login("tim", "123456");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}