AOP
面向切面编程,通过预编译和运行期动态代理,实现程序功能,是函数式编程的一个衍生范型
AOP实现方式
- 动态代理Proxy:接口+实现类
- cglib字节码: 实现类 (通过创建目标类的子类来实现功能)
JDK动态代理
该方式必须有接口和实现类,原因是对应回调传参的参数是接口类型
/**
* 切面类:增加代码 与 切入点 结合
*/
public class MyAspect2 {
public void before(){
System.out.println("开启事务...");
}
public void after(){
System.out.println("提交事务...");
}
}
/**
* 接口
*/
public interface IUserService {
public void add();
public void add(User user);
//切面编程
public void addUser();
public void updateUser();
public void deleteUser();
public int deleteUser(int id);
}
/**
* 接口实现类
*/
@Service("myUserService")
public class UserServiceImpl implements IUserService {
@Autowired //spring会自动注入userDao赋值
private IUserDao userDao;
@Override
public void add(User user) {
System.out.println("service 添加用户:" + user);
//调用dao
userDao.add(user);
}
@Override
public void addUser() {
System.out.println("添加用户。。。。");
}
@Override
public void updateUser() {
System.out.println("更新用户。。。。");
}
@Override
public void deleteUser() {
System.out.println("删除用户。。。。");
}
@Override
public int deleteUser(int id) {
System.out.println("通过id删除用户");
return 1;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void add() {
System.out.println("创建用户...." + name);
}
public UserServiceImpl() {
//System.out.println("UserServiceImpl()调用了");
}
}
/**
* 工厂类
*/
public class MyBeanFactory {
/**
* JDK实现代理
* @return
*/
public static IUserService createUserService(){
//1.创建目标对象target
final IUserService userService = new UserServiceImpl();
//2.声明切面类对象
final MyAspect2 aspect = new MyAspect2();
//3.把切面类2个方法 应用 目标类
//3.1 创建JDK代理,拦截方法
/*newProxyInstance(
ClassLoader loader, 类加载器,写当前类(记住即可)
Class<?>[] interfaces, 接口,接口的方法会被拦截
InvocationHandler h) 处理
*/
IUserService seriviceProxy = (IUserService) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
//因为此处必须传接口,所以原生jdk动态代理必有有接口
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开启事务
aspect.before();
//方法返回值是 业务方法的返回值
Object retObj = method.invoke(userService,args);
//System.out.println("拦截返回值:" + retObj);
//提交事务
aspect.after();
return retObj;
}
}
);
return seriviceProxy;
}
}
/**
* 测试方法
*/
@Test
public void test1() throws Exception {
//实现AOP编程,使用cglib代理来实现
StudentService ss = MyBeanFactory.createStudentService();
ss.delete();
/* ss.update();
ss.add();*/
}
cglib字节码实现AOP
只需要实现类(当然有接口和实现类也可)即可,当然切面对象也不能少即(MyAspect2.java)
public class StudentService {
public void delete(){
System.out.println("删除学生");
}
public void add(){
System.out.println("add学生");
}
public void update(){
System.out.println("update学生");
}
}
public class MyBeanFactory {
/**
* cglib实现代理
* @return
*/
public static StudentService createStudentService(){
//1.创建目标对象target
final StudentService studentService = new StudentService();
//2.声明切面类对象
final MyAspect2 aspect = new MyAspect2();
//3.创建增强对象
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(studentService.getClass());
//设置回调【拦截】
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
/**
* proxy:
* proxy代理对象是StudentService的子类
*/
aspect.before();
//方式一:放行方法
//Object retObj = method.invoke(studentService,args);
//方式二:等同于方式一,通过代理方法调用父类,好处是解耦了,因为没有引用外部变量
Object retObj = methodProxy.invokeSuper(proxy,args);//解耦
System.out.println("拦截.....");
aspect.after();
return retObj;
}
});
//创建代理对象
StudentService serviceProxy = (StudentService) enhancer.create();
return serviceProxy;
}
}
Spring的AOP自动和半自动
不论自动还是半自动,目的都是不写生成代理对象的相关方法,例如避免书写上面的MyBeanFactory.java文件
Spring项目中半自动AOP
需要额外添加AOP联盟(规范)、spring-aop(实现)两个jar包
- com.springsource.org.aopalliance-1.0.0.jar AOP联盟
- spring-aop-3.2.0.RELEASE.jar AOP实现
简单案例
<!--bean.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p ="http://www.springframework.org/schema/p"
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/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置UserService-->
<bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
<!-- 配置切面类对象-->
<bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
<!-- 配置代理对象
默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
-->
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List-->
<property name="interfaces" value="com.zengqiang.service.IUserService">
</property>
<!-- 目标对象 -->
<!-- 注意此处和上下不同。用的是ref,而不是value,value是特殊写法,记下即可 -->
<property name="target" ref="userService"/>
<!-- 切面类-->
<property name="interceptorNames" value="myAspect"></property>
<!-- 配置使用cglib生成-->
<property name="optimize" value="true"></property>
</bean>
</beans>
/**
* 切面类:增加代码 与 切入点 结合
务必实现MethodInterceptor
*/
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//拦截方法
System.out.println("开启事务...");
//放行
Object retObj = mi.proceed();
System.out.println("拦截.....");
System.out.println("提交事务...");
return retObj;
}
}
Spring项目中全自动AOP
需要com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(织入)
需要添加的索引
- xmlns:aop ="http://www.springframework.org/schema/aop"
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop.xsd
<!--bean.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p ="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置UserService-->
<bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
<bean id="studentService" class="com.zengqiang.service.StudentService"></bean>
<!-- 配置切面类对象-->
<bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
<!-- 全自动AOP配置
1.在bean中配置aop约束
2.配置aop:conifg内容,把切入点和通知结合
proxy-target-class:使用cglib实现代理
expression 表达式:*任意
execution(* com.zengqiang.service.*. * (..))
返回值 包名 类名 方法名 参数
proxy-target-class="true":代表开启cglib
-->
<aop:config proxy-target-class="true">
<!-- 切入点:
expression:表达式
每个service的方法前面都开启事务和结束事务
AOP:用于事务配置&日志记录
-->
<aop:pointcut id="myPointcut" expression="execution(* com.zengqing.service.*.*(..))"/>
<!-- 通知(就是切面类对象) 关联 切入点-->
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>
</beans>
AspectJ
基本案例
/**
* 切面类
*/
public class MyAspect3{
public void myBefore(){
System.out.println("前置通知...");
}
public void myAfterReturning(){
System.out.println("后置通知...");
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知...");
System.out.println(pjp.getSignature().getName());//切入点就方法名
System.out.println("开启事务...");
//放行
Object retObj = pjp.proceed();
System.out.println("提交事务...");
return retObj;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p ="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置UserService-->
<bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
<!-- 配置切面对象-->
<bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
<!-- 配置 aop -->
<aop:config>
<!-- aop:指定切面-->
<aop:aspect ref="myAspect">
<!--定义一个切入点-->
<aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
<!-- 配置前置通知...-->
<aop:before method="myBefore" pointcut-ref="myPointcut" />
<!-- 配置后置通知...-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
<!--配置环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
<!-- 配置异常通知
throwing="e" 值,是方法的参数名
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
<!--配置最终通知:不管有没有异常,最终通知都会执行-->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
execution()
语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
修饰符,一般省略
public 公共方法
* 任意
返回值,不能省略
void 返回没有值
String 返回值字符串
* 任意
包,[省略]
com.zengqiang.crm 固定包
com.zengqiang.crm.*.service crm包下面子包任意 (例如:com.zengqiang.crm.staff.service)
com.zengqiang.crm.. crm包下面的所有子包(含自己)
com.zengqiang.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包
类,[省略]
UserServiceImpl 指定类
*Impl 以Impl结尾
User* 以User开头
* 任意
方法名,不能省略
addUser 固定方法
add* 以add开头
*Do 以Do结尾
* 任意
(参数)
() 无参
(int) 一个整型
(int ,int) 两个
(..) 参数任意
throws ,可省略,一般不写。
案例1:
execution(* com.zengqiang.crm.*.service..*.*(..))
案例2:或
<aop:pointcut expression="execution(* com.zengqiang.crm.service.*.*(..)) ||
execution(* com.zengqiang.*Do.*(..))" id="myPointCut"/>
AspectJ基于xml的案例
package com.zengqiang.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
public class MyAspect {
public void myPointcut(){}
public void myBefore(JoinPoint jp){
System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
}
public void myAfterReturning(JoinPoint jp,Object retValue){
System.out.println("3.后置通知..." + jp.getSignature().getName());
System.out.println("返回值:" + retValue);
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
//放行
Object retObj = pjp.proceed();
System.out.println("4.环绕通知....提交事务...");
return retObj;
}
public void myAfterThrowing(JoinPoint jp,Throwable e){
System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
}
public void myAfter(JoinPoint jp){
System.out.println("最终通知..." + jp.getSignature().getName());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p ="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置UserService-->
<bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
<!-- 配置切面对象-->
<bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
<!-- 配置 aop -->
<aop:config>
<!-- aop:指定切面-->
<aop:aspect ref="myAspect">
<!--定义一个切入点-->
<aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
<!-- 其实上面公共的切入点也可以不声明,直接再配置每个通知的时候在每次填写,但是复用性有问题 -->
<!-- 配置前置通知...-->
<aop:before method="myBefore" pointcut-ref="myPointcut" />
<!-- 配置后置通知...-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
<!--配置环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
<!-- 配置异常通知
throwing="e" 值,是方法的参数名
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
<!--配置最终通知:不管有没有异常,最终通知都会执行-->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
AspectJ基于注解的案例
//@Aspect对应 bean.xml中的<aop:config><aop:aspect ref="myAspect"></aop:aspect></aop:config>
@Component
@Aspect //告诉配置文件这是切入点类
public class MyAspect {
//声明一个公共的切入点
//等效于 <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
@Pointcut("execution(* com.zengqiang.service.UserServiceImpl.*(..))")
public void myPointcut(){}
@Before("myPointcut()")
public void myBefore(JoinPoint jp){
System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
//例如此时test中调用的是userService.deleteUser();方法,则上面jp.getSignature().getName()输出
//就是deleteUser
}
/**
* 后置通知获取service方法执行后的返回值
* Object retValue:service方法执行的返回值,如果写了返回值,需要在xml中配置returning
* @param jp
*/
// <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
@AfterReturning(pointcut = "myPointcut()",returning = "retValue")
public void myAfterReturning(JoinPoint jp,Object retValue){
System.out.println("3.后置通知..." + jp.getSignature().getName());
System.out.println("返回值:" + retValue);
}
@Around("myPointcut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
//放行
Object retObj = pjp.proceed();
System.out.println("4.环绕通知....提交事务...");
return retObj;
}
@AfterThrowing(pointcut = "myPointcut()",throwing = "e")
public void myAfterThrowing(JoinPoint jp,Throwable e){
System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
}
@After("myPointcut()")
public void myAfter(JoinPoint jp){
System.out.println("最终通知..." + jp.getSignature().getName());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p ="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置扫描注解的位置 -->
<context:component-scan base-package="com.zengqiang"/>
<!-- 配置aop注解生效-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--aop配置-->
<aop:config>
<aop:aspect ref="myAspect"></aop:aspect>
</aop:config>
</beans>
当AspectJ-xml和注解同时存在的执行顺序
大部分情况下,这个顺序对业务没影响,主要注意事务提交和资源释放的顺序。该顺序针对上面AspectJ基于注解的案例中的MyAspect.JAVA
- service方法有返回值 + 无异常 + XML
1.前置通知...deleteUser
2.环绕通知...开启事务...deleteUser
通过id删除用户
3.后置通知...deleteUser
返回值:1
4.环绕通知....提交事务...
最终通知...deleteUser
- service方法有返回值 + 无异常 + 注解
2.环绕通知...开启事务...deleteUser
1.前置通知...deleteUser
通过id删除用户
4.环绕通知....提交事务...
最终通知...deleteUser
3.后置通知...deleteUser
返回值:1
- service方法没有返回值 + 无异常 + XML
1.前置通知...deleteUser
2.环绕通知...开启事务...deleteUser
删除用户。。。。
3.后置通知...deleteUser
返回值:null
4.环绕通知....提交事务...
最终通知...deleteUser
- service方法没有返回值 + 无异常 + 注解
2.环绕通知...开启事务...deleteUser
1.前置通知...deleteUser
删除用户。。。。
4.环绕通知....提交事务...
最终通知...deleteUser
3.后置通知...deleteUser
返回值:null
AOP的事务配置
基于xml的形式
<!-- 配置事务管理器,对照下面的图片使用-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--配置dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
图片1.png
图片2.png
补充:isolation:代表隔离级别,propagation代表传播行为,图上是默认值,其实可以省略不写
网友评论