- 面向切面编程(Aspect Oriented Programming)
- aop是一种编程方法,通过预编译和动态代理模式结合来实现。
- 使用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() { } }
-
-
-
-
-
- 切面(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
-