spring-aop

mac2025-09-10  2

代理模式

1.1 静态代理

1.1.1 创建接口

public interface UserService { public void addUser(String a); public void updateUser(String b); }

1.1.2 创建实现类

public class UserServiceImpl implements UserService{ public void addUser(String a) { System.out.println("新增用户"); } public void updateUser(String a) { System.out.println("修改用户"); } }

1.1.3 创建静态代理类

public class UserProxyHandle implements UserService { private UserServiceImpl userServiceImpl; public UserProxyHandle(UserServiceImpl userServiceImpl) { this.userServiceImpl = userServiceImpl; } public void addUser(String a) { System.out.println("新增用户前的操作"); userServiceImpl.addUser(a); System.out.println("新增用户后的操作"); } public void updateUser(String b) { System.out.println("修改用户前的操作"); userServiceImpl.updateUser(b); System.out.println("修改用户后的操作"); } }

1.1.4 测试调用

@Test public void test01(){ //静态代理模式 UserServiceImpl userService = new UserServiceImpl(); UserProxyHandle userProxyHandle = new UserProxyHandle(userService); userProxyHandle.addUser(); userProxyHandle.updateUser(); }

1.1.5 缺点

静态代理的缺点很明显:一个代理类只能对一个业务接口的实现类进行包装,如果有多个业务接口的话就要定义很多实现类和代理类才行。而且,如果代理类对业务方法的预处理、调用后操作都是一样的(比如:调用前输出提示、调用后自动关闭连接),则多个代理类就会有很多重复代码。这时我们可以定义这样一个代理类,它能代理所有实现类的方法调用:根据传进来的业务实现类和方法名进行具体调用。——那就是动态代理。

1.2 Jdk 动态代理

1.2.1 jdk动态代理原理

**JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。**我们需要做的,只需指定代理类的预处理、调用后操作即可。

1.2.2 定义接口

与上方相同即可

1.2.3 创建实现类

与上方相同即可

1.2.4 实现调用管理接口InvocationHandler创建动态代理类

public class JdkProxyHandle implements InvocationHandler { private Object target;//这其实业务实现类对象,用来调用具体的业务方法 public Object bind(Object obj){ this.target=obj; //通过反射机制,创建一个代理类对象实例并返回。用户进行方法调用时使用 //创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,在包装方法是调用真正的业务方法)、接口、handler实现类 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用方法前"); Object invoke = method.invoke(target,args); System.out.println("调用方法后"); return invoke; } }

1.2.5 测试

@Test public void test01(){ //动态代理模式 jdk UserServiceImpl userService = new UserServiceImpl(); JdkProxyHandle jdkProxyHandle = new JdkProxyHandle(); UserService bind = (UserService) jdkProxyHandle.bind(userService); bind.addUser("1"); bind.updateUser("2"); }

1.2.6 缺点

​ JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

1.3 CGlib动态代理

1.3.1 cglib实现原理

使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚

​ 第二个入参 interfaces就是需要代理实例实现的接口列表.

​ 对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.

​ GCLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.

1.3.2 定义业务类,无需实现接口(实现接口也可以,不影响)

与上方相同即可

1.3.3 实现MethodInterceptor方法代理接口,创建代理类

public class CglibProxyHandle implements MethodInterceptor { private Object target; //设置被代理对象 public Object instance(Object obj){ this.target =obj; //创建加强器,用来创建动态代理类 Enhancer enhancer = new Enhancer(); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类) enhancer.setSuperclass(obj.getClass()); //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方 //法进行拦 enhancer.setCallback(this); // 创建动态代理类对象并返回 Object o = enhancer.create(); return o; } // 实现回调方法 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib 执行方法前"); //调用业务类(父类中)的方法 Object o1 = methodProxy.invokeSuper(o, objects); System.out.println("cglib 执行方法后"); return o1; } }

测试

@Test public void test2(){ //TODO CGlib实现 UserServiceImpl user= new UserServiceImpl(); CglibProxyHandle cglibProxyHandle = new CglibProxyHandle(); UserServiceImpl userService = (UserServiceImpl)cglibProxyHandle.instance(user); userService.addUser("a"); }

比较三种代理:

静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;

​ JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;

​ CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

Spring-AOP的使用(xml配置)

1.导入对应的AOP jar 包,需注意spring JAR版本需相同

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>

2.创建通知类

//前置 public void before(){ System.out.println("这是前置通知"); } //后置 public void after(){ System.out.println("这是后置通知"); } //异常通知 public void throwing(){ System.out.println("这是异常通知"); } //最终通知 public void afterEnd(){ System.out.println("这是最终通知"); } //环绕通知 public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分"); Object proceed=null; try { proceed = point.proceed(); System.out.println("这是环绕通知之后的部分"); }catch (Exception e){ System.out.println("这是环绕通知异常的部分"); }finally { System.out.println("这是最终通知"); } return proceed; }

3.配置xml文件

3.1 导入约束

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

3.2配置扫描包以及将我们的通知类交给spring管理

<context:component-scan base-package="com.hello"></context:component-scan> <bean id="myAdvice" class="com.hello.handle.MyAdvice"></bean>

3.3开始AOP的配置

3.3.1使用aop:config标签表明开始AOP的配置

<aop:config> </aop:config>

3.3.2 使用aop:aspect标签表明配置切面,id属性:是给切面提供一个唯一标识,ref属性:是指定通知类bean的Id。

<aop:aspect id="advice" ref="myAdvice"> </aop:aspect>

3.3.3 配置切入点表达式

切入点表达式的写法: 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) 标准的表达式写法: public void com.hello.service.impl.BookServiceImpl.save() 访问修饰符可以省略 void com.hello.service.impl.BookServiceImpl.save() 返回值可以使用通配符,表示任意返回值 * com.hello.service.impl.BookServiceImpl.save() 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. * *.*.*.BookServiceImpl.save()) 包名可以使用..表示当前包及其子包 * *..BookServiceImpl.save() 类名和方法名都可以使用*来实现通配 * *..*.*() 参数列表: 可以直接写数据类型: 基本类型直接写名称 int 引用类型写包名.类名的方式 java.lang.String 可以使用通配符表示任意类型,但是必须有参数 可以使用..表示有无参数均可,有参数可以是任意类型 全通配写法: * *..*.*(..) 实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.hello.service.impl.*.*(..) <aop:pointcut id="ponitcut" expression="execution( * com.hello.service.*.*.*(..))"></aop:pointcut>

3.3.4 在aop:aspect标签的内部使用对应标签来配置通知的类型

<aop:aspect id="advice" ref="myAdivce"> <aop:before method="before" pointcut-ref="ponitcut"></aop:before> <aop:after-returning method="after" pointcut-ref="ponitcut"></aop:after-returning> <aop:after-throwing method="throwing" pointcut-ref="ponitcut"></aop:after-throwing> <aop:after method="afterEnd" pointcut-ref="ponitcut"></aop:after> //环绕通知 <aop:around method="around" pointcut-ref="ponitcut"></aop:around> </aop:aspect>

Spring-aop的使用(注解)

1.导入jar包,跟xml方式导入的一样,需注意spring Jar版本需相同

2.创建通知类,与xml相同,不同的是

@Component //将该类交给spring @Aspect //标注该类是一个切面类 public class MyAdviceAnnoation { @Pointcut("execution( * com.hello.service.*.*.*(..))") //切入点表达式 public void pt1(){ } //前置 // @Before("pt1()") public void before(){ System.out.println("这是前置通知"); } //后置 // @AfterReturning("pt1()") public void after(){ System.out.println("这是后置通知"); } //异常通知 // @AfterThrowing("pt1()") public void throwing(){ System.out.println("这是异常通知"); } //最终通知 //@After("pt1()") public void afterEnd(){ System.out.println("这是最终通知"); } @Around("pt1()") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分"); Object proceed=null; try { proceed = point.proceed(); System.out.println("这是环绕通知之后的部分"); }catch (Exception e){ System.out.println(e.getMessage()); System.out.println("这是环绕通知异常的部分"); }finally { System.out.println("这是最终通知"); } return proceed; } }

3.开启扫描以及spring-aop的注解模式,约束跟我们xml配置的相同

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd "> <context:component-scan base-package="com.hello"></context:component-scan> <!-- 开始spring-aop注解模式--> <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
最新回复(0)