文章目录
- 1.Spring_AOP铺垫
- 2代理模式
- 3 Spring AOP
1.Spring_AOP铺垫
1创建新项目
file=>project=>spring Initializr=>Spring Web=>finish
2AOP项目代码铺垫
2.1业务说明
**事务特性:**原子性/一致性/隔离性/持久性
**业务说明:**在增删改的操作过程中添加事务控制
Demo代码的演示:
结论:
- 如果按照上述的代码进行编辑,则所有的增删改操作的代码都必须按照上述的规则,代码冗余
- UserService与事务控制代码紧紧的耦合在一起,不方便以后扩展,应该尽可能保证业务的纯粹性
2.2代理模式说明
说明:在**业务层我们不方便,但又不得不做的事情,**可以放到代理对象中,通过这样的设计,我们就可以解决业务层耦合的问题,代理对象看起来和真的对象一摸一样,所以用户使用不会察觉
2.3 动态代理-JDK动态代理
package com.jt.demo1.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy {
/**
* 获取代理对象
* 参数说明:
* 1. ClassLoader loader 类加载器 读取真实的类数据
* 2. Class<?>[] interfaces, 要求传递接口信息
* 3. InvocationHandler h 当代理对象执行方法时 执行
* 注意事项: JDK代理必须要求 "被代理者"要么有接口(本身就是接口),要么实现接口(实现类).
*/
public static Object getProxy(Object target){
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口
Class[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader,interfaces,getInvocationHandler(target));
}
//代理对象执行方法时调用
public static InvocationHandler getInvocationHandler(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;
}
};
}
}
2.4 业务代码测试
package com.jt.demo1;
import com.jt.demo1.config.SpringConfig;
import com.jt.demo1.proxy.JDKProxy;
import com.jt.demo1.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTx {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
System.out.println(userService.getClass());
//获取代理对象
UserService proxy = (UserService) JDKProxy.getProxy(userService);
System.out.println(proxy.getClass());
//基于代理对象,执行业务操作 实现方法扩展
proxy.addUser();
proxy.deleteUser();
}
}
2代理模式
2.1JDK的动态代理特点
- 类型的名称: class com.sun.proxy.$Proxy9
- 要求 要求被代理者,必须是接口或是实现类
- JDK代理是java原生提供的API无需导包
- JDK动态代理在框架的源码经常使用
2.2动态代理机制-CGLIB
2.2.1Cglib代理特点说明
-
**历史原因:**JDK动态代理要求必须要有接口参与,但是某些类没有接口,所以无法使用JDK代理生成代理对象,所以为了填补空缺,则引入Cglib代理
-
说明:Cglib动态代理要求有无接口都可以创建对象,但如何保证和被代理者"相同"?
- 答案:通过cglib动态代理继承被代理者==>即代理对象是被代理者的子类
2.2.2动态代理的作用
说明1:一般我们将业务层中耦合性高的代码,采用动态代理的方式进行解耦,使得程序具有扩展性
说明2:Spring专门针对动态代理的规则,封装了一套API==>起名AOP
IOC+DI==>
动态代理===>业务逻辑解耦
3 Spring AOP
3.1AOP介绍
-
面向切面编程:通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
-
作用: 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
3.2AOP中/专业术语(难点)
- 连接点: 用户可以被扩展的方法
- 切入点: 用户实际扩展的方法
- 通知: 扩展方法的具体体现
- 切面: 将通知应用到切入点的过程
3.3 AOP的入门案例
3.3.1引入AOPjar包
<!--引入AOPjar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.3.2切入点表达式
- bean(“对象的Id”)
- within(“包名.类名”)
- execution(返回值类型 包名.类名.方法名(参数列表))
- @annotation(注解的路径)
3.3.3定义切面类
知识点1
注解:
- @Component //将当前类交给Spring容器管理
- @Aspect //我是一个切面类
- @Pointcut //定义切面表达式
知识点2
- 切面=切入点表达式+通知方法
- 切入点:
- 理解: 可以理解就是一个if判断
- 判断条件: 切入点表达式
- 规则:
- 如果满足表达式 则判断为true,则执行通知方法
- 如果满足不表达式 则判断为false,则不执行通知方法
代码
-
package com.jt.demo2.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; @Component //将当前类交给Spring容器管理 @Aspect //我是一个切面类 public class SpringAOP { @Pointcut("bean(userServiceImpl)") public void pointcut(){ } @Before("pointcut()") public void before(){ System.out.println("你好,我是前置通知"); } }
3.3.4让AOP生效
- **说明: **编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效
3.3.5编辑测试类
知识点
-
UserService userService = context.getBean(UserService.class);
-
理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
-
package com.jt.demo2; import com.jt.demo2.config.SpringConfig; import com.jt.demo2.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Spring_AOP { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); //理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象 UserService userService = context.getBean(UserService.class); //如果是实现类对象,则方法没有被扩展 //如果是代理对象, 则方法被扩展 aop有效的 System.out.println(userService.getClass()); userService.addUser(); } }
3.3.6AOP形象化的比喻
- 说明: AOP是一种抽象的一种概念,看不见/摸不着.
- AOP就是为了给特定类添加一定的扩展且不动源代码所产生的=>通过四种表达式拦截想要的扩展的对象,从而不去破坏运来程序的结构
3.4切入点表达式解析
3.4.1bean(“对象的id”)
- Pointcut(“bean(userServiceImpl)”) 只匹配ID为userServiceImpl的对象
3.4.2within(“包名.类名”)
- @Pointcut(“within(com.jt.demo2.service.*)”) 匹配com.jt.demo2.service下面的所有的对象==>"*"号代表通配符
说明:上面两种操作都是粗粒度的,===>按类匹配
3.4.3execution(返回值类型 包名.类名.方法名(参数列表))
-
@Pointcut(“execution(* com.jt.demo2.service**…***.*(…)))”)
-
匹配任意返回值的 在com.jt.demo2.service包下面的**子子孙孙包中**的不限参数类型的方法
-
@Pointcut(“execution(* com.jt.demo2.service**.***.*(…)))”)
-
匹配任意返回值的 在com.jt.demo2.service包下**子包中**不限参数类型的方法
-
@Pointcut(“execution(* com.jt.demo2.service**…***.add*(…)))”)
-
匹配任意返回值的 在com.jt.demo2.service包下面的子子孙孙包的中不限参数类型的以add开头的方法
-
"*":任意返回类型 "..*":子包及其子孙包 "(..)":任意参数类型 "add*(..)":不限参数且以add开头的方法
3.3.4@annotation(注解的路径)
-
定义注解类
-
@Target(ElementType.METHOD) //注解对方法有效 @Retention(RetentionPolicy.RUNTIME) //运行期有效 public @interface cgb2110 { //注解起标记作用 }
2.切点表达式写法
- @Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
- 拦截有该注解的对象
3. 注解解释
-
元注解: @Target: 注解用在哪里:类上、方法上、属性上等等
-
ElementType.TYPE 应用于类的元素 ElementType.METHOD 应用于方法级 ElementType.FIELD 应用于字段或属性(成员变量) ElementType.ANNOTATION_TYPE 应用于注解类型 ElementType.CONSTRUCTOR 应用于构造函数 ElementType.LOCAL_VARIABLE 应用于局部变量 ElementType.PACKAGE 应用于包声明 ElementType.PARAMETER 应用于方法的参数
-
元注解: @Retention 注解的生命周期:源文件中、字节码文件中、运行中
SOURCE 在源文件中有效(即源文件保留) CLASS 在class文件中有效(即class保留) RUNTIME 在运行时有效(即运行时保留
-
3.5动态获取注解参数
3.5.1 定义注解
知识点
-
@target 表示注释用在哪里
-
@Retention 表示注解生命周期
-
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Find { int id() default 0; }
使用注解
3.5.2 需求
利用前置通知,打印注解中的Id值!!!
2.5.3 编辑切面类
知识点
-
/** * 知识点: * 1.如果切入点表达式只对当前通知有效.则可以按照如下方式编辑 * 要求: 动态的拦截Find注解,并且要获取Find注解中的参数Id * 难点: 动态获取注解的对象!! * 代码解释: * 1.@annotation(find) 拦截find变量名称对应类型的注解 * 2.当匹配该注解之后,将注解对象当做参数传递给find * 优势: 可以一步到位获取注解的内容.避免了反射的代码 */ @Before("@annotation(find)") public void before2(Find find){ System.out.println("ID的值为:"+find.id()); }
3.6 通知方法
3.6.1关于通知方法解析
- 前置通知 在目标方法执行之前执行
- 后置通知 在目标方法执行之后执行.
- 异常通知 在目标方法执行之后抛出异常时执行.
- 最终通知 都要执行的通知
说明: 上述的四大通知一般用于记录程序的运行状态.只做记录.
5.环绕通知 在目标方法执行前后都要执行的通知
3.6.2前置通知案例
@Before("pointCut()")
public void Before(JoinPoint joinPoint) {
//1.获取目标对象的类型
Class tagetClass = joinPoint.getTarget().getClass();
//2.获取目标对象的路径
String path = joinPoint.getSignature().getDeclaringTypeName();
//3.目标对象的方法名
String methodName = joinPoint.getSignature().getName();
//4.目标对象的方法参数
Object[] args = joinPoint.getArgs();
System.out.println("对象的类型" + tagetClass);
System.out.println("对象的路径" + path);
System.out.println("对象的方法名" + methodName);
System.out.println("对象的方法参数" + Arrays.toString(args));
}
3.6.3后置通知案例
3.6.3.1AOP设置
//注意: 如果多个参数,joinPoint必须位于第一位
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
//如果获取当前方法信息,则通过joinPoint获取
System.out.println("我是后置通知,返回值是:" + result);
}
3.6.3.2接口设置
3.6.3.3编辑实现类
3.6.3.4编辑测试案例
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
UserService userService = context.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象, 则方法被扩展 aop有效的
System.out.println(userService.getClass());
userService.addUser();
userService.findCount(); //测试带返回值的方法
}
3.6.4异常通知案例
//后置通知与异常通知互斥,只能存在一个
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrow(JoinPoint joinPoint, Exception exception) {
//打印异常
// exception.printStackTrace();
System.out.println("异常信息:" + exception.getMessage());
}