一、静态代理设计模式
1.为什么需要代理设计模式
1.1问题
-
在JavaEE分层开发过程中,哪个层次对于我们来讲最重要
DAO—>Service–>Controller
JavaEE分层开发中,最为重要的是Service Service业务层 -
Service层中包含了哪些代码?
Service层中 =核心功能代码(几十行 上百行)+额外功能代码(附加功能) 1.核心功能 业务运算 DAO调用 2.额外功能 不属于业务 可有可无 代码量小 例如:事务、日志、性能。。。
-
额外功能书写在Service层中好不好?
Service层的调用者(controller):需要在Service层书写额外功能 软件设计者:Service层不需要额外功能
-
现实生活中 的解决方式
2.代理设计模式
2.1概念通过代理类,为原始类(目标)增加额外功能
好处:利于原始类(目标)的维护2.2名词解释
1.目标类 原始类 指的是 业务类(核心功能 --->业务运算 DAO调用) 2.目标方法 原始方法 目标类(原始类)中的方法 就是目标方法(原始方法) 3.额外功能(附加功能) 日志,事务,性能
2.3代理开发的核心要素
代理类=目标类(原始类)+额外功能+原始类(目标类)实现相同的接口 房东---->public interface UserService{ m1(){} m2(){} } UserServiceImpl implements UserService{ m1(){} m2(){} }
2.4编码
静态代理: 为每一个原始类,手工编写一个代理类(.java .class)
2.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动态代理的开发步骤
①创建原始对目标对象
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======核心业务代码登录 调用DAO");
return false;
}
}
<bean id="userService" class="com.myspring.proxy.UserServiceImpl" />
②、额外功能
MethodBeforeAdvice接口
额外的功能书写在接口的实现中,运行在原始方法之前运行额外功能
public class Before implements MethodBeforeAdvice {
/*
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("======额外功能=====");
}
}
<bean id="before" class="com.myspring.dynamic.Before" />
③、定义切入点
切入点:额外功能加入的位置
目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法
registe / longin
简单的测试:所有方法都作为切入点都加入额外的功能。
<aop:config>
<!--定义切入的方式-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>
④、组装(②、③整合)
表达的含义:所有方法 都加入before的额外功能
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
这是④的完整配置
<aop:config>
<!--定义切入的方式-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>
⑤、调用
目的:获得Sporing工厂的动态代理对象,并进行调用
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
注意:
1.Spring的工厂通过原始对象的id值获得的是代理对象
2.获得代理对象后,可以通过声明接口类型,进行对象的存储
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login("bugz","123456");
userService.register(new User("bugz","123456"));
4.动态代理细节分析
①、Spring创建的动态代理类在那里?
Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。
————————————————————————————————————————————————————
什么叫动态字节码技术:通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。
—————————————————————————————————————————————————
结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影响项目管理的问题。
②、动态代理编程简化代理的开发
在额外功能不变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。
③动态代理额外功能的维护性大大增强
三、Spring动态代理详解
1.额外功能的详解
- MethodBeforeAdvice分析
1.MethodBeforeAdvice接口作用:额外功能运行在原始方法之前,进行额外功能操作。
public class Before implements MethodBeforeAdvice {
/*
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
方法参数介绍:
Method method: 额外功能所增加给的那个原始方法
Object[] objects: 额外功能所增加给的那个原始方法的参数。
Object o:额外功能所增加给的那个原始对象
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("======额外功能=====");
}
}
——————————————————————————————————————————————————————————————————————————————————————
2.before方法中的三个参数在实战中该如何使用
before方法中的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用。
- MethodInterceptor(方法拦截器)
methodinterceptor接口:额外功能可以根据需要运行在原始方法执行 前、后、前后。
-------------------------------------------------------------------
public class Around implements MethodInterceptor {
/*
invoke方法的作用:额外功能书写在invoke
额外功能可以加在 原始方法之前
原始方法之后
原始方法之前and之后
确定:原始方法怎么执行
methodeINvocation.proceed();
参数:MethodInvocation methodInvocation:额外功能所增加给的那个原始方法
返回值:原始方法的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("+++++++++++额外功能运行在之前+++++++++");
Object proceed = methodInvocation.proceed();
return proceed;
}
}
额外功能运行在原始代码方法执行之后
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object proceed = methodInvocation.proceed();
System.out.println("+++++++++++额外功能运行在之后+++++++++");
return proceed;
}
额外功能运行在原始代码方法执行之前、之后。
什么样的额外功能,运行在原始方法之前,之后都要添加? 例:事务
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("+++++++++++额外功能运行在之前+++++++++");
Object proceed = methodInvocation.proceed();
System.out.println("+++++++++++额外功能运行在之后+++++++++");
return proceed;
}
额外功能运行在原始方法抛出异常的时候
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object proceed = null;
try {
proceed = methodInvocation.proceed();
} catch (Throwable throwable) {
System.out.println("+++++++++++ 抛出异常时的 额外功能+++++++++");
throwable.printStackTrace();
}
return proceed;
}
MethodInterceptor影响原始方法的返回值
原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值
MethodInterceptor影响原始方法的返回值
Invoke方法的返回值,不要直接返回原始方法的运行结果即可。
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("+++++++++++额外功能+++++++++");
Object proceed = methodInvocation.proceed();
return false;
}
2.切入点详解
切入点决定额外功能加入位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..)) --->匹配了所有方法 a b c
1.execution() 切入点函数
2.* *(..) 切入点表达式
2.1切入点表达式
①、方法切入点表达式
* *(..) -->所有方法
* --->修饰符 返回值
* --->方法名
() --->参数表
.. --->对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型的都行)
- 定义login方法的切入点
* login(..) #定义register作为切入点 * register(..)
- 定义long方法且login方法有两个字符串类型的参数 作为切入点
* login(String,String) #注意:非Java.lang包中的类型,必须要写全限定名 * register(com.xxx.xxx.User) # ..可以和具体的参数类型连用 * login(String,..) -> login(String)/login(String,String)/login(String,com.xxx.User)
- 精准方法切入点限定
修饰符 返回值 包.类.方法(参数) * com.xxx.xxx.login() * com.xxx.xxx.login(String,String)
②、类切入点
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上额外的功能
- 语法1
#类中所有的方法加入了额外功能 * com.xx.xx.UserServiceImpl.*(..)
- 语法2
#忽略包 1.类只存在一级包 com.UserServiceImpl * *.UserServiceImpl.*(..) 2.类存在多级包 com.xxx.xxx.xxx.UserServiceImpl * *..UserServiceImpl.*(..)
③、包切入点 实战
指定包作为额外功能加入的位置,自然包中所有类及其方法都会加入额外功能
-
语法1
#切入包中的所有类,必须在xxx中,不能在xxx包的子包中 * com.a.xxx.*.*(..)
-
语法2
#切入点当前包及其子包都生效 * com.a.xxx..*.*(..)
2.2切入点函数
切入点函数:用于执行切入点表达式
①、execution
最为重要的切入点函数,功能最全
执行 方法切入点表达式 类切入点表达式 包切入点表达式
弊端:execution执行切入点表达式,书写麻烦
execution(* com.xxx.xxx..*.*(..))
注意: 其他的切入点函数 简化execution书写复杂度,功能上完全一致
②、args
作用:主要用于函数(方法)参数的匹配
切入点:例如---方法参数必须得是2个字符串类型的参数
execution(* *(String,String))
args(String,String)
③、within
作用:主要用于进行类、包切入点表达式的匹配
切入点:UserServiceImpl这个类
execution(* *..UserServiceImpl.*(..))
within(*..UserServiceImpl)
execution(* com.xx.xx..*.*(..))
within(com.xx.xx..*)
④、@annotation
作用:为具有特殊注解的方法加入额外功能
<aop:pointcut id="pc" expression="@annotation(com.xx.Log)"/>
⑤、切入点函数的逻辑运算
指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求
- and与操作
案例:login 同时 参数2个字符串
1.exection(* login(String,String))
2.exection(* login(.)) and args(String,String)
注意 与操作不同用于同种类型的切入点函数
案例:register方法和login方法作为切入点
exection(* login(..)) or execution(* register(..))
- or或操作
案例:register方法和login方法作为切入点
exection(* login(..)) or execution(* register(..))
四、AOP编程
1.AOP编程
AOP(Aspect Oriented Programming) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点+额外功能
OOP(Object Oriented Programming) 面向对象编程 JAVA
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建
POP(Procedure Oriented Programming) 面向过程(方法、函数)编程 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.newProxyInstance方法参数详解
- 编码
public class TestJDKProxy {
public static void main(String[] args) {
//1.创建原始对象
UserServiceImpl userService = new UserServiceImpl();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====log====");
//原始方法运行
Object invoke = method.invoke(userService, args);
return invoke;
}
};
//2.JDK创建动态代理
UserService userService1 =(UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),userService.getClass().getInterfaces(),invocationHandler);
userService1.login("bugz","123");
userService1.register(new User("bugz","123"));
/*
细节问题:
1.借用类加载器 UserService/TestJDKProxy
2.JDK8.x前 需要这样写 final UserServiceImpl userService = new UserServiceImpl();
*/
}
2.2 CGlib动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)
- 编码
import com.myspring.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TestCglib {
public static void main(String[] args) {
//1.创建原始对象
UserService userService = new UserService();
/*2.通过cglib的方式创建动态代理对象
JDK创建代理方式 Proxy.newProxyInstance(classloader,interfaces,invocationhandler)
Enhancer.setClassLoader();
Enhancer.setSuperClass();
Enhancer.setCallback();---->MethodInterceptor(cglib)
Enhancer.create()--->代理
*/
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("------cglib new log--------");
Object invoke = method.invoke(userService, args);
return invoke;
}
};
enhancer.setCallback(methodInterceptor);
UserService userServiceProxy=(UserService) enhancer.create();
userServiceProxy.login("bugz","123456");
userServiceProxy.register(new User());
}
}
- 总结
1.JDK动态代理 Proxy.newProxyInstance() ------------通过接口创建代理的实现类
2.Cglib动态代理 Enhancer---------------------------------通过继承父类创建代理类
3.Spring工厂如何加工原始对象
- 思路分析
- 编码
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;
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null;
}
//使用jdk代理模拟工厂的代理
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---模拟代理的额外功能---");
System.out.println(beanName);
Object invoke = method.invoke(bean, args);
return invoke;
}
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),invocationHandler);
}
}
<!--原始对象-->
<bean id="userService" class="com.myspring.factory.UserServiceImpl"/>
<!--模拟代理
1.实现BeanPostProcessor进行加工
2.配置文件中对BeanPostProcessor进行配置
-->
<bean id="beanPostProcessor" class="com.myspring.factory.ProxyBeanPostProcessor"/>
六、基于注解的AOP编程
1.基于注解的AOP开发步骤
1.原始对象
2.额外功能
3.切入点
4.组装切面
#通过切面类 定义了 额外功能@Around
定义了切入点@Around("execution(* login(..))")
@Aspect 切面类
/*
1.额外功能
public class MyAround implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
Object ret = invocation.proceed();
return ret;
}
}
2.切入点
<aop:config
<aop:pointcut id="" expression="execution(* login(..))"/>
*/
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---切面类额外功能");
Object proceed = joinPoint.proceed();
return proceed;
}
}
<!--原始对象-->
<bean id="userService" class="com.myspring.aspectproxy.UserServiceImpl"/>
<!--
切面
1.额外功能
2.切入点
3.组装切面
-->
<bean id="arround" class="com.myspring.aspectproxy.MyAspect"/>
<!--告知Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy/>
2.切入点复用
(1)切入点复用
#切入点复用:在切面类中定义一个函数 上面加@Pointcut注解 通过这种方式,定义切入点表达式,后续更加有利于切入点复用。
@Aspect
public class MyAspect {
@Pointcut("execution(* login(..))")
public void pointcut(){
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---切面类额外功能111");
Object proceed = joinPoint.proceed();
return proceed;
}
@Around(value = "pointcut()")
public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---切面类额外功能222");
Object proceed = joinPoint.proceed();
return proceed;
}
}
(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:config>
七、AOP开发中的一个坑
坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,就要ApplicationContextAware获得工厂,进而获得代理对象。
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register");
/*
调用的是原始方法的login方法--->核心功能
设计的目的:代理对象的log方法 ——————>额外功能+核心功能
ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/applicationContext2.xml");
UserService userService =(UserService) classPathXmlApplicationContext.getBean("userService");
userService.login();
Spring工厂重量级资源 一个应用中 应该只创建一个工厂
*/
UserService userService = (UserService) ctx.getBean("userService");
userService.login("bugz", "123444");
}
@Override
public void login(String name, String password) {
System.out.println("UserServiceImpl.login");
}
}