SpringBoot下的Spring——DAY04——动态代理总结、AOP、自定义注解进行拦截、动态获取注解参数、通知方法(内含源代码)
源代码下载链接地址:
https://download.csdn.net/download/weixin_46411355/87549575
目录
- SpringBoot下的Spring——DAY04——动态代理总结、AOP、自定义注解进行拦截、动态获取注解参数、通知方法(内含源代码)
- `源代码下载链接地址:`[https://download.csdn.net/download/weixin_46411355/87549575](https://download.csdn.net/download/weixin_46411355/87549575)
- 1.动态代理总结
- 1.1 JDK动态代理特点
- 1.2 CGlib动态代理
- 1.2.1 CGLib特点说明
- 1.3 动态代理的作用
- 2 Spring中的AOP
- 2.1 AOP介绍
- 2.2 AOP中专业术语(难点)
- 2.3 AOP 入门案例
- 2.3.1 创建一个SpringBoot的module
- 2.3.1 导入jar包
- 2.3.2 项目工程结构
- 2.3.3 配置类
- 2.3.4 Service层
- 2.3.4.1 接口
- 2.3.4.2 实现类
- 2.3.5 切入点表达式
- 2.3.6 定义切面类
- 2.3.7 让AOP生效
- 2.3.8 编辑测试类
- 2.4 AOP形象化的比喻
- 2.5 关于切入点表达式解析
- 2.5.1 bean标签写法
- 2.5.2 within表达式
- 2.5.3 execution表达式
- 2.6 按照自定义注解进行拦截
- 2.6.1 自定义注解
- 2.6.2 切入点表达式写法
- 2.6.3 在service层实现类UserServiceImpl的addUser()方法上添加自定义的注解
- 2.7 动态获取注解参数
- 2.7.1 自定义注解
- 2.7.2 使用注解
- 2.8.3 需求
- 2.8.4 编辑切面类
- 2.8 通知方法
- 2.8.1 关于通知方法解析
- 2.8.2 前置通知案例
- 2.8.3 后置通知案例
- 2.8.3.1 添加接口方法
- 1.编辑接口
- 2.编辑实现类
- 2.8.3.2 编辑AOP切面类SpringAOP
- 2.8.3.3 编辑测试案例
- 2.8.3.4 测试效果
- 2.8.4 异常通知案例
- 2.8.4.1 让service层实现类代码报错
- 2.8.4.2 异常通知案例
- 2.8.4.3 测试结果
- 常用注解
1.动态代理总结
1.1 JDK动态代理特点
- 类型名称: class com.sun.proxy.$Proxy9
- 要求: 要求被代理者,必须是接口或者是实现类.
- JDK代理是java原生提供的API 无需导包.
- JDK动态代理在框架的源码中经常使用.
- 代理类和被代理类继承相同的接口,所以两者为兄弟关系
1.2 CGlib动态代理
1.2.1 CGLib特点说明
历史原因: JDK动态代理要求必须"有接口",但是某些类它没有接口,则无法使用JDK代理生成代理对象. 所以为了填补知识的空缺,则引入cglib代理.
问题说明: cglib动态代理 要求有无接口都可以创建代理对象. 问题? 如何保证和被代理者"相同"
答案(特点): 要求cglib动态代理继承被代理者.代理对象是被代理者的子类.
代理类和被代理类(目标类)两者是父子关系,代理对象是目标类的子类。
1.3 动态代理的作用
说明1: 一般我们将业务层中的耦合性高的代码,采用动态代理的方式进行解耦.使得程序更加具有扩展性. (业务逻辑的解耦)
说明2: Spring专门针对动态代理的规则.封装了一套API 起名 AOP
2 Spring中的AOP
2.1 AOP介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
2.2 AOP中专业术语(难点)
1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程
2.3 AOP 入门案例
2.3.1 创建一个SpringBoot的module
2.3.1 导入jar包
<!--引入AOPjar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.3.2 项目工程结构
2.3.3 配置类
SpringConfig.java
package com.jt.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.jt")
@Configuration
public class SpringConfig {
}
2.3.4 Service层
2.3.4.1 接口
package com.jt.service;
public interface UserService {
void addUser();
void deleteUser();
}
2.3.4.2 实现类
package com.jt.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("完成用户新增");
}
@Override
public void deleteUser() {
System.out.println("完成用户删除操作");
}
}
2.3.5 切入点表达式
切入点表达式
- bean(“对象的Id”) 每次拦截,只拦截1个
- within(“包名.类名”)
- execution(返回值类型 包名.类名.方法名(参数列表))
- @annotation(注解的路径)
2.3.6 定义切面类
package com.jt;
import com.jt.config.SpringConfig;
import com.jt.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring_AOP {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象,则方法被扩展 aop有效的
System.out.println(userService.getClass());//class com.jt.service.UserServiceImpl$$EnhancerBySpringCGLIB$$baeada27
userService.addUser();
}
}
2.3.7 让AOP生效
说明: 编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效
package com.jt.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@ComponentScan("com.jt")
@Configuration
@EnableAspectJAutoProxy//让spring中的AOP生效
public class SpringConfig {
}
2.3.8 编辑测试类
package com.jt;
import com.jt.config.SpringConfig;
import com.jt.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring_AOP {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象 但是与切入点表达式匹配,为了后续扩展方便,为其创建代理对象
UserService userService = applicationContext.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象,则方法被扩展 aop有效的(是代理对象)
/*getClass()是Object中的方法,不拦截*/
System.out.println(userService.getClass());//class com.jt.service.UserServiceImpl$$EnhancerBySpringCGLIB$$baeada27
userService.addUser();
}
}
2.4 AOP形象化的比喻
说明: AOP是一种抽象的一种概念,看不见/摸不着.所以需要大家对概念有自己的认知.
2.5 关于切入点表达式解析
2.5.1 bean标签写法
@Pointcut(“bean(userServiceImpl)”) 只匹配ID为userServiceImpl的对象
2.5.2 within表达式
@Pointcut(“within(com.jt.service.*)”) 匹配xx.xx.service下的所有对象
2.5.3 execution表达式
@Pointcut("execution(* com.jt.service..*.*(..))")
拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类的任意方法
@Pointcut("execution(* com.jt.service..*.add*(..))")
拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类.以add开头的方法
2.6 按照自定义注解进行拦截
2.6.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;
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface MyAnnotation {//注解起标记作用
}
2.6.2 切入点表达式写法
@Pointcut("@annotation(com.jt.anno.MyAnnotation)")
public void pointCutMethod(){
}
2.6.3 在service层实现类UserServiceImpl的addUser()方法上添加自定义的注解
测试类运行
2.7 动态获取注解参数
2.7.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;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Find {
int id() default 0;
}
2.7.2 使用注解
在service层的实现类UserServiceImp的addUser()方法上面添加自定义注解@Find(id=101)
2.8.3 需求
利用前置通知,打印注解中的id值!!!
2.8.4 编辑切面类
/**
* 知识点:
* 如果切入点表达式只对当前通知有效,则可以按照如下方式编辑
* 要求:动态的拦截Find注解,并且要获取Find注解中的参数Id
* 难点:动态获取注解的对象!!
* 代码解释:
* 1.@annoattion(find) 拦截find变量名称对应类型的注解
* 2.当匹配该注解后,将注解对象当做参数传递给find
* 优势:可以一步到位获取注解的内容,避免了反射的代码
*/
@Before("@annotation(find)")
public void before2(Find find){
System.out.println("ID的值为:"+find.id());
}
2.8 通知方法
2.8.1 关于通知方法解析
1.前置通知 在目标方法执行之前执行.
2.后置通知 在目标方法执行之后执行.
3.异常通知 在目标方法执行之后抛出异常时执行.
4.最终通知 都要执行的通知
说明: 上述的四大通知一般用于记录程序的运行状态.只做记录.
5.环绕通知 在目标方法执行前后都要执行的通知
2.8.2 前置通知案例
切面类
SpringAOP.java
/**
* 定义通知方法:
* 1.前置通知 在目标方法执行之前执行
* 2.后置通知 在目标方法执行之后执行
* 3.异常通知 在目标方法执行之后抛出异常时执行
* 4.最终通知 都要执行的通知
* 5.环绕通知 在目标方法执行前后都要执行的通知
*
*记录程序的状态
* 1.目标对象的class/类路径 com.jt.xx.xxx.UserServiceImpl
* 2.目标对象的方法名
* 3.目标对象的方法的参数信息
* 4.获取目标对象方法的返回值
* 5.获取目标对象执行报错的异常信息
*/
@Before("pointCutMethod()")
public void before(JoinPoint joinPoint){
//1.获取目标对象的类型
Class<?> targetClass = joinPoint.getTarget().getClass();
//2.获取目标对象的路径
String path = joinPoint.getSignature().getDeclaringTypeName();
//3.获取目标对象的方法名称
String methodName = joinPoint.getSignature().getName();
//4.获取方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("类型" + targetClass);
System.out.println("类的路径:" + path);
System.out.println("方法名:" + methodName);
System.out.println("参数:" + Arrays.toString(args));
}
运行结果
2.8.3 后置通知案例
2.8.3.1 添加接口方法
1.编辑接口
package com.jt.service;
public interface UserService {
void addUser();
void deleteUser();
int findCount();//查询总数
}
2.编辑实现类
package com.jt.service;
import com.jt.anno.Find;
import com.jt.anno.MyAnnotation;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Find(id = 101)
@MyAnnotation//标记作用
@Override
public void addUser() {
System.out.println("完成用户新增");
}
@Override
public void deleteUser() {
System.out.println("完成用户删除操作");
}
/**
* 测试获取返回值的!!!
* @return
*/
@MyAnnotation
@Override
public int findCount() {
return 1000;
}
}
2.8.3.2 编辑AOP切面类SpringAOP
//注意事项:如果多个参数,joinPoint必须位于第一位!!!
@AfterReturning(value="pointCutMethod()",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
//如果需要获取当前的方法信息,则可以通过joinPoint获取
// System.out.println("我是后置通知");
System.out.println("我是后置通知,获取方法的返回值"+result);
}
2.8.3.3 编辑测试案例
package com.jt;
import com.jt.config.SpringConfig;
import com.jt.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring_AOP02 {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象 但是与切入点表达式匹配,为了后续扩展方便,为其创建代理对象
UserService userService = applicationContext.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象,则方法被扩展 aop有效的(是代理对象)
/*getClass()是Object中的方法,不拦截*/
System.out.println(userService.getClass());//class com.sun.proxy.$Proxy19
userService.addUser();
userService.findCount();//测试代返回值的方法
}
}
2.8.3.4 测试效果
2.8.4 异常通知案例
2.8.4.1 让service层实现类代码报错
2.8.4.2 异常通知案例
throwing:获取异常信息,之后进行传递
//后置通知与异常通知是互斥的,只能有一个
@AfterThrowing(value = "pointCutMethod()",throwing = "exception")
public void afterThrow(JoinPoint joinPoint,Exception exception){
//打印异常
//exception.printStackTrace();
System.out.println("我是异常通知:"+exception.getMessage());
}
2.8.4.3 测试结果
常用注解
@Configuration 标识当前类是配置类
@ComponentScan 包扫描注解 扫描注解
@Bean 标识该方法的返回值交给Spring容器管理
@Scope 控制多例和单例
@Lazy 懒加载
@PostConstruct 初始化方法
@PreDestroy 销毁方法
@Component 将当前类未来的对象交给容器管理
@Autowired 按照类型进行注入
@Qualifier 按照名称进行注入
@Repository 标识持久层注解
@Service 标识Service层
@Controller 标识Controller层
@Value 为属性赋值 @Value(“${key}”)
@PropertySource 加载指定路径的配置文件properties
@Aspect 标识当前类是一个切面类
@Pointcut 用于定义切入点表达式 表达式写法4种
@EnableAspectJAutoProxy 让AOP的注解有效果
@Before AOP-前置通知
@AfterReturning AOP-后置通知
@AfterThrowing AOP-异常通知
@After AOP-最终通知
@Around AOP-环绕通知