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 //传入的新的参数去执行目标方法