代理模式

1 代理模式的产生
  • 额外功能

    1. 软件设计者的角度Service层不需要额外功能
    2. Service层的调用者则需要额外功能
    
    示例:
    1. 房东(软件设计者)出租房子,只想实现核心功能而不去实现额外功能
    	核心功能:
    		1.出租房屋
    		2.收钱
    	额外功能:
    		1.打广告
    		2.带客户看方
    2. 房客(软件调用者)则需要这些额外功能。
    此时房东和房客就会产生矛盾,应景而生中介(proxy)。房东不想实现的额外功能中介来实现
    
2 设计模式
2.1 概念
通过代理类,为原始类(目标类)增加额外的功能
2.2 好处
利于原始类的维护
2.3 示例场景
事务、日志、性能检测
2.4 核心要素
1. 原始类
2. 代理对象和原始对象实现相同的接口(代理类必须和原始类的方法保持一致,所以需要实现共同的接口)
3. 额外功能
3 静态代理
3.1 编码实现

在这里插入图片描述

3.2 存在问题
1. 每一个原始类都需要与之对应的代理类的实现,静态类文件数量过多,不利于项目管理
2. 额外功能难以维护,需要去修改每一个代理类的额外功能实现
4 动态代理
4.1 spring的动态代理
4.1.1 依赖jar包
org.springframework:spring-aop:5.1.14.RELEASE
org.aspectj:aspectjrt:1.8.8
org.aspectj:aspectjewaver:1.8.3
4.1.2 实现额外功能
/**
 * @author wl
 * @version 1.0
 * @Description 实现MethodBeforeAdvice接口重写before方法,实现原始方法之前的额外功能
 * @date 2021/1/20 19:36
 */
public class Before implements MethodBeforeAdvice {

    /**
     * @Description 需要把在原始方法执行之前运行的额外功能写在before方法中
     * @Param [method, objects, o]
     * @return
     */        
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("--- method before advice ----");
    }
}
4.1.3 原始类、共同接口
// 共同接口
public interface UserService {
    
    boolean login(String name, String password);
}

// 原始类
public class UserServiceImpl implements UserService {
    @Override
    public boolean login(String name, String password) {
        return true;
    }
}
4.1.4 定义切入点
切入点:额外功能加入的位置

<!-- spring的xml配置文件中定义切入点-->
<aop:config>
    <!-- 给所有方法加入额外功能 -->
	<aop:pointcut id="ex" expression="execution(* *(..))"
</aop:config>
4.1.5 组装
<!-- 将切入点和额外功能整合 -->
<bean name="before" class = "Before"></bean>
<aop:config>
    <!-- 给所有方法加入额外功能 -->
	<aop:pointcut id="ex" expression="execution(* *(..))"/>
    <!-- 指定额外功能和切入点的id -->
    <aop:advisor advice-ref="before" pointcut-ref="ex"/>
</aop:config>
4.1.6 调用
spring的工厂通过原始对象的id值获得的是代理对象
    <bean name="userService" class="UserServiceImpl"></bean>
    所以 getBean("userService")获得的是代理对象。
将获取的代理对象进行存储,可通过共同接口的类型声明
	UserService userService = (UserSerivce)applicationContext.getBean("userService");
4.2 spring动态代理细节
4.2.1 代理类存放位置
Spring框架在运行时,通过动态字节码技术,在JVM中创建的,代理类运行在JVM内部。生命周期和JVM一致
4.2.2 动态字节码
不存在静态java文件,也就无法编译成class文件。
动态字节码是通过第三方的动态字节码框架在JVM中创建。如:Cglib、ASM、Javassist
有了字节码就可在JVM中创建对象
4.2.3 动态代理好处
1. 不需要定义代理类文件,都是JVM运行过程中动态创建。不会造成类文件过程,而影响项目管理的问题。
2. 在额外功能不改变的情况下,只需要指定原始类,不再需要如静态代理般自己定义代理文件
3. 额外功能要改变的化,只需要重新实现Advice接口重新组装,不再需要如静态代理般每个代理都去修改
4.2.4 MethodBeforeAdivce详解
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
	System.out.println("--- method before advice ----");
}
1. 参数
	Method method
		额外功能所增加原始类的对应原始方法
	Object[] objects 
		method原始方法的所有参数
	Object o
		原始类对象
