Spring学习第六天---Spring-AOP编程

Spring-AOP编程

静态代理模式

Spring中动态代理

概念

通过代理类为原始类(⽬标类)增加额外功能,利于原始类(⽬标类)的维护

实现

(1)工程加入jar包

compile group: 'org.springframework', name: 'spring-aop', version: '5.1.14.RELEASE'
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.8'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.8'

(2)创建原始对象(⽬标对象)并且在Spring工厂中配置

package com.designpatterns.factory.proxy;

/**
 * @author PitterWang
 * @create 2020/6/2
 * @since 1.0.0
 */
public class UserServiceImpl  implements UserService{
    
    

   @Override
   public void login() {
    
    
      System.out.println("login 核心业务代码");
   }

   @Override
   public void home() {
    
    
      System.out.println("home 核心业务代码");
   }
}
<bean id = "userService" class="com.designpatterns.factory.proxy.UserServiceImpl"/>

(3)定义额为方法,实现implements MethodBeforeAdvice,并且在Spring工厂中配置

package com.designpatterns.factory.proxy;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * 〈额外功能实现〉
 *
 * @author PitterWang
 * @create 2020/6/2
 * @since 1.0.0
 */
public class Before implements MethodBeforeAdvice {
    
    

   /*
   *  作⽤:需要把运⾏在原始⽅法执⾏之前运⾏的额外功能,书写在before⽅法中
   */
   @Override
   public void before(Method method, Object[] args, Object target) throws Throwable {
    
    

      System.out.println("日志功能~~~~~~");
   }
}
<bean id="before" class="com.designpatterns.factory.proxy.Before"/>

(4)定义切入点

切入点:额外功能将加入的位置
目的:根据自己的需要,将额为功能加入那个原始方法
<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>

(5)组合(3,4整合)

<aop:config>
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>

(6)使用

	/**
	 * 1.Spring框架在运⾏时,通过动态字节码技术,在JVM创建的,运⾏在JVM内部,等程序结束后,会和JVM⼀起消失
	 * 什么叫动态字节码技术:
	 * 通过第三个动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。
	 * 结论:动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态代理,类⽂件数量过多,影响项⽬管
	 * 理的问题。
	 */
	@Test
	public void test1(){
    
    
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/context3.xml");
		//通过Spring工厂创建的动态代理对象,并进行调用

		//Spring的工厂通过原始对象的id值获得的代理对象
		//获得代理对象后,可以通过什么的接口类型,进行对象存储
		UserService userService = (UserService)applicationContext.getBean("userService");
		userService.home();
		userService.login();
	}

详解

(1)MethodBeforeAdvice分析

**
 * 〈额外功能实现〉
 *
 * @author PitterWang
 * @create 2020/6/2
 * @since 1.0.0
 */
public class Before implements MethodBeforeAdvice {
    
    

   /*
   *  作⽤:需要把运⾏在原始⽅法执⾏之前运⾏的额外功能,书写在before⽅法中
   *  Method:额为功能所增加给的那个原始方法(login,home等)
   * public abstract void com.designpatterns.factory.proxy.UserService.login()
   *  Object[]:额为功能所增加给的那个原始方法的参数
   *  Object:额外功能所增加给的那个原始对象UserServiceImpl。
   */
   @Override
   public void before(Method method, Object[] args, Object target) throws Throwable {
    
    
      System.out.println("日志功能~~~~~~");
   }
}

(2)MethodInterceptor分析

package com.designpatterns.factory.proxy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 〈实现MethodInterceptor接口,重写invoke方法〉
 *
 * MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行
 * 前
 * 后
 * 前后
 *
 *
 *
 * @author PitterWang
 * @create 2020/6/4
 * @since 1.0.0
 */
public class Arround implements MethodInterceptor {
    
    

   /***
    * MethodInvocation invocation 额外功能所增加给的那个原始方法
    *
    * invocation.proceed(); 是让额外功能所增加给的那个原始方法执行
    *
    * 所以额为功能想要在原始方法之前,之后,之前后都可以直接在
    * invocation.proceed();之前,之后,之前后写即可
    *
    * 返回值Object:原始方法的返回值
    * 
    * 因为返回值是原始方法的返回值,所以会影响原始方法的返回值
    * 即在return的时候修改返回的值即
    * @param invocation
    * @return
    * @throws Throwable
    */
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
    
    

      System.out.println("原始方法之前的log!!");
      Object proceed = invocation.proceed();
      System.out.println("原始方法之后的log!!");
      return proceed;
   }
}

(3)切入点详解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id = "userService" class="com.designpatterns.factory.proxy.UserServiceImpl"/>


