代理模式概述
代理模式是Java开发中使用较多的一种设计模式。
代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
代理类似中介的身份
应用场景:
安全代理
:屏蔽对真实角色的直接访问。
远程代理
:通过代理类处理远程方法调用(RMI)
延迟加载
:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类
静态代理
(静态定义代理类):专门针对某个接口
动态代理
(动态生成代理类):不显式的写代理类,JDK自带的动态代理,需要反射等知识
一、静态代理模式
实现步骤
1、创建一个类NetWorkTest
2、创建一个接口NetWork
,其中有browse()方法,需要被代理类和被代理类共同实现
3、创建被代理类Server
实现NetWork接口,重写其中的browse()方法
4、创建代理类ProxyServer
实现NetWork接口。声明变量NetWork、提供代理类ProxyServer构造器、重写的browse()方法调用NetWork.browse()
5、使用时:new一个真实的被代理类server、new一个代理类ProxyServer传入server、调用ProxyServer的browse()方法
不直接使用server.browse()的好处就是,不止可以调用server.browse()方法,proxyServer.browse()中还能做些其它的事,比如信息校验等
//1、创建一个类NetWorkTest
public class NetWorkTest {
public static void main(String[] args) {
//new一个真实的被代理类server
Server server = new Server();
//server.browse();
//相当于server赋予了ProxyServer中的NetWork
//因为server和ProxyServer都实现了NetWork,所以server可以当作NetWork传入构造器ProxyServer(NetWork work),实际调用的是实现NetWork 的server.browse()
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
//代理类和被代理类共同实现的接口NetWork
interface NetWork{
public void browse();//浏览的方法
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
//声明变量
private NetWork work;
//提供当前代理类的构造器
public ProxyServer(NetWork work){
this.work = work;//对work进行初始化
}
//校验的方法
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();//先调用check方法校验
work.browse();//真实的情况下使用work进行browse操作
}
}
二、动态代理模式
📖学习地址:https://www.bilibili.com/video/BV1Vf4y127N5?p=25&vd_source=461545ff50a35eaeaa8218ecdc5f7152
动态代理主要是Spring的两大特点之一AOP,即面向切面编程
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
什么是 AOP
1、通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
2、通常使用AspectJ框架实现AOP
主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑中划分出来,通过对这些行为的分离,我们希望可以将他们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
使用登录例子说明 AOP:
普通的登录功能添加权限控制时,需要修改原代码。创建新的权限判断模块,通过AOP加到原有的登录流程中可以不改变原代码
1、AOP 使用动态代理的两种情况
代理:创建被增强对象的代理对象,通过代理对象将功能做到
第一种 有接口情况,使用 JDK 动态代理
创建接口实现类UserDaoImpl代理对象,增强类的方法
第二种 没有接口情况,使用 CGLIB 动态代理
创建子类Person的代理对象,增强类的方法
2、底层原理(JDK动态代理实现)
基本逻辑
1、创建接口UserDao
2、创建接口的实现类UserDaoImpl
3、创建代理对象UserDaoProxy实现InvocationHandler接口,里面写增强的方法
4、调用Proxy.newProxyInstance传入(类加载器,实现的接口UserDao,实现InvocationHandler这个接口的类UserDaoProxy)
1.创建接口,定义方法
创建接口UserDao
public interface UserDao {
public int add(int a,int b);//相加方法
public String update(String id);//直接返回方法
}
2.创建接口实现类,实现方法
创建实现类UserDaoImpl 实现UserDao接口
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {//相加方法
return a+b;
}
@Override
public String update(String id) {//直接返回方法
return id;
}
}
3.创建增强类实现InvocationHandler接口
创建增强类UserDaoProxy 实现InvocationHandler接口,里面编写增强的内容
//创建代理对象代码,里面写增强的逻辑,实现InvocationHandler接口
class UserDaoProxy implements InvocationHandler {
//1 把创建的是谁的代理对象,把谁传递过来
//UserDaoImpl
//有参数构造传递,new UserDaoProxy(userDao)传递对象
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//invoke里面写增强的逻辑
//三个参数proxy:就是代理对象,newProxyInstance方法的返回对象,method:调用的方法,args: 方法中的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
//被增强的方法执行
Object res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后执行...."+obj);
return res;
}
}
4.使用 Proxy 类创建接口代理对象
调用方法newProxyInstance 方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类
,这个类实现的接口UserDao ,支持多个接口
第三参数,实现InvocationHandler这个接口
,创建代理对象,写增强的部分
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();//创建的代理对象传到new UserDaoProxy(userDao)里
//创建接口实现类代理对象
//Proxy.newProxyInstance(类加载器,实现的接口,实现InvocationHandler这个接口的类)
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result:"+result);
}
}
3、AspectJ操作术语
- 连接点:类里面哪些方法可以被增强称为连接点
- 切入点:实际被真正增强的方法
- 通知(增强):实际增强的逻辑部分称为通知(增强)
通知有多种类型:
前置通知:增强的方法前面执行
后置通知:增强的方法后面执行
环绕通知:增强的方法前后都有执行
异常通知:出现异常执行
最终通知:类似finally - 切面:是动作,把通知应用到切入点的过程。例如将权限判断加入到登录方法的过程
Aspect(切面)
: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点)
:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点)
:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强)
:Advice 定义了在 Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象)
:织入Advice
的目标对象.。
Weaving(织入)
:将 Aspect
和其他对象连接起来, 并创建 Adviced object
的过程
4、AspectJ准备工作
1、Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分(AspectJ 不需要Spring 也能独立使用),独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作
2、基于 AspectJ 实现 AOP 操作
两种主要方式:
1.基于 xml 配置文件实现
2.基于注解方式实现(一般使用方式)
3、在项目工程里面引入 AOP 相关依赖
4、切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构: execution([权限修饰符] [返回类型] [类全路径] 方法名称 )
//*表示任意的修饰符,返回类型可以省略不写,参数列表..表示
//举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(..))
//举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
//举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
5、相同的切入点抽取
切入点表达式相同时可以抽取出来,在方法上面加入切入点注解@Pointcut,之后使用时只需要使用方法
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}
5、AspectJ注解开发-重点
1.编写配置文件
创建配置类,可以不需要xml 配置文件,都用注解做到
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})//开启注解扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//开启 Aspect 生成代理对象
public class ConfigAop {
}
2.创建被增强类
在类里面定义方法
//被增强类
@Component
public class User {
public void add() {
System.out.println("add.......");
}
}
3.创建增强类,配置不同类型的通知
编写增强的逻辑,在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//增强的类
@Component
@Aspect //生成代理对象
@Order(3)//有多个增强类多同一个方法进行增强,设置增强类优先级,@Order(数字类型值),数字类型值越小优先级越高,两个增强类都会执行
public class UserProxy {
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
//最终通知
@After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}
//异常通知
@AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}
//环绕通知
@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
}
}
4.配置xml文件方式-了解
可以作为配置类方式的替代,仅供参考,使用比较麻烦
在 spring 配置文件中,开启注解扫描
需要新加xmlns:context,xmlns:aop, xsi:schemaLocation
<?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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan basepackage="com.atguigu.spring5.aopanno"></context:component-scan>
<!-- 开启 Aspect 生成代理对象-->
<!-- 到类上面寻找@Aspect注解,有注解生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
6、AspectJ 配置文件开发-了解
1、创建两个类,增强类和被增强类,创建方法
package com.atguigu.spring5.aopxml;
public class Book {
public void buy() {
System.out.println("buy.............");
}
}
package com.atguigu.spring5.aopxml;
public class BookProxy {
public void before() {
System.out.println("before.........");
}
}
2、在 spring 配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
3、在 spring 配置文件中配置切入点
<!--配置 aop 增强-->
<aop:config>
<!--切入点,id起个名字-->
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>