4.2.5 MethodInterceptor方法拦截器
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return invocation.proceed();
    }
}
MethodBeforeAdivce和MethodInterceptor的区别:
	前者只能在方法之前添加额外功能,后者则可以在方法的前后都可添加额外功能

1. 参数
	MethodInvocation invocation
		原始方法封装
2. 原始方法的调用
		invocation.proceed();
	注意:当前额外功能加在哪个原始方法上,此时的invocation.proceed()就代表运行调用该原始方法。
3. 额外功能
		写在invocation.proceed()之前和之后的代码就是额外功能
4. 返回值
	Object
		原始方法的返回值 -- 返回值为void的Object为Null
4.2.6 Adivce原理
1. 事务的实现(around) -- 原始方法之前开始事务,之后提交事务;
2. 原始方法抛出异常后的额外功能 -- 将额外功能写在invocation.proceed()方法调用的catch块中;
3. 无论是否有异常都执行的额外功能 -- 将额外功能写在finall代码块中.
4.3 切面详解
4.3.1 概念
连接点:在应用执行过程中能够插入切面的一个点
	所有的连接点就组成了整个切点
切点:定义了需要在哪些连接点上执行通知
	明确了执行在哪些方法上
切面:切点(在哪里) + 通知(什么时候)组成了切面。
	切面的类中定义了切点和通知
4.3.2 切入点表达式
表达式书写原理
示例:【* *(..)】【修饰符+返回值 包+类+方法名(参数类型、个数)】
代表所有方法:*为通配符,代表无要求,参数两个点代表参数无要求,即该表达式适配所有方法
4.3.2.1 表达式详解
1. 包切入点
	1.1 不包含包的子包中的资源 -- 匹配具体包中所有类的所有方法
		* com.examine.controller.*.*(..)
	1.2 包含包及其子包资源 -- 匹配具体包及其子包中所有类的所有方法
		* com.examine.controller..*.*(..)
2. 类切入点
	2.1 忽略包
	  2.2.1 类只存在一级包 -- com.UserController 匹配一级包目录下的具体类的所有方法
			* *.UserController.*(..)
	  2.2.2 类存在多级包 -- com.examine.controller.UserController 匹配多级包
			* *..UserController.*(..)
	2.2 具体包下某个具体类 -- 匹配类中所有方法
		* com.examine.controller.UserController.*(..)
3. 方法切入点
	3.1 具体包下的具体类中的具体方法
	  3.1.1 未知参数方法 -- 两个点代表多或者没有,类型无限制
		* com.examine.controller.UserController.login(..)
	  3.1.2 具体参数方法 -- 非java.lang包中类型的参数,必须写全限定类名
		* com.examine.controller.UserController.login(String,com.examine.entiry.user)
	3.2 忽略包忽略类 -- 匹配所有包所有类中的login方法
		* login(..)
4.3.3 切入点函数
1. execution - 功能最全的切入点函数
		可执行包、类、方法的切入点表达式
2. args
		主要用于方法参数的匹配
	示例:args(String,String)
		匹配所有具有两个String类型参数的方法
3. within
		主要用于类、包切入点表达式
	示例:within(*..UserController)
		匹配所有包下的UserController中的所有方法
	示例2:within(com.example.controller..*)
		匹配具体包下的所有类中的所有方法
4. @annotation注解
		为具体自定义注解的方法加入额外功能
	示例:expression="@annotation(com.example.annotation.Log)"
		为具有@Log注解的方法加入额外功能
4.3.4 切入点函数逻辑运算
1. and -- 与操作
	示例:expression="excution(* login(..)) and args(String,String)"
		匹配所有包所有类下具有login方法且有两个Sring类型的参数
注意:与操作不能用于同种类型的切入点函数
	示例:expression="excution(* login(..)) and excution(* register(..))"
		该操作会别解释为该方法名既得叫login又得叫register,最终两者都不会加入额外功能
2. or -- 或操作
	示例:expression="excution(* login(..)) or excution(* register(..))"
		匹配所有包所有类下具有login方法或者register方法
5 AOP的底层实现原理
5.1 动态代理的创建
5.1.1 JDK动态代理
1. 创建原始对象
	UserSercice userService = new UserServiceImpl();
