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();
}
}