aop:
概念:
预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续
作用:
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
常用的场景:
日志记录
权限控制
事务控制
性能测试
底层使用的动态代理
常见的技术:
jdk:
前提必须有接口实现 Proxy.newProxyInstance(类加载器,需要实现的接口,执行处理类)
####spring中使用:
CGLIB:核心对象enhancer对象
hibernate和struts
用的是这个,javassist
下面我用Proxy和enhancer对象写两个例子:
需求:在dao对象 update方法前打印一个当前时间:
proxy代码
//被代理对象
final OrderDao orderDao = new OrderDaoImpl();
//创建代理对象
OrderDao orderDaoProxy = (OrderDao)Proxy.newProxyInstance(orderDao.getClass().getClassLoader(), orderDao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取update方法
if("update".equals(method.getName())){
System.out.println(new Date().toLocaleString());
}
//执行原来的操作
return method.invoke(orderDao, args);
}
});
下面是用CGLIB实现
//创建被代理对象
final UserDao userDao = new UserDao();
//创建代理对象
UserDao userDaoProxy = (UserDao)Enhancer.create(userDao.getClass(), new MethodInterceptor() {
@Override
/**
* 前三个参数和jdk中proxy中的InvocationHandler的参数一样
* MethodProxy:方法的代理对象(我们在这用不到)
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("find".equals(method.getName())){
System.out.println(new Date().toLocaleString());
}
//执行原来的逻辑
return method.invoke(userDao, args);
}
});
aop中的术语
英文 | 名称 | 用法 | 备注 |
---|---|---|---|
joinpoint | 连接点 | 可以被增强的方法 | 例如:save update delete find |
pointcut | 切入点 | 被增强的方法 | 例如:update |
advice | 增强/通知 | 增强的代码 | |
target | 目标对象 | 被增强的对象 | 例如:userDao |
weaving | 织入 | 将通知添加到目标对象的切入点上过程 | |
proxy | 代理对象 | 增强后产生的对象 | |
aspect | 切面 | 切入点和通知的组合 |
基于aspectj的aop的xml方式入门
步骤:
1.导入jar包
核心的6个+aop的4个
aop4个是:
spring核心包:
aop
spring整合aspectj :spring-aspects-4.2.4.RELEASE.jar
spring依赖包:
aop联盟(规范) :org.aopalliance
aspectj :org.aspectj
2.创建配置文件
导入约束(beans和aop)
<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" 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">
</beans>
3.创建目标类
CustomerDao 和 CustomerDaoImpl
public class CustomerDaoImpl implements CustomerDao{
public void save(){
System.out.println("customerDao中的save执行了");
}
public void delete(){
System.out.println("customerDao中的delete执行了");
}
public void update(){
System.out.println("customerDao中的update执行了");
}
public void find(){
System.out.println("customerDao中的find执行了");
}
}
创建切面(通知/增强)类(放的是增强的代码)
public class MyAspectXml {
/**
* 要在customerdao中的delete方法执行之前调用
*/
public void printTime(){
System.out.println(new Date().toLocaleString());
}
}
将目标类和切面类加入spring容器中
<bean id="customerDao" class="com.xx.c_aop_hello.CustomerDaoImpl"></bean>
<bean id="myAspect" class="com.xx.c_aop_hello.MyAspectXml"></bean>
aop配置(增强方法)
格式:
<aop:config>
//1定义切入点
<aop:pointcut expression="execution(切入点表达式)" id="切入点名字"/>
//2配置切面
<aop:aspect ref="切面类">
<aop:通知类型 method="切面类中的某个方法(通知)" pointcut-ref="切入点名字"/>
</aop:aspect>
</aop:config>
配置代码入下:
<!-- aop配置 -->
<aop:config>
<!-- 1.定义切入点 -->
<aop:pointcut expression="execution(* com.xx.c_aop_hello.CustomerDaoImpl.delete())" id="pointcut1"/>
<!-- 2.配置切面 -->
<aop:aspect ref="myAspect">
<aop:before method="printTime" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
切入点表达式:
必须使用execution函数引入 execution(表达式)
表达式格式:
[方法的修饰符] 返回值 包名.类名.方法名(参数列表)
例如:
表达式 | 作用 |
---|---|
com.xx.c_aop_hello.CustomerDaoImpl.delete() | 指定包下的指定类的无参的delete |
com.xx.c_aop_hello.CustomerDaoImpl.delete(..) | 指定包下的指定类的任何参数的delete |
com.xx.c_aop_hello.CustomerDaoImpl.*() | 指定包下的指定类的无参的所有方法 |
com.xx.c_aop_hello..(..) | 指定包下的所有类的所有方法 |
com.xx...(..) | 指定包下及其子包下的的所有类的所有方法 |
com.xx...save(..) | 指定包下及其子包下的的所有类的以save开头的方法 |
com.xx.service.CustomerDao+.*(..) | 指定包下的指定类及其子类和实现类的所有方法 |
通知类型:
通知类型 | 何时执行 | code |
---|---|---|
前置通知 | 在切入点执行前执行的方法 | before |
后置通知 | 在切入点执行后执行的方法,还可以获取切入点的返回值 | after-returning |
环绕通知 | 在切入点执行前和后要执行的方法 | around |
异常通知 | 在切入点执行前和后要执行的方法 | around |
环绕通知 | 在切入点执行时抛异常的时候要执行的方法 还可以获取异常信息 | after-throwing |
最终通知 | 在切入点执行后都要执行的方法 | after |
异常通知,后置通知,环绕通知详解:
如果想获取after-returning绑定目标方法的返回值,就加上returning标签
<aop:after-returning method="afterReturningMethod" pointcut-ref="pt2" returning="res"/>
然后在通知就可以加入这个变量了
public void afterReturningMethod(Object res){
System.out.println("------afterReturning------:"+res);
}
这样就能把这个的目标对象的返回值打印出来了
同理,如果想获取AfterThrowing绑定的目标方法的返回值,就加上throwing属性
这个环绕通知原理跟动态代理差不多,配置没什么特别的,但是通知有点不太一样,写法是这样的:
public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable{
System.out.println("------around:1------");
//让切入点执行
Object res = jp.proceed();
System.out.println("------around:2------");
return res;
}
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pt4" throwing="ex"/>
打印异常信息
public void afterThrowingMethod(Throwable ex){
System.out.println("------afterThrowing------"+ex.getMessage());
}
案例:aop的注解方式
1.xml和注解混合方式
步骤:
1.导入jar:11个
2.创建配置文件,导入beans aop context约束
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
3.编写目标类
@Repository("linkmanDao")
public class LinkmanDao {
public void save(){
System.out.println("linkmanDao中的save执行了");
}
public int delete(){
System.out.println("linkmanDao中的delete执行了");
return 100;
}
public void update(){
System.out.println("linkmanDao中的update执行了");
}
public void find(){
System.out.println("linkmanDao中的find执行了");
//int i = 1/0;
}
4.编写切面类
@Component("myAspectAdvice")
@Aspect//声明它是一个切面类
public class MyAspectAdvice {
//@Before(value="execution(* com.xx.d_aop_advice.LinkmanDao.save(..))")
@Before("execution(* com.xx.a_aop_hello.LinkmanDao.save(..))")
public void beforeMethod(){
System.out.println("------before------");
}
@AfterReturning(value="execution(* com.xx.a_aop_hello.LinkmanDao.delete(..))",returning="res")
public void afterReturningMethod(Object res){
System.out.println("------afterReturning------:"+res);
}
@AfterThrowing(value="execution(* com.xx.a_aop_hello.LinkmanDao.find(..))",throwing="ex")
public void afterThrowingMethod(Throwable ex){
System.out.println("------afterThrowing------"+ex.getMessage());
}
@Around(value="execution(* com.xx.a_aop_hello.LinkmanDao.update(..))")
public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable{
System.out.println("------around:1------");
//让切入点执行
Object res = jp.proceed();
System.out.println("------around:2------");
return res;
}
@After(value="execution(* com.xx.a_aop_hello.LinkmanDao.find(..))")
public void afterMethod(){
System.out.println("------after------");
}
注:下面几步上面代码已经编好
5.给目标类和切面类添加注解 加入spring容器
6.在切面类上添加注解 @Aspect //声明切面类
7.在切面类的方法上添加通知注解
@Before(value=”execution(表达式)”)
8.在配置文件中开启组件扫描\在配置文件中开启aop注解自动代理
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.xx"/>
<!-- 在配置文件中开启aop注解自动代理-->
<aop:aspectj-autoproxy/>
注解小结
切面类上的注解
@Aspect
切面类中的方法上的注解(通知注解)
@Before(value=”execution(表达式)”)
@AfterReturning(value=”execution(表达式)”)
@Around(value=”execution(表达式)”)
@AfterThrowing(value=”execution(表达式)”)
@After(value=”execution(表达式)”)
切入点注解的切入点注解方式
在切面类中提供给一个方法(方法名就相当于之前的id值)
在方法上添加注解
@Pointcut(value=”execution(表达式)”)
通知中就可以通过以下注释添加
@Before(value=”类名.切入点方法名()”)
例如:
//@After(value="execution(* com.xx.a_aop_hello.*.find(..))") --这个是之前的方式
@After(value="MyAspectAdvice.pt1()")
public void afterMethod(){
System.out.println("------after------");
}
@Pointcut(value="execution(* com.xx.a_aop_hello.*.find(..))")
public void pt1(){}
纯注解方式(了解)
创建一个配置类:
能做以下两件事
开启组件扫描
开启aop注解的自动代理
就可以删除配置文件了,其他的与上述的混合方式一样
在配置类上添加注解:
@ComponentScan(value=”包名”)
@EnableAspectJAutoProxy
代码:
@ComponentScan(value="com.xx")
@Configuration
public class SpringConfig {
}