【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中,有两个属性:

  1. Value:表达式切入点
  2. 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>
posted @   VVS,冷漠的心  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?

囚鸟该如何超越今生?

点击右上角即可分享
微信分享提示