版权声明:Arno https://blog.csdn.net/yu342107056/article/details/86021268
spring的 aop:
aop:面向方面的编程,面向切面的编程,面向横切的编程
方面就是新的业务功能,
把新的方面的功能横切到老的业务的前或后
spring的aop的底层用的是动态代理
如果目标类有接口就默认用jdk动态代理
如果目标类没有接口就用默认cglib动态代理
如果目标类有接口且不是final的,还想用cglib动态代理则需要单独配置
spring aop中定义个一套独有的表达式规则用于指定某些业务方法横切新功能
实现spring aop的步骤:
1.创建一个工程 工程是基础
2.导入jar包 spring的功能基础
spring的基础jar
spring-context.jar
spring-core.jar
spring-beans.jar
spring-expression.jar
commons-logging.jar
spring aop的jar
spring-aop.jar
aspectjweaver-1.8.7.jar
aopalliance-1.0.jar
3.创建java类
老的业务类:
UserDao.java UserDaoImpl.java
UserService.java UserServiceImpl.java
新的业务功能/新方面的功能/新的切面的功能,简称为方面/切面
由动态代理创建代理类用于调用接口方法,在接口方法中耦合切面和老业务功能
4.创建spring的配置文件
springaop的xml版本
springaop的annotation版本
无论是xml版本还是注解版本
a.实例化老业务和新业务的对象(ioc)
b.根据业务需求,设定对象的注入关系(di)
c.写spring的特有的表达式规则,用于指定哪些方法需要横切新的功能<aop:XXX>
5.启动spring的容器读取spring的配置文件
6.从spring容器中取出对应的对象备用
xml版本:
新业务功能:
TransactionManager.java
/**
* 此类是一个新功能类,用于给原有业务添加事务处理的功能
* 符合单一职责原则,此类只做事务管理
* @author Administrator
*
*/
public class TransactionManager {
/**
* 事务的开启
* 在前置通知方法中得到目标的参数及其他信息
*/
public void begin(JoinPoint joinPoint){
System.out.print("事务开启");
System.out.println("目标方法的参数:"+joinPoint.getArgs()[0].getClass());
User user=(User)joinPoint.getArgs()[0];
System.out.println("目标方法的参数的值:"+user.getName()+" "+user.getPassword());
System.out.println("目标方法的名称:"+joinPoint.getSignature().getName());
System.out.println("调用目标方法的目标对象"+joinPoint.getTarget().getClass());
}
/**
* 事务提交
* 在后置通知中得到目标方法的参数及其他信息,还可以得到目标方法的返回值
* 注意:如果有JoinPoint joinPoint参数一定要放参数的第一个位置
*/
public void commit(JoinPoint joinPoint,Object returnValue){
System.out.println("事务提交");
System.out.println("目标方法的返回值:"+returnValue);
}
/**
* 事务的回滚
* 在异常通知中得到目标方法的参数及其他信息,不能得到目标方法的返回值,但能得到异常信息
*/
public void rollback(JoinPoint joinPoint,Throwable ex){
System.out.println("事务回滚");
System.out.println("目标方法的异常信息:"+ex.getMessage());
}
public void finalMethod(JoinPoint joinPoint){
System.out.println("finally");
}
/**
* 环绕通知,可以完全替换前4个通知
* 用环绕通知有一个最大的好处,能够控制目标方法的执行
* @param pjp
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object retVal =null;
try{
//前置通知
System.out.println("前置通知");
System.out.println("目标方法的参数:"+pjp.getArgs()[0].getClass());
User user=(User)pjp.getArgs()[0];
System.out.println("目标方法的参数的值:"+user.getName()+" "+user.getPassword());
System.out.println("目标方法的名称:"+pjp.getSignature().getName());
System.out.println("调用目标方法的目标对象"+pjp.getTarget().getClass());
retVal = pjp.proceed();//调用目标方法
//后置通知
System.out.println("后置通知");
System.out.println("目标方法的返回值:"+retVal);
}catch(Exception e){
//异常通知
System.out.println("异常通知");
e.printStackTrace();
}finally{
//最终通知
System.out.println("最终通知");
}
return retVal;
}
}
spring的配置文件 spring.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:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
file:///c:/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
file:///c:/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
file:///c:/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 老的业务需要实例化对象和注入 -->
<bean id="userDao" class="com.arno.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.arno.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 新的业务/切面实例化对象和注入 /-->
<bean id="tm" class="com.arno.other.TransactionManager">
</bean>
<!-- spring的独有的配置用于做横切
<aop:config 代表的一个spring的aop配置
proxy-target-class="true" 告知spring用cglib生成代理对象
proxy-target-class="false" 告知spring用jdk生成代理对象
-->
<aop:config proxy-target-class="true">
<!-- <aop:aspect 代表一个切面,一个spring的aop配置可以包含很多个切面 -->
<aop:aspect id="myTransactionAspect" ref="tm">
<!-- <aop:pointcut 切点,且在方法上,由execution表达式限定哪些方法需要切面 ,可以写多个切点 -->
<aop:pointcut id="businessService"
expression="execution(* com.arno.service..*.*(..))"/>
<aop:pointcut id="businessService1"
expression="execution(* com.arno.service..*.*(..)) and args(user)"/>
<!-- 前置通知/前置功能
aop:before 切在前面
pointcut-ref 切在哪个方法上
method:切的什么功能
arg-names="user" 参数
-->
<!-- <aop:before -->
<!-- pointcut-ref="businessService" -->
<!-- method="begin"/> -->
<!-- 后置通知/后置功能 -->
<!-- <aop:after-returning -->
<!-- pointcut-ref="businessService" -->
<!-- method="commit" -->
<!-- returning="returnValue"/> -->
<!-- 异常通知/异常功能 -->
<!-- <aop:after-throwing -->
<!-- pointcut-ref="businessService" -->
<!-- method="rollback" -->
<!-- throwing=""/> -->
<!-- 最终通知 -->
<!-- <aop:after -->
<!-- pointcut-ref="businessService" -->
<!-- method="finalMethod"/> -->
<!-- 环绕通知 -->
<aop:around
pointcut-ref="businessService"
method="around"/>
</aop:aspect>
</aop:config>
</beans>
有关于spring aop中的那些让你发狂的几个概念解释:
切面 aspect:
切面是一个类,是一个新业务的功能类;
切面类中有很多个通知方法
比如:TransactionManager.java
连接点 joinpoint:
连接点就是用代理对象调用目标方法的那句话
只有用代理对象调用了目标方法,才有可能把新的业务横切到老的业务上
没有连接点,老的业务和新的业务永远不会耦合
比如:userService.addUser(new User());//userService是代理对象
通知 advice:
通知就是一些功能方法,每个功能就是一个通知,通知存在于切面类中
可以有很多个通知的方法
比如: begin方法 前置通知
commit方法 后置通知
rollback方法 异常通知
finalMethod方法 最终通知
around方法 环绕通知
环绕通知能完全替代上面的4个通知,环绕通知能控制目标方法的执行
环绕通知和前4中不要同时使用
切入点 pointcut:
切入点是把切面中的通知,切在什么地方,一般指的老业务中的方法上
切入点是且在方法上
切入点一般要配合execution表达式和within表达式一同使用
execution表达式
修饰符 返回值 包名.类名.方法名(参数) throws 异常类型
spring的官方案例:
§ the execution of any public method:匹配任意公有方法
公有的 任意返回类型,任意包任意类,任意方法名称,任意参数
execution(public * *(..))
§ the execution of any method with a name beginning with "set":
任意返回类型,任意包,任意类,任意以set开头的方法,任意参数
execution(* set*(..))
§ the execution of any method defined by the AccountService interface:
任意返回类型,com.xyz.service.AccountService接口中的任意方法,任意参数
execution(* com.xyz.service.AccountService.*(..))
§ the execution of any method defined in the service package:
任意返回类型,com.xyz.service包下任意类或接口,任意方法,任意参数
execution(* com.xyz.service.*.*(..))
§ the execution of any method defined in the service package or a sub-package:
任意返回类型,com.xyz.service包及其子包中的任意类,任意方法,任意参数
execution(* com.xyz.service..*.*(..))
winthin表达式:
spring官方案例:
any join point (method execution only in Spring AOP) within the service package:
com.xyz.service包中的任意接口或类
within(com.xyz.service.*)
any join point (method execution only in Spring AOP) within the service package or a sub-package:
com.xyz.service包及其子包中的任意类
within(com.xyz.service..*)
总结execution控制粒度细,控制到方法 winthin控制粒度粗,控制类
目标对象 targetObject:是老业务类的对象 比如UserServiceImpl的对象
aop代理:
用spring的aop的底层用的是动态代理,aop代理就是用springaop生成的代理对象
织入weaving:把新的功能织入到老的功能上去,引申义就是横切/切入
springaop的注解版本:
TransactionManager.java
/**
* 此类是一个新功能类,用于给原有业务添加事务处理的功能
* 符合单一职责原则,此类只做事务管理
* @author Administrator
*
*/
@Component("tm")
@Aspect
public class TransactionManager {
@Pointcut("execution(* com.arno.service..*.*(..))")
public void myTransactionPointCut() {}
/**
* 事务的开启
* 在前置通知方法中得到目标的参数及其他信息
*/
//@Before(value="myTransactionPointCut()")
public void begin(JoinPoint joinPoint){
System.out.print("事务开启");
System.out.println("目标方法的参数:"+joinPoint.getArgs()[0].getClass());
User user=(User)joinPoint.getArgs()[0];
System.out.println("目标方法的参数的值:"+user.getName()+" "+user.getPassword());
System.out.println("目标方法的名称:"+joinPoint.getSignature().getName());
System.out.println("调用目标方法的目标对象"+joinPoint.getTarget().getClass());
}
/**
* 事务提交
* 在后置通知中得到目标方法的参数及其他信息,还可以得到目标方法的返回值
* 注意:如果有JoinPoint joinPoint参数一定要放参数的第一个位置
*/
//@AfterReturning(value="myTransactionPointCut()",returning="returnValue")
public void commit(JoinPoint joinPoint,Object returnValue){
System.out.println("事务提交");
System.out.println("目标方法的返回值:"+returnValue);
}
/**
* 事务的回滚
* 在异常通知中得到目标方法的参数及其他信息,不能得到目标方法的返回值,但能得到异常信息
*/
//@AfterThrowing(value="myTransactionPointCut()",throwing="ex")
public void rollback(JoinPoint joinPoint,Throwable ex){
System.out.println("事务回滚");
System.out.println("目标方法的异常信息:"+ex.getMessage());
}
//@After(value="myTransactionPointCut()")
public void finalMethod(JoinPoint joinPoint){
System.out.println("finally");
}
/**
* 环绕通知,可以完全替换前4个通知
* 用环绕通知有一个最大的好处,能够控制目标方法的执行
* @param pjp
* @return
* @throws Throwable
*/
@Around(value="myTransactionPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object retVal =null;
try{
//前置通知
System.out.println("前置通知");
System.out.println("目标方法的参数:"+pjp.getArgs()[0].getClass());
User user=(User)pjp.getArgs()[0];
System.out.println("目标方法的参数的值:"+user.getName()+" "+user.getPassword());
System.out.println("目标方法的名称:"+pjp.getSignature().getName());
System.out.println("调用目标方法的目标对象"+pjp.getTarget().getClass());
retVal = pjp.proceed();//调用目标方法
//后置通知
System.out.println("后置通知");
System.out.println("目标方法的返回值:"+retVal);
}catch(Exception e){
//异常通知
System.out.println("异常通知");
e.printStackTrace();
}finally{
//最终通知
System.out.println("最终通知");
}
return retVal;
}
}
spring.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:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
file:///c:/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
file:///c:/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
file:///c:/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 解析@Controller,@Service,@Repository@Component
@Resource @Autowired@Qualifier
-->
<context:component-scan base-package="com.arno.dao"></context:component-scan>
<context:component-scan base-package="com.arno.service"></context:component-scan>
<context:component-scan base-package="com.arno.other"></context:component-scan>
<!-- 解析有关于aop的注解
@Aspect @Pointcut
@Before @AfterReturning @AfterThrowing @After
@Around
-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>
一个需求或目的:
老的业务
新的业务
新的业务横切到老的业务上
代理模式能够实现
静态代理:
用静态代理类方式把老业务和新的业务耦合在一起
在编译期间就确认老业务和新业务的耦合关系
动态代理:
jdk的动态代理
动态生成代理类(由jdk底层来创建,程序员是看不见的)
用动态生成的代理类和InvocationHandler接口一起做到老业务和新业务的耦合
代理类是在运行期间才出现的,所以叫做动态代理
代理类和目标类是兄弟关系
cglib动态代理
动态生成代理类(由asm.jar来创建,程序员是看不见的)
用动态生成的代理类和MethodInterceptor接口一起做到老业务和新业务的耦合
代理类是在运行期间才出现的,所以叫做动态代理
代理类是目标类的子类
动态代理实现,会把新的业务功能全部横切到老业务的方法的前或后
spring aop解决了把新业务横切到老业务的部分方法的前或后
开发效率
spring aop > 动态代理 >静态代理
执行效率
静态代理 > 动态代理 >spring aop