【Spring 框架(自学)】Day05(Aop切面)--2022/4/5
AOP面向切面编程
动态代理
动态代理的实现方式常用的有两种:使用JDK的Proxy,与CGLIB生成动态代理
JDK动态代理:简介
JDK的动态代理要求目标对象必须实现接口,这是java设计上的要求
从JDK1.3以来,java语言通过java.lang.reflect包提供三个类支持代理模式:
- Proxy
- Method
- InovcationHandler
什么是CGLIB动态代理(了解):简介
它是一个第三方的工具库
如果说JDK的动态代理要求目标对象必须实现接口,那CGLIB动态代理则可以对无接口的类,为其创建动态代理
CGLIB动态代理的原理是继承(即生成目标类的子类),这个子类对象就是代理对象
所以要实现CGLIB动态代理,那么其目标类必须能够被继承(即不能是final的类)
不使用动态代理实现 功能增强
首先创建一个包(com.spring.java) 即程序的入口
service包:
- 1.定义接口
public interface javaService{
//模拟一个doSome方法
void doSome();
//模拟一个doOther方法
void doOther();
}
Service子包:Impl
- 2.定义接口实现类
public class javaService implements javaService{
@Override
public void doSome(){
sout("执行了doSome方法");
}
@Override
public void doOther(){
sout("执行了doOther方法");
}
}
接着测试类调用即可
如果要增添功能的话,就要破坏原来的业务代码的结构
接口实现类:
public class javaService implements javaService{
@Override
public void doSome(){
sout("执行方法的时间为:"+ new Date());
sout("执行了doSome方法");
sout("执行了一个事务方法");
}
@Override
public void doOther(){
sout("执行方法的时间为:"+ new Date());
sout("执行了doOther方法");
sout("执行了一个事务方法");
}
}
//如果很复杂,可以定义一个工具类业务以外的方法进行封装:com.spring.java包-->utils包-->utilsTool类
public class utilsTool {
public static void time(){
System.out.println("执行方法的时间为:"+ new Date());
}
public static void service(){
System.out.println("执行了一个事务方法");
}
}
//进行优化之后的代码如下:
public class javaService implements javaService{
@Override
public void doSome(){
utilsTool.time();
sout("执行了doSome方法");
utilsTool.service();
}
@Override
public void doOther(){
utilsTool.time();
sout("执行了doOther方法");
utilsTool.service();
}
}
但以上操作仅仅只是优化代码书写的量,并没有真正的保护它的业务结构,那么这时便需要用到动态代理
使用JDK动态代理实现 功能增强
在已创建的(com.spring.java)包中新建一个handler包
- 创建InvocationHandler
public class MyinvocationHandler implements InvocationHandler{
//3.创建目标类
private Object targetr;
public MyInvocationHandler(Object target) {
this.target = target;
}
//1.创建InvocationHandler,添加ivoke()方法,通过代理对象执行方法时,会调用这个invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//4.定义一个结果集 将method.invoke()记录
Object res = null;
sout(method.getName());
String methodName = method.getName();
if("doSome".equals(methodName)){
//6.调用事务以外所增加的方法
utilsTool.time();
//2.执行目标类方法,通过method类来实现
res = method.invoke(target,args);
utilsTool.service();
} else{
res = method.invoke(,args);
}
//5.返回目标方法的执行结果
return res;
}
}
main主入口实现:
public class javaTest{
public static void mian(String[] args){
//使用JDK动态代理实现
//1.创建目标对象
javaService target = new javaServiceImpl();
//2.创建InvocationHandler对象 将javaService传给invoke 在通过method类实现接口方法
InvocationHandler handler = new MyinvocationHandler(target)
//3.使用Proxy实现动态代理,将代理传给javaService
javaService proxy = (javaService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler)
//proxy创建动态代理的参数是固定的(目标类加载器,目标类接口实现,代理功能的实现)
proxy.doSome();//如果说增加的功能只要求doSome执行,那么可以在handler中写方法判断
sout("=================================");
proxy.doOther();
}
}
动态代理的作用:
- 在目标类源代码不改变的情况下,增加功能
- 减少代码的重复
- 解耦合,功能分离
- 专注业务逻辑代码
AOP的实现
aop的技术实现框架:
1.spring
- Spring:实现了aop规范
- 主要在事务处理时使用aop
- 开发项目中很少用spring的aop实现,因为比较笨重
2.AspectJ:
它是一款专门做aop的开源框架,而spring也集成了该框架,通过spring即可使用aspectj框架的功能
- 使用xml的配置文件:配置全局事务
- 使用注解,我们在项目中要做aop功能,一般都是使用的注解,aspectj有5个注解
切面的三要素
在学习前面之前,需要了解切面的三要素:
- 切面的功能代码(切面是干什么的)
- 切面的执行位置(使用Pointcut表示切面执行的位置)
- 切面的执行时间(使用Advice表示时间;执行前 或 执行后)
aspectj框架的使用
学习aspect框架的使用:
1.切面的执行时间,这个执行时间在规范中叫Advice(通知、增强),在aspectj框架中使用注解表示:
- @Before
- @AfterReturning
- @Around
- @After
- 也可以用xml配置中的标签
2.切面执行的位置,使用的是切入点表达式
execution(modifiers-pattern? //空格
ret-type-pattern //空格
declaring-type-pattern?name-pattern(param-pattern)//空格
throw-pattern?)
解释:
- modifiers-pattern:访问权限的类型
- ret-type-pattern:返回值类型
- declaring-type-pattern:包名
- name-pattern(param-pattern):方法名(参数类型和参数的个数)
- throw-pattern:抛出异常类型
"?"表示可选的部分
综上所述,其四个部分用最直观的表示为:
- execution(访问权限 方法返回值 方法名(参数) 异常类型)
在上述中,可以使用通配符:
- == :0至多个任意字符==*
- .. :用在方法参数中,表示任意多个参数
用在报名后,表示当前包及其子包路径
- + :用在类名后,表示当前类及其子类
用在接口后,表示当前接口及其实现类
aspectj与spring的具体实现
@After前置通知(掌握)
实现思路如下:
-
1.引入依赖
-
2.定义接口,书写方法
-
3.定义接口实现类,书写具体方法(解耦)
-
4.定义该接口实现类以外的事务或方法
-
5.配置spring文件
-
6.进行Junit单元测试
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--加入Aspectj框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
//接口及其登录方法
public interface userService{
boolean login(String username,String password);
}
//接口实现类 进行解耦
public class userServiceImpl Implements userService{
public boolean login(String username,String password){
boolean invalid = false;
if("root".equals(username) && "123456".equals(password)){
sout("用户登录成功!")
invalid = true;
}else {
sout("用户登录失败")
}
}
}
//除该接口实现类以外的事务或方法
/*
@Aspect注解:指定该myAspectj为切面类,这个类中有切面的实现代码
*/
@Aspect
public class myAspectj{
/*
前置执行时间@Before:其作用是在方法执行前执行其定义的方法
*/
@Before(value = "execution(public boolean com.spring.aspectj.ba01.userServiceImpl.login(String,String))")
public time(){
System.out.println("目标方法执行前--用户登录时间为:"+new Date());
}
}
<!--头文件:配置spring-->
<!--声明目标对象
创建对象交给spring容器去完成:
userService userService = new userServiceImpl();
-->
<bean id = "userService" class = "com.spring.aspectj.ba01.userServiceImpl"></bean>
<!--声明AspectJ对象
创建对象交给spring容器去完成:
myAspectj myAspectj = new myAspectj();
-->
<bean id="myAspectj" class="com.spring.aspectj.myAspectj"></bean>
<!--声明动态代理生成器-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
//junit测试
public class userTest{
@Test
public void loginTest(){
//存取spring配置文件
//定义容器接收spring的配置文件
/*====================重点!=========================*/
//通过容器获取目标对象
userService proxy = (userService) act.getBean("userService")
//执行
proxy.login("传入的值","传入的值");
}
}
*JoinPoint的实现
其作用是可以在方法执行时,获取该方法的信息(例如:方法的定义、参数等)
常用的JoinPoint参数:
- getSignature():获取方法的定义-->即方法的类型+包名+方法名(参数类型)
- getSignature().getName():获取方法的名称
- getArgs():获取方法中传递的参数值,由于是从0到1查找要用数组接收
@Aspect
public class myAspectj{
@Before(value = "execution(public boolean com.spring.aspectj.ba01.userServiceImpl.login(String,String))")
public time(JoinPoint jp){
sout("方法的定义:"+jp.getSignature())//即方法的类型+包名+方法名(参数类型)
sout("方法的名称:"+jp.joinPoint.getSignature().getName())
Object arg[] = jp.getArgs();
for(Object oj:arg){
sout("方法中的参数传递值"+oj);
}
System.out.println("目标方法执行前--用户登录时间为:"+new Date());
}
}
@AfterReturning后置通知(掌握)
当方法中有返回值(return xxx),可以使用后置通知(@AfterReturning)
如果目标对象是一个引用数据类型,那么后置通知执行完成后,会与目标对象的return保持同步,就算是修改,也是保持原有业务的源代码再其执行完成时,进行修改,但是String引用数据类型不会修改
@AfterReturning中,有两个属性:
- Value:表达式切入点
- Returning:自定义变量,表示目标方法的返回值
//以Student引用类型为例
/*
定义Student实体类
定义属性、setter()、toString()方法
*/
/*
定义studentService接口,书写方法
*/
//定义接口实现类:studentServiceImpl
public class studentService implements studentServiceImpl{
@Override
public Student doOther(String name, Integer age) {
Student student = new Student();
student.setName("张三");//先定义了set方法
student.setAge(30);//同上
System.out.println("执行了doOther()方法");//doOther()方法的实现
return student;//----->返回值是student的引用数据类型
}
}
//定义MyAspect普通类,用@Aspect声明为切面类
@Aspect
public class MyAspect{
/*
@AfterReturning(value="execution()",Returning = "自定义名")
*/
@AfterReturning(value ="execution(* *..doOther(..))",Returning = "result")
public void afterReturning(JoinPoint jp,Object result){
sout("执行了其它事务方法");
sout("执行完之后,result为:"+result)
sout("修改result")
if(result != null){
result = "修改student成功!"
}
sout("修改完student之后,result为:"+result)
}
}
@Around环绕通知(掌握)
其注解就像它的名字一样,功能很强大,其中要用到ProceedingJoinPoint属性,而它就相当于JDK的动态代理:Invocation Handler
- 可以进行除此事务以外的@After或@Before切面功能(环绕)
- 可以让目标方法是否执行
- 修改原来的目标方法执行结果,影响最后的调用结果
只要是引用类型都可以改变结果(String类型)
//定义service接口
/*
service接口
String 定义方法(需要返回值)
*/
//定义接口实现类
/*
接口实现类(){
具体方法
return "register"(String的引用数据类型)
}
*/
@Aspect
public class MyAspect{
//定义方法
@Around(value = "execution(* *..接口实现类.方法名(..))")
public Object myAround(ProceedingJoinPoint pjp){//ProceedingJoinPoint等同于InvocationHandler
//如果要对方法进行判断,是则执行,不是则不执行,那么:
//定义string类型接收数组参数(方法传入的参数值)
String name = ""
//首先接收方法传入的参数值
Object args[] = pjp.getArgs();
//对方法传入的参数值进行判断
if(args != null && args.lenght>1){
name = (String) args[0]
}
// 1. 目标方法实现之前,要创建目标对象
Object result = null;
//再对参数值进行判断
if("注册".equals(name)){
//@Before方法
sout("该方法的执行时间为:"+new Date())
// 2. 目标方法实现
result = pjp.proceed();//其相当于method.invoke()
//以上两步相当于: Object result = register()
//@After方法
sout("register方法执行完,又执行了其它事务方法")
}else {
sout("用户名已被注册")
}
//再通过修改目标方法的执行结果,影响最后调用的结果
if(result != null){
result = "hello"
}
return result;
}
}
<!--头文件-->
< bean id="接口" class="接口实现类"></bean>
<bean id = "切面对象" class = "..."></bean>
<!--创建自动代理-->
//junit单元测试
@AfterThrowing异常通知
其功能是:当程序发生异常的时候可以抛出信息
该方法有两个属性:
-
Value:切入点表达式
-
throwing:自定义变量要求与参数名相同
最通俗的理解就是:
try{
}catch(Exception e){
myAfterThrowing()
}
//定义service接口
//定义接口实现类
//定义切面类
public class MyAspect{
@Aspect(value = "execution(* *..接口实现类.方法名(..))",throwing = "e")
public void myAfterThrowing(Exception e){
sout("当程序有异常的时候将其抛出"+e.getMessage())
}
}
@After最终通知
最终通知,一般把它当作程序清除来使用,因为它在目标方法执行时,总是会被执行的(不管是否出错)
最通俗的理解就是:
try{
}catch(Exception e){
}finally{
myAfter()
}
//定义service接口
//定义接口实现类
//定义切面类
@Aspect
public class MyAspect{
@After(value = "execution(* *..接口实现类.方法名(..))")
public void myAfter(){
sout("一般来讲,它被作用于程序清除,而且它总会被执行")
}
}
@Pointcut定义切入点
当目标方法执行的时候,如果需要给它增加多个功能,那么每次添加都得导入重复的包,所以@Pointcut注解可以让其封装并复用
//定义service接口
//定义接口实现类
//定义切面类
@Aspect
public class MyAspect{
@Before(value = "myCut()")
public void doBefore(){
sout("前置通知方法,在目标方法前执行")
}
@After(value = "myCut()")
public void doAfter(){
sout("执行了最终通知,它总是会被执行")
}
//定义切入点,因为是让目标方法中增加的多个功能导入的包复用,所以无需定义实现类,也无需公开
@Pointcut(value = "execution(* *..接口实现类.方法名(..))")
private void myCut(){
}
}
CGLIB动态代理
无接口实现CGLIB动态代理
//定义实现类
/*
public class userServiceImpl{
public void login(String username,String password){
sout("登陆成功!")
}
}
*/
@Aspect
public class MyAspect {
@Before(value = "mypt()")
public void doBefore(){
System.out.println("前置通知,在目标方法前执行:"+new Date());
}
@After(value = "mypt()")
public void doAfter(){
System.out.println("最终通知,总是会被执行");
}
//切入点
@Pointcut(value = "execution(* *..userServiceImpl.login(..))")
private void mypt(){
}
}
<bean id="userService" class="com.spring.aspectj.ba04.userServiceImpl"></bean>
<bean id="MyAspect" class="com.spring.aspectj.ba04.MyAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
//junit单元测试
String config = "applicationContext04.xml";
ApplicationContext act = new ClassPathXmlApplicationContext(config);
userServiceImpl cglib = (userServiceImpl) act.getBean("userService");
String a = cglib.getClass().getName();
System.out.println(a);
cglib.login("root","123456");
有接口实现CGLIB动态代理
实现起来很简单,直接在spring的applicationContext配置文件里设置:
- proxy-target-class="true"
//这是Service接口
//这是接口实现类
//这是切面类
<!--这是spring配置文件-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?