SpringAOP随笔

it2023-08-12  65

什么是aop:

面向切面编程(Aspect Oriented Programming)aop是一种编程方法,通过预编译和动态代理模式结合来实现。使用SpringAOP对业务逻辑的各个部分可以进行隔离,例如:日志,开关事务等,让这些和业务逻辑相关的代码和业务逻辑分离开。抽取在一个公共的组件中,然后通过我们的程序在执行中动态地织入到业务代码的合适位置。SpringAOP降低程序的耦合度。而且可以大大减少代码量。还可以达到程序重用的效果。

SpringAOP应用场景

场景:针对非业务代码但是又不得不做的,不可能针对每个模块都编写一次。

解决:

如开启和关闭事务

第一种实现:

public class UserServiceImpl implements UserService { @Override public void updateUser(User user) { beginTransaction(); System.out.println(user); endTransaction(); } @Override public void addUser(User user) { beginTransaction(); System.out.println(user); endTransaction(); } public void beginTransaction(){ System.out.println("开启事务!"); } public void endTransaction(){ System.out.println("关闭事务!"); } }

第二种实现

public class TransactionHandler { public void beginTransaction(){ System.out.println("开启事务!"); } public void endTransaction(){ System.out.println("关闭事务!"); } } public class UserServiceImplHandler extends TransactionHandler implements UserService { @Override public void addUser(User user) { this.beginTransaction(); System.out.println(user); this.endTransaction(); } @Override public void updateUser(User user) { this.beginTransaction(); System.out.println(user); this.endTransaction(); } }

第三种:动态代理的方式

动态代理区分两种

JDK动态代理

代理对象和目标对象必需实现相同的接口