2. JDK创建动态代理对象
	Object obj = Proxy.newProxyInstance(classloader,interfaces,invocationHandler);
		返回值:Object 代表生成的动态代理对象
		参数: 
			1.ClassLoader 由于动态代理对象没有class字节码文件,所以动态代理对象没有被JVM分配类加载器。但是又必须由ClassLooader为代理对象生成Class对象,所以就需要借用其他类的类加载器。
		  	2.interfaces 代理对象和原始对象实现的共同接口数组
		  	3.invocationHandler 额外功能,实现了InvocationHandler接口的类
3. 额外功能
	实现InvocationHandler接口,并重写其invoke(Object proxy,Method method,Object[] args)方法。
		参数:1.proxy 代表代理对象,一般忽略掉
			 2.method 额外功能所增加给的当前对象的原始方法
			 3.args 原始方法的参数
		返回值:Object 代表原始方法的返回值
5.1.1.1 编码实现
public class ProxyHandler {

	public static void main(String[] args) {
		// 内部类使用外部类的局部变量需要声明为final
        final UserService userService = new UserServiceImpl();

        // 内部类
		InvocationHandler ih = new InvocationHandler() {

			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println("------ Proxy before ----");
                /**
                 * 方法的调用需要三部分:对象+方法+参数
                 * method为原始方法,通过反射调用invoke(当前方法所属的对象,当前方法的参数)
                 * 反射示例:Method method = UserController.class.getMethod("login", 				   		String.class, String.class);
                   此时method就是userController的login方法。
                   method.invoke(userController,args);
                 * 对比:Spring封装的MethodInvocation方法拦截器的invocation.proceed();
                  		该方法是将method、方法所属对象、方法参数args进行了封装
                 */
                Object res = method.invoke(userService, args);
				System.out.println("------ Proxy after ----");
				return res;
			}
		};
        /**
         * 创建代理对象
         * Proxy.newProxyInstance方法根据其参数共同接口+额外功能,通过借用的类加载器使用动态字节码			 技术在JVM中创建动态代理类
         */
		UserService proxy =
            // 由于动态代理类没有静态java文件也就不会有.class文件。随即就不会为其产生类加载器,所以需要借用ClassLoader通过动态字节码技术在JVM创建动态代理类。非固定的某个类借用其他类的也可以
            (UserService)Proxy.newProxyInstance(UserService.class.getClassLoader(),				userService.getClass().getInterfaces(), ih);
		// 动态代理对象调用方法
        proxy.login();
	}
}
5.1.2 CGlib动态代理
1. 创建原始对象,不需要实现接口
	UserServiceImpl userServiceImpl = new UserServiceImpl();
2. CGlib创建动态代理对象
	Enhancer enhancer = new Enhancer();
	enhancer.create();
		Enhancer类需要注入的属性:
			1.ClassLoader 由于动态代理对象没有class字节码文件,所以动态代理对象没有被JVM分配类加载器。但是又必须由ClassLooader为代理对象生成Class对象,所以就需要借用其他类的类加载器。
		  	2.superclass 代理对象继承的父类
		  	3.MethodInterceptor 额外功能,实现了MethodInterceptor接口的类
3. 额外功能
	实现MethodInterceptor接口,并重写其intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)方法。
		参数:1.proxy 代理对象,一般忽略掉
			 2.method 额外功能所增加给的当前对象的原始方法
			 3.args 原始方法的参数
			 4.methodProxy 代理类对方法的代理引用
		返回值:Object 代表原始方法的返回值
5.1.2.1 编码实现
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 UserService {
   
    public static void main(String[] args) {
        // cglib创建动态代理的类
        Enhancer enhancer = new Enhancer();
        // 借用的类加载器
        enhancer.setClassLoader(UserService.class.getClassLoader());
        // 继承的父类
        enhancer.setSuperclass(UserService.class);
        // 此处的MethodInterceptor和Spring中使用的方法拦截器不是同一个接口
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            // 等同于 InvocationHandler.invoke()
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				System.out.println("------ Proxy before ----");
                Object res = method.invoke(userService,args);
                System.out.println("------ Proxy after ----");
                return res;
            }
        };
        // 额外功能
        enhancer.setCallback(methodInterceptor);
        // 生成的代理类 - 父类引用指向子类对象
        UserService userService = (UserService)enhancer.create();
        userService.login();
    }
}
5.2 JDK、CGlib区别
  • JDK动态代理需要原始类和代理类实现共同的接口,即通过接口创建代理的实现类
  • CGlib动态代理是直接继承原始类,重写原始类的方法通过super.原始方法()+额外功能去实现
5.3 BeanPostProcess
// 实现BeanPostProcess接口,Spring就是在这里使用AOP创建的代理类,并且通过原始类的id来获取代理类
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    ClassLoader classLoader = bean.getClass().getClassLoader();
    Class<?>[] interfaces = bean.getClass().getInterfaces();
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(bean,args);
        }
    };
    // 所以4.1.6中提到的Spring中通过原始对象的id获取到的是代理对象
    return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
6 基于注解的AOP开发

使用注解开发需要在配置类上标注开启此功能:@EnableAspectJAutoProxy

6.1 编码实现
import java.lang.reflect.Method;
import java.math.BigDecimal;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.sgcc.examine.anno.AnnoBusinessLog;
import com.sgcc.examine.entity.LogEntity;
import com.sgcc.examine.service.LogService;
import com.sgcc.isc.ualogin.client.util.IscSSOResourceUtil;

// 定义切面
@Aspect
// 加入IOC容器
@Component
public class BusinessLogAspect {

	private static final Logger logger = LoggerFactory.getLogger(BusinessLogAspect.class);
	@Autowired
	private LogService logService;

    // 定义切入点
	@Pointcut("@annotation(com.sgcc.examine.anno.AnnoBusinessLog)")
	public void businessLog() {
	}

    /**
     * 通知类型Around(组装切入点)
     * 方法内部实现额外功能
     */
	@Around("businessLog()")
	public void doAround(JoinPoint joinPoint) {
		String queryLogOpenStatus = logService.queryLogOpenStatus();
		if (!queryLogOpenStatus.equals("1")) {
            logger.info("业务日志未开启");
            return;
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        try {
            if (method != null)
                addBusinessLog(method);
        } catch (Exception e) {
            logger.warn("类:" + signature.getDeclaringTypeName() + ",方法名:" + method + ",业务日志保存失败!!!" + e);
        }
	}

	private void addBusinessLog(Method method) throws Exception {
        // 获取方法上的特定注解
		AnnoBusinessLog annotation = method.getAnnotation(AnnoBusinessLog.class);
		if (annotation != null) {
			LogEntity logEntity = new LogEntity();
            // 获取特定注解声明时的属性值
			logEntity.setDescription(annotation.description());
			logService.saveLog(logEntity);
		}
	}
}
6.2 通知详解

五种通知执行顺序
在这里插入图片描述
在这里插入图片描述

// 目标方法执行前通知
@Before("businessLog()")
public void doBefore(JoinPoint joinPoint) {
	
}

// 目标方法执行后通知(无论是否正常返回)
@After("businessLog()")
public void doAfter(JoinPoint joinPoint) {
	
}

// 目标方法正常返回通知,result为其返回值映射对象,对应参数名
@AfterReturning(value="businessLog()",returning="result")
public void doAfterReturning(JoinPoint joinPoint,Object result) {
	
}

// 目标方法异常时通知,ex为其异常映射对象,对应参数名
@AfterThrowing(value="businessLog()",throwing="ex")
public void doAfterThrowing(JoinPoint joinPoint,Exception ex) {
	
}

// 环绕通知
@Around("businessLog()")
public void doAround(JoinPoint joinPoint) {
	
}
6.3 JoinPoint 详解

注意:JoinPoint joinPoint参数必须写在方法参数列表的第一位,否则无法识别报错

1. JoinPoint对象常用API
	Signature getSignature();
		获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
	Object[] getArgs();	
		获取传入目标方法的参数对象
	Object getTarget();	
		获取被代理的对象
	Object getThis();
		获取代理对象
2. Signature对象常用API
	目标方法名: 
		joinPoint.getSignature().getName();
	目标方法所属类
		joinPoint.getSignature().getDeclaringType();
	目标方法所属类的简单类名:
    	joinPoint.getSignature().getDeclaringType().getSimpleName());
    目标方法所属类的类名
    	joinPoint.getSignature().getDeclaringTypeName());
    目标方法声明类型
    	Modifier.toString(joinPoint.getSignature().getModifiers()));
	获取传入目标方法的所有参数
        Object[] args = joinPoint.getArgs();
3. ProceedingJoinPoint对象
		ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
添加了以下两个方法:
	Object proceed() throws Throwable //执行目标方法 
	Object proceed(Object[] args) throws Throwable //传入的新的参数去执行目标方法 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值