<!--
    <bean id="before" class="com.designpatterns.factory.proxy.Before"/>
-->

    <bean id = "arround" class="com.designpatterns.factory.proxy.Arround"/>
    <aop:config>

        <!--切点
        execution(* *(..))  匹配所有的方法
        execution() 表示切入点函数
        (* *(..))     表示切入点表达式
        1.切入点表达式
          ①方法切入点表达式
              public void login(String username,String password)
                     *      *  (..)
               第一个* 表示修饰符  返回值
               第二个* 表示方法名
               ()      表示参数列表
               ..       表示对参数没有要求
               eg: 如果定义所有login方法作为切入点                   * login(..)
                    如果定义login并且有两个字符串类型的参数作为切入点   * login(String String)  如果参数不是lang包中的必须写全限定名
                    如果精确的缺点切入点                              * com.designpatterns.factory.proxy.UserServiceImpl.login()
           ②类切入点
               指定特定类作为切入点,这个类中的方法都会加上对应的额为方法
               er:UserServiceImpl所有类都加如额外方法 * com.designpatterns.factory.proxy.UserServiceImpl.*(..)
                  注意可以忽略包:如果类只存在一级包:  * *.UserServiceImpl.*(..)
                                 如果存在多级包:     * *..UserServiceImpl.*(..)
           ③包切入点
               指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能
               er:* com.designpatterns.factory.proxy.*.*(..)  proxy包下的所有类,不包括子包中的类
                  * com.designpatterns.factory.proxy..*.*(..)  proxy包下的所有类,包括子包中的类
         2.切入点函数
            用于执行切入点表达式
            ①execution:执行方法,类,包切入点表达式,就是书写比较麻烦
            ②args:主要用于方法参数的匹配
                   eg:方法参数必须有2个字符串类型的参数
                          execution(* *(String,String))
                          args(String,String)
            ③within:主要用于类,包切入点表达式
                  eg:切入点为UserServiceImpl这个类
                        execution(* *..UserServiceImpl.*(..))
                           within(*..UserServiceImpl)
            ④@annotation注解 为具有特殊注解的方法加入额为功能
             <aop:pointcut id=""expression="@annotation(com.designpatterns.factory.proxy.Log)"/>
            ⑤切入点函数的逻辑运算
              整合多个切入点函数一起配合工作,进而完成更复杂的需求
               and与操作
               or 或操作
                register方法和 login方法作为切入点 : execution(* login(..)) or execution(* register(..))
        -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="arround" pointcut-ref="pc"/>
    </aop:config>

</beans>

AOP底层实现原理

1.AOP如何创建动态代理类
2.Spring工厂如何加工创建代理对象
  通过原始对象的id值,获得的是代理对象

动态代理的创建

代理模式

Spring工厂如何加工创建代理对象

(1)在后置处理Bean中我们可以看到BeanPostProcessor,对Spring工厂所创建的对象进行再加工。所以Spring工厂是通过在BeanPostProcessor中进行代理,把代理对象放回去,这样就实现了 通过原始对象的id值,获得的是代理对象。

(2)编码

​ ①创建目标对象

package com.designpatterns.factory.proxy;

public interface PersionService {
    
    
   public void getPersions();
   public void getPersionByName(String username);
}


package com.designpatterns.factory.proxy;

/**
 * @author PitterWang
 * @create 2020/6/4
 * @since 1.0.0
 */
public class PersionServiceImpl implements PersionService{
    
    

	@Override
	public void getPersions() {
    
    
		System.out.println("getPersions()");
	}
	@Override
	public void getPersionByName(String username) {
    
    
		System.out.println("getPersionByName"+ username);

	}
}

②开发后置处理bean且进行注入

package com.designpatterns.factory.proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author PitterWang
 * @create 2020/6/4
 * @since 1.0.0
 */
public class ProxyBeanPostProcessor implements BeanPostProcessor {
    
    


   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
      return bean;
   }

   /**
    * 在后置bean中进行代理
    * @param bean 原始对象
    * @param beanName
    * @return
    * @throws BeansException
    */
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    

      InvocationHandler invocationHandler = new InvocationHandler() {
    
    
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    

            System.out.println("前log");
            Object invoke = method.invoke(bean, args);
            System.out.println("后log");
            return invoke;
         }
      };

      //把代理对象返回
      return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler);
   }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id = "persionService" class="com.designpatterns.factory.proxy.PersionServiceImpl"/>


    <!--后置bean配置-->
    <bean class="com.designpatterns.factory.proxy.ProxyBeanPostProcessor"/>
</beans>

③测试

@org.junit.Test
public void test8(){
    
    
   ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/context4.xml");
   PersionService user= (PersionService)applicationContext.getBean("persionService");
   user.getPersionByName("dd");
   user.getPersions();
}

基于注解的AOP编程

基于注解AOP编程的开发步骤

1.原始对象

public interface PersionService {
    
    
   public void getPersions();
   public void getPersionByName(String username);
}

package com.designpatterns.factory.proxy;

/**
 * @author PitterWang
 * @create 2020/6/4
 * @since 1.0.0
 */
public class PersionServiceImpl implements PersionService{
    
    
	@Override
	public void getPersions() {
    
    
		System.out.println("getPersions()");
	}
	@Override
	public void getPersionByName(String username) {
    
    
		System.out.println("getPersionByName"+ username);

	}
}

2.额外对象

通过@Around注解,告知Spring这个方法是额外方法

ProceedingJoinPoint joinPoint参数是传入的原始对象,进行原始对象执行

3.切入点

execution(* *(..))  切入点

4.组装切面

定义一个切面类@Aspect注解注解
package com.designpatterns.factory.proxy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

/**
 * 〈基于注解的切面类〉
 *
 * @author PitterWang
 * @create 2020/6/5
 * @since 1.0.0
 */


/*public class Arround implements MethodInterceptor {
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {

      System.out.println("原始方法之前的log!!");
      Object proceed = invocation.proceed();
      System.out.println("原始方法之后的log!!");
      return proceed;
   }
}
 <bean id = "arround" class="com.designpatterns.factory.proxy.Arround"/>
    <aop:config>
   <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="arround" pointcut-ref="pc"/>
    </aop:config>
*/

/**
 * @Aspect-定义为这个类是一个切面类
 */
@Aspect
public class MyApect {

   /**
    *    @Around——通知切面类,定义了额外功能
    *    execution(* *(..))  切入点
    *
    * @param joinPoint
    * @return
    * @throws Throwable
    */
   @Around("execution(* *(..))")
   public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
      System.out.println("基于注解的切面类");
      Object proceed = joinPoint.proceed();
      return proceed;
   }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id = "persionService" class="com.designpatterns.factory.proxy.PersionServiceImpl"/>

    <!--注册切面类-->
    <bean class="com.designpatterns.factory.proxy.MyApect"/>

    <!--告知Spring基于注解进行AOP编程-->
    <aop:aspectj-autoproxy/>
</beans>

细节

1.切入点复用

切入点复用,在切面类中定义一个函数,上面@Pointcut注解,通过这种方式,定义切入点表达式,有益于切入点复用

/**
	 * 切入点复用
	 */
	@Pointcut("execution(* *(..))")
	public void myPointcut(){

	}


	/**
	 *    @Around——通知切面类,定义了额外功能
	 *    execution(* *(..))  切入点
	 *
	 * @param joinPoint
	 * @return
	 * @throws Throwable
	 */
	@Around(value = "myPointcut()")
	public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("基于注解的切面类Log~~~");
		Object proceed = joinPoint.proceed();
		return proceed;
	}

2.动态代理的创建方式

1.AOP底层实现 2种代理方式
  (1)JDK代理
  (2)Cglib动态代理
  默认情况底层用JDK动态代理创建方式
2,如果切换Cglib动态代理方式使用
   (1)基于注解AOP
       <aop:aspectj-autoproxy proxy-target-class="true"/>
   (2)基于传统的AOP
     <aop:config proxy-target-class="true">
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="arround" pointcut-ref="pc"/>
    </aop:config>

AOP编程中容易遇到的问题

如果在原始类中,业务方法调用该类中的另一个方法,使用this.方法名(),这样就是使用的当前对象调用另一个方法,这样就会造成无法进行调用对象的额外方法执行
如果想要执行,必须使工厂类获取代理对象,进行执行
(1)在调用类中是再次创建工程,进行调用。不好,Spring是一个重量级工厂,应该创建一个就好
(2)业务类实现ApplicationContextAware接口,实现setApplicationContext方法,即可把工厂传到当前业务类,使用如下


package com.designpatterns.factory.proxy;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author PitterWang
 * @create 2020/6/4
 * @since 1.0.0
 */
public class PersionServiceImpl implements PersionService, ApplicationContextAware {
    
    

   private ApplicationContext applicationContext;
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
      this.applicationContext = applicationContext;
   }
   @Override
   public void getPersions() {
    
    
      System.out.println("getPersions()");
   }
   @Override
   public void getPersionByName(String username) {
    
    
      System.out.println("getPersionByName"+ username);
      PersionService user= (PersionService)applicationContext.getBean("persionService");
      user.getPersions();
   }
}

猜你喜欢

转载自blog.csdn.net/weixin_35133235/article/details/106603966
今日推荐