package com.fxyh.spring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TransactionHandlerWithJDKDynamicProxy implements InvocationHandler { private Object targetObject; public TransactionHandlerWithJDKDynamicProxy() { } public TransactionHandlerWithJDKDynamicProxy(Object targetObject) { this.targetObject = targetObject; } public Object createProxyInstance(){ return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } /** * * @param proxy 代理对象 * @param method 目标对象的方法 * @param args 方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnObj; if (proxy == null) throw new IllegalArgumentException(""); if (method == null) throw new IllegalArgumentException(""); String methodName = method.getName(); boolean flag = methodName.startsWith("add") || methodName.startsWith("delete") || methodName.startsWith("update"); if (flag){ beginTransaction(); } returnObj = method.invoke(this.targetObject,args); if (flag){ endTransaction(); } return returnObj; } private void endTransaction() { System.out.println("JDK关闭事务!"); } private void beginTransaction() { System.out.println("JDK开启事务!"); } } package com.fxyh.spring.service.impl; import com.fxyh.spring.entity.User; import com.fxyh.spring.proxy.TransactionHandlerWithCGlibDynamicProxy; import com.fxyh.spring.service.UserService; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class UserServiceImplHandlerWithCGlibDynamicProxyTest { private UserService userService; private TransactionHandlerWithCGlibDynamicProxy transactionHandlerWithCGlibDynamicProxy; @Before public void setUp() { this.transactionHandlerWithCGlibDynamicProxy = new TransactionHandlerWithCGlibDynamicProxy(new UserServiceImplHandlerWithCGlibDynamicProxy()); this.userService = (UserService) this.transactionHandlerWithCGlibDynamicProxy.createProxyInstance(); } @Test public void addUser() { User user = new User(); user.setUsername("zhangsan"); user.setPassword("123456"); this.userService.addUser(user); } }

Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this);

这也是为啥jdk动态代理要实现相同的接口的原因。

CGLib动态代理

代理对象和目标对象不需要实现相同的接口,只需要维护父子关系

package com.fxyh.spring.proxy; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.InvocationHandler; import java.lang.reflect.Method; public class TransactionHandlerWithCGlibDynamicProxy implements InvocationHandler { private Object targetObject; public TransactionHandlerWithCGlibDynamicProxy() { } public TransactionHandlerWithCGlibDynamicProxy(Object targetObject) { this.targetObject = targetObject; } public Object createProxyInstance(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.targetObject.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnObj; if (proxy == null) throw new IllegalArgumentException(""); if (method == null) throw new IllegalArgumentException(""); String methodName = method.getName(); boolean flag = methodName.startsWith("add") || methodName.startsWith("delete") || methodName.startsWith("update"); if (flag){ beginTransaction(); } returnObj = method.invoke(this.targetObject,args); if (flag){ endTransaction(); } return returnObj; } private void endTransaction() { System.out.println("CGlib关闭事务!"); } private void beginTransaction() { System.out.println("CGlib开启事务!"); } } package com.fxyh.spring.service.impl; import com.fxyh.spring.entity.User; import com.fxyh.spring.proxy.TransactionHandlerWithCGlibDynamicProxy; import com.fxyh.spring.service.UserService; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class UserServiceImplHandlerWithCGlibDynamicProxyTest { private UserService userService; private TransactionHandlerWithCGlibDynamicProxy transactionHandlerWithCGlibDynamicProxy; @Before public void setUp() { this.transactionHandlerWithCGlibDynamicProxy = new TransactionHandlerWithCGlibDynamicProxy(new UserServiceImplHandlerWithCGlibDynamicProxy()); this.userService = (UserService) this.transactionHandlerWithCGlibDynamicProxy.createProxyInstance(); } @Test public void addUser() { User user = new User(); user.setUsername("zhangsan"); user.setPassword("123456"); this.userService.addUser(user); } @Test public void updateUser() { } }

SpringAOP相关术语

切面(Aspect) 横切关注点将非业务代码抽取出来放到一个组件类中,这个组件就称为切面。 通知(Advice) 非业务代码建成的方法就称为通知通知分为:前通知,后通知,返回通知,环绕通知和异常通知。 切入点(PointCut) 将通知用在目标对象上的位置(细粒度控制使用在哪些类的哪些方法上),也就是定义的哪些连接点会得到通知。com.fxyh.service.*.*(…) 连接点(JoinPoint) 通知织入的位置 目标对象(TargetObject)通知者(Advisor) 通知的增强Advisor = Advice + PointCut 织入(Weaving) 把切面应用到目标对象来创建新的代理对象的过程 导入(Introduction) 允许我们向现有的类添加新方法属性。就是把切面用到目标类中。

SpringAOP的实现

使用proxyFactoryBean的方式

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userService" class="com.fxyh.spring.service.impl.UserServiceImpl"/> <bean id="transactionHandler" class="com.fxyh.spring.handler.TransactionHandler"/> <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces"> <array> <value>com.fxyh.spring.service.UserService</value> </array> </property> <property name="interceptorNames"> <array> <value>transactionHandler</value> </array> </property> <property name="targetName" value="userService"/> </bean> </beans> package com.fxyh.spring.handler; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.lang.reflect.Method; public class TransactionHandler implements MethodInterceptor { public void beginTransaction(){ System.out.println("开始事务!"); } public void endTransaction(){ System.out.println("关闭事务!"); } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String methodName = method.getName(); boolean isUse = methodName.startsWith("save") || methodName.startsWith("delete") || methodName.startsWith("update"); if (isUse){ beginTransaction(); } //调用目标方法 Object returnObj = invocation.proceed(); if (isUse){ endTransaction(); } return returnObj; } } package com.fxyh.spring.service; import com.fxyh.spring.domain.User; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import static org.junit.Assert.*; public class UserServiceTest { private ApplicationContext context; private UserService userService; @Before public void setUp() throws Exception { this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); this.userService = (UserService) this.context.getBean("proxyFactoryBean"); } @Test public void saveUser() { User user = new User(); user.setId(1); user.setUsername("zhangsan"); user.setAge(18); userService.saveUser(user); } }

使用aop命名空间方式

<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="userService" class="com.fxyh.spring.service.impl.UserServiceImpl"/> <bean id="transactionHandlerAOP" class="com.fxyh.spring.handler.TransactionHandlerAOP"/> <aop:config> <!-- 第一个*号表示返回值,此时为任意返回值也可没有返回值 第二个*号表示所有的类或接口 第三个*号表示所有的方法 ..表示方法为任意参数 --> <aop:pointcut id="AllMethod" expression="execution(* com.fxyh.spring.service.*.add*(..)) || execution(* com.fxyh.spring.service.*.delete*(..))"/> <aop:aspect id="transactionHandlerAspect" ref="transactionHandlerAOP"> <aop:before method="beginTransaction" pointcut-ref="AllMethod"/> <aop:after method="endTransaction" pointcut-ref="AllMethod"/> </aop:aspect> </aop:config> </beans> - ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.21.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.21.RELEASE</version> </dependency> package com.fxyh.spring.handler; public class TransactionHandlerAOP { public void beginTransaction(){ System.out.println("开始事务!"); } public void endTransaction(){ System.out.println("关闭事务!"); } } package com.fxyh.spring.service; import com.fxyh.spring.domain.User; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserServiceAOPTest { private ApplicationContext context; private UserService userService; @Before public void setUp() throws Exception { this.context = new ClassPathXmlApplicationContext("classpath:applicationContext-aop.xml"); this.userService = this.context.getBean(UserService.class); } @Test public void saveUser() { User user = new User(); user.setId(1); user.setUsername("zhangsan"); user.setAge(18); userService.saveUser(user); } @Test public void findUserById() { userService.findUserById(1); } }

这里要注意要导入spring-aop和spring-aspects两个包。

这里定义的只有add和delete开头的方法才会开启和关闭事务。findUserById测试的时候就不会开启事务。

使用注解方式

package com.fxyh.spring.handler; import com.fxyh.spring.exception.CustomException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class TransactionHandlerAutoAOP { /* * 定义一个公共的切点 */ @Pointcut("execution(* com.fxyh.spring.service.*.*(..))") public void pointcut() {} /** * 前置通知 * @param joinPoint */ @Before(value = "execution(* com.fxyh.spring.service.*.*(..))") public void beginTransaction(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Object target = joinPoint.getTarget(); String kind = joinPoint.getKind(); Signature signature = joinPoint.getSignature(); System.out.println("注解方式开始事务!"); } /** * 后置通知 */ @After(value = "pointcut()") public void endTransaction(){ System.out.println("注解方式关闭事务!"); } /** * 异常通知 * @param joinpoint * @param exception */ @AfterThrowing(value = "pointcut()", throwing = "exception") public void logException(JoinPoint joinpoint, Exception exception){ System.out.println(exception.getMessage()); } /** * 自定义异常通知 * @param joinPoint * @param exception */ @AfterThrowing(value = "pointcut()", throwing = "exception") public void logException(JoinPoint joinPoint, CustomException exception){ System.out.println(exception.getMessage()); } } package com.fxyh.spring.exception; public class CustomException extends RuntimeException { private static final long serialVersionUID = 5100311675985804166L; private String message; public CustomException(){} public CustomException(String message){ super(message); this.message = message; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }

配置:

xml方式

<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.fxyh.spring"/> <aop:aspectj-autoproxy /> </beans>

JavaConfig方式

package com.fxyh.spring; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan("com.fxyh.spring") @EnableAspectJAutoProxy public class AppAOPConfig { }

测试:

package com.fxyh.spring.service; import com.fxyh.spring.domain.User; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserServiceAutoAOPTest { private ApplicationContext context; private UserService userService; @Before public void setUp() throws Exception { //xml配置方式 this.context = new ClassPathXmlApplicationContext("classpath:applicationContext-autoAop.xml"); //JavaConfig方式 //this.context = new AnnotationConfigApplicationContext(AppAOPConfig.class); this.userService = this.context.getBean(UserService.class); } @Test public void saveUser() { User user = new User(); user.setId(1); user.setUsername("zhangsan"); user.setAge(18); userService.saveUser(user); } @Test public void findUserById() { userService.findUserById(1); } }

需要完整的源码可以去我的GitHub下载:https://github.com/fxyh9712/SpringStudy

最新回复(0)