文章目录
1.代理机制
一般采用代理模式,主要的目的就是为了解耦,将公共的通用的方法(功能/业务)放到代理对象中,由业务层专注于业务执行即可。
1.2特点
1.为什么使用代理? 因为自己不方便(没有资源)
2.代理的作用? 代理要解决(扩展)某些实际问题.
3.用户最终执行目标方法!
1.3JDK代理
1.JDK代理模式是java原生提供的API,无需导包
2.JDK代理要求: 被代理者必须 要么是接口,要么实现接口
3.灵活: 代理对象应该看起来和被代理者一模一样!!! (方法相同)
1.3.1代理类
package com.jt.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy {
//传入target目标对象获取代理对象的
//利用代理对象 实现方法的扩展
public static Object getProxy(Object target){
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口数组类型
Class[] interfaces = target.getClass().getInterfaces();
//3.代理对象执行方法时的回调方法(代理对象调用方法时,执行InvocationHandler)
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler(target));
}
//要求必须传递目标对象
public static InvocationHandler invocationHandler(Object target){
return new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事务开启");
//获取目标方法的返回值
Object result = method.invoke(target,args);
System.out.println("事务提交");
return result;
}
};
}
}
1.3.2测试类
package com.jt;
import com.jt.config.SpringConfig;
import com.jt.proxy.JDKProxy;
import com.jt.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring {
@Test
public void demo1(){
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//获取目标对象
UserService userService = context.getBean(UserService.class);
//userService.addUser();
//获取代理对象
UserService proxy = (UserService) JDKProxy.getProxy(userService);
//代理对象执行方法, 调用invoke方法
proxy.addUser();
}
}
1.4 CGLIB代理
jdk代理: 要求必须有/实现接口. 如果没有接口,则JDK代理不能正常执行.
cglib代理: 要求被代理者有无接口都可以,代理对象是目标对象的子类 重写子类方法
1.4.1代理类
package com.jt.proxy;
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 CGlibProxy {
public static Object getProxy(Object target){
//1.创建增强器对象
Enhancer enhancer = new Enhancer();
//2.设定父级 目标对象
enhancer.setSuperclass(target.getClass());
//3.定义回调方法 代理对象执行目标方法时调用
enhancer.setCallback(getMethodInterceptor(target));
//4.创建代理对象
return enhancer.create();
}
//需要传递target目标对象
public static MethodInterceptor getMethodInterceptor(Object target){
return new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("事务开始");
//执行目标方法
Object result = method.invoke(target,args);
System.out.println("事务提交");
return result;
}
};
}
}
1.4.2测试类
@Test
public void demo2(){
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//获取目标对象
UserService userService = context.getBean(UserService.class);
UserService proxy = (UserService) CGlibProxy.getProxy(userService);
System.out.println(proxy.getClass());
proxy.addUser();
}
2.Spring Aop
需要导入依赖:
<!--引入AOPjar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.1Aop介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展。
2.2Aop中注解
@EnableAspectJAutoProxy //开启AOP
@Aspect //标识该类是一个切面
@Pointcut(“bean(userServiceImpl)”)
2.3常见通知类型
第一类:
- @Before(“pointcut()”)
- @AfterReturning(“pointcut()”)
- @AfterThrowing(“pointcut()”)
- @After(“pointcut()”)
可以记录程序执行的各个过程. 为日志提供记录.
第二类: - @around环绕通知 可以控制目标方法是否执行. 环绕通知可以控制业务流转的过程!!!
例子: - 权限的校验
- 缓存系统
- 异常的处理等
package com.jt.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component //将给类交给Spring容器管理 !!!
@Aspect //标识该类是一个切面
@Order(1) //AOP第一个执行 数字越小越靠前
public class SpringAOP {
/**
* 知识回顾: AOP利用动态代理扩展目标方法.
* 公式: 切面 = 切入点表达式 + 通知方法
* 切入点表达式: 如果目标对象满足切入点表达式的判断(if),则spring自动为其创建代理对象
* 通知方法: 对目标方法进行扩展的封装方法.
* 目标对象的bean的ID: userServiceImpl
* 切入点表达式:
* 1. bean("bean的ID")
* AOP规则: 如果目标对象满足切入点表达式,则执行通知方法
*/
@Pointcut("bean(userServiceImpl)")
public void pointcut(){
}
//1.前置通知: 在目标方法执行之前执行.
@Before("pointcut()")
public void before(){
System.out.println("我是前置通知!!!!");
}
//2.后置通知: 在目标方法执行之后执行
@AfterReturning("pointcut()")
public void afterReturn(){
System.out.println("我是后置通知!!!!");
}
//3.异常通知: 目标方法执行报错时,执行该通知
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("我是异常通知!!!!");
}
//4.最终通知: 目标方法之后都要执行的通知
@After("pointcut()")
public void after(){
System.out.println("最终通知都要执行");
}
//5.重点掌握 环绕通知: 在目标方法执行前后都要执行. 控制目标方法
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行前!!!!");
//底层调用动态代理的invoke方法,执行目标方法
Object result = joinPoint.proceed();
System.out.println("环绕通知执行后!!!!");
return result;
}
}
2.4 切入点表达式
2.4.1 bean表达式
根据bean的ID拦截指定的对象。
@Pointcut("bean(userServiceImpl)")
public void pointcut(){
}
2.4.2 within表达式
按照类型匹配,可以使用通配符*号。
1. @Pointcut("within(com.jt.service.UserServiceImpl)")
只拦截UserServiceImpl的类
2. @Pointcut("within(com.jt.service.*)")
拦截com.jt.service下的一级的类.
3. @Pointcut("within(com.jt.service..*)")
拦截com.jt.service下所有包下的所有类
4. @Pointcut("within(com.*.service..*)")
拦截com.任意包.service下所有包下的所有类
2.4.3 execution表达式
粒度比较细,可以按照方法参数进行匹配。
语法: @Pointcut("execution(返回值类型 包名.类名.方法名(参数列表))")
1. 按照类型方法匹配
@Pointcut("execution(* com.jt.service.UserServiceImpl.addUser())")
2. 要求返回值任意, com.jt.service包下的所有的子孙类中的任意方法的任意参数要求拦截.
@Pointcut("execution(* com.jt.service..*.*(..))")
3. 要求返回值任意, com.jt.service包下的所有的子孙类中的add开头的方法并且参数1个是int类型 进行拦截
@Pointcut("execution(* com.jt.service..*.add*(int))")
2.4.4 @annotation表达式
可以根据用户的自定义注解进行拦截。
1.完成自定义注解
package com.jt.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) //注解运行期有效
@Target(ElementType.METHOD) //注解标识 方法
public @interface Lyj {
}
2.标记注解
3.AOP拦截注解
3.总结
关于代理对象生成策略说明
默认策略:
- Spring中默认采用的动态代理的规则是JDK代理,如果被代理者没有接口.则自动使用CGLIB
- 如果需要修改为cglib代理,则添加如下代码
@Configuration
@ComponentScan("com.jt") //包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启AOP
public class SpringConfig {
}
SpringBoot中默认代理模式采用CGLIB代理.如果需要修改为JDK代理则需要修改配置文件即可。