1、 面向切面编程AOP
- 作用:在不改变程序代码的基础上进行功能增强。
- 原理:Proxy代理。即假如业务是从淘宝网买书,那么淘宝网只需要把数放在包裹并填写地址,剩下的任务由快递公司完成,在这个过程中,快递公司就是淘宝网的代理。
2、AOP的原理是代理设计模式
java中实现动态代理可以使用四种方式:
- JDK提供的动态代理(原理是实现接口)
- 使用cglib框架(原理是继承)
- 使用javaassist框架(原理是继承)
- 使用Spring框架(原理是实现接口)
2.1 静态代理的实现
概述
代理对象与被代理对象必须实现一个接口,保留被代理对象的接口样式,保持接口不变原则。
实现
proxy.SFSendBook:
package com.wqq.www.proxy.proxy;
import com.wqq.www.proxy.service.ISendBook;
public class SFSendBook implements ISendBook {
private ISendBook who;
public SFSendBook(ISendBook who) {
this.who = who;
}
@Override
public void sendBook() {
System.out.println("顺丰接收书籍,从数据库取得订单信息");
who.sendBook();
System.out.println("顺丰开始送书,最终送达");
}
}
DangDang:
package com.wqq.www.test1.service;
public class DangDang implements ISendBook{
@Override
public void SendBook() {
System.out.println("从数据库取得订单信息");
System.out.println("当当保存了订单信息");
System.out.println("当当配送");
}
}
ISendBook:
package com.wqq.www.proxy.service;
public interface ISendBook {
void sendBook();
}
JD:
package com.wqq.www.proxy.service;
public class JD implements ISendBook{
@Override
public void sendBook() {
System.out.println("京东保存数据库订单信息");
}
}
Test1:
package com.wqq.www.proxy.test;
import com.wqq.www.proxy.proxy.SFSendBook;
import com.wqq.www.proxy.service.DangDang;
import com.wqq.www.proxy.service.ISendBook;
public class Test1 {
public static void main(String[] args) {
ISendBook sendBook = new DangDang();
SFSendBook sfSendBook = new SFSendBook(sendBook);
sfSendBook.sendBook();
}
}
运行结果:
2.2 动态代理
概述
java中的动态代理类的对象由Proxy.java类的newProxyInstance方法进行创建。它不是由程序员自己创建的,而是由jvm创建的。
增强类算法需要实现InvocationHandler接口来进行实现。
实现
handler.MyHandler:
package com.wqq.www.proxyDynamic.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
private Object who;
public MyHandler(Object who) {
this.who = who;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
method.invoke(who);
System.out.println("结束事务");
return null;
}
}
test.Test1:
package com.wqq.www.proxyDynamic.test;
import com.wqq.www.proxyDynamic.handler.MyHandler;
import com.wqq.www.proxyDynamic.serviceBook.DangDang;
import com.wqq.www.proxyDynamic.serviceBook.ISendBook;
import sun.management.Sensor;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test1 {
public static void main(String[] args) {
DangDang dangDang = new DangDang();
ClassLoader classLoader = Test1.class.getClassLoader();
Class<?>[] intefaces = dangDang.getClass().getInterfaces();
InvocationHandler handler = new MyHandler(dangDang);
Object o = Proxy.newProxyInstance(classLoader,intefaces, handler);
ISendBook sendBook = (ISendBook) o;
sendBook.sendBook();
}
}
运行结果:
service包的类与上一个案例相同。
总结
- 代理类由jvm创建,不需要程序员手动创建,减少编码量。
- 代理类不再绑死固定接口,达到代理类与接口解耦。
- 再InvocationHandler中通过反射技术可以更灵活的处理增强算法。
注:Spring虽使用反射原理,但不支持Field字段级的增强。此例中的invoke方法返回null会导致该方法实例化的toString返回的是null,hashcode方法会报空指针错误,想要修复这一类错误请不要返回null而是返回有意义的对象。
2.3 实现多个代理
概述
通过实现代理套代理便可实现多个代理。
代码
public static void main(String[] args) {
ClassLoader classLoader = Test1.class.getClassLoader();
//取得当当的接口,当当的接口也是代理类的接口
DangDang dangDang = new DangDang();
Class<?>[] interfaces = dangDang.getClass().getInterfaces();
InvocationHandler handler = new MyHandler(dangDang);
//o是使用handler增强过的代理对象
Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
//构造第二个增强方法
InvocationHandler handler2 = new MyHandler2(o);
//o2在o的基础上二次增强
Object o2 = Proxy.newProxyInstance(classLoader, interfaces, handler2);
//o2仍然可以强转为ISendBook接口,此时调用便可以实现多个代理。
ISendBook sendBook = (ISendBook) o2;
sendBook.SendBook();
}
2.4使用Spring实现动态代理
概述
虽然jdk的动态代理能实现目标对象的功能增强,但是仍然不够方便,且程序员的写法不同也会使得代码风格不一致从而难以后期维护。
好在Spring框架对动态代理进行了封装,产生了自己的AOP框架,称为SpringAOP,该框架的常见接口如下:
- 方法前增强实现org.springframework.aop.MethodBeforeAdvice接口
- 方法后增强实现
org.springframework.aop.AfterReturningAdvice接口 - 方法环绕增强实现
org.springframework.aop.MethodInterceptor接口 - 异常处理增强实现
org.springframework.aop.ThrowsAdvice接口
代码实现
项目结构:
注意applicationContext的创建方法,右键Resource文件夹(没有的右键main->new->directory->Resources进行创建),按照一下的顺序生成(也要记得引用spring相关依赖)。
MyAfterReturningAdvice :
package com.wqq.www.test53.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("afterReturning");
}
}
MyMethodBeforeAdvice :
package com.wqq.www.test53.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("MethodBeforeAdvice");
}
}
MyMethodInterceptor :
package com.wqq.www.test53.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.sql.SQLOutput;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("MethodInterceptor begin");
Object returnValue = methodInvocation.proceed();
System.out.println("MethodInterceptor end");
return returnValue;
}
}
MyThrowsAdvice :
package com.wqq.www.test53.advice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("ThrowsAdvice method.getName()=" + method.getName() + " target=" + target + " ex.getMessage()=" + ex.getMessage() + " save db");
}
}
service.DB:
package com.wqq.www.test53.service;
public class DB implements com.wqq.www.test53.service.ISaveData {
@Override
public void ok() {
System.out.println("ok");
}
@Override
public void no() {
System.out.println("no");
Integer.parseInt("a");
}
}
service.ISaveData :
package com.wqq.www.test53.service;
public interface ISaveData {
public void ok();
public void no();
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myAfterReturningAdvice" class="com.wqq.www.test53.advice.MyAfterReturningAdvice"></bean>
<bean id="myMethodBeforeAdvice" class="com.wqq.www.test53.advice.MyMethodBeforeAdvice"></bean>
<bean id="myMethodInterceptor" class="com.wqq.www.test53.advice.MyMethodInterceptor"></bean>
<bean id="myThrowsAdvice" class="com.wqq.www.test53.advice.MyThrowsAdvice"></bean>
<bean id="db" class="com.wqq.www.test53.service.DB"></bean>
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces">
<value>com.wqq.www.test53.service.ISaveData</value>
</property>
<property name="target" ref="db"></property>
<property name="interceptorNames">
<list>
<value>myAfterReturningAdvice</value>
<value>myMethodBeforeAdvice</value>
<value>myMethodInterceptor</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
</beans>
Test1 :
package com.wqq.www.test53.test;
import com.wqq.www.test53.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test1 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ISaveData saveData = (ISaveData) ac.getBean("proxy");
saveData.ok();
}
}
运行结果:
package com.wqq.www.test53.test;
import com.wqq.www.test53.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ISaveData saveData = (ISaveData) ac.getBean("proxy");
saveData.no();
}
}
运行结果:
3、与AOP相关的必备概念
1. 横切关注点Cross-cutting Concerns
横切关注点就是通用性功能,比如:
- 方法执行时间记录
- 方法权限验证
- 常规日志记录
- 数据库事务处理
2. 切面与面向切面编程
三个业务类都需要3个切面中的功能,所以把这些功能提取出来,当业务类的代码执行时动态的对切面中的功能代码进行调用,便实现了对“横切关注点”通用性代码的复用,达到了解耦的目的。AOP可以让一组类的对象具有相同的行为。
(1)切面
对“横切关注点”的模块化,也就是将“横切关注点”的功能代码提取出来放入一个单独的类中进行统一处理,这个类就是切面类,也可称为切面Aspect。
(2)AOP编程(Aspect Oriented Programming面向切面编程)
主要就是针对切面进行设计代码。
3.连接点 Join Point
连接点是在软件执行过程中能够插入切面的一个点(可以是调用方法前、调用方法后、方法抛出异常时、方法返回了值后等位置)。
4.切点 Pointcut
大多数情况下只针对部分连接点应用切面,这些部分连接点称为切点。(不可能在所有连接点都应用切面,会拖慢程序运行效率。)
5.通知 Advice
通知是应用切面的时机。
在SpringAOP中,通知分为5种:
(1)前置通知(before):方法被调用之前。
(2)后置通知(after):方法被调用之后。
(3)环绕通知(around):方法被调用之前与之后。
(4)返回通知(after-returning):方法返回了值。
(5)异常通知(after-throwing):方法出现了异常。
6.织入 Weaving
织入就是把切面(干什么)、切点(在哪干)、通知(什么时候干)整合起来,应用到目标对象中。
4. 面向切面编程AOP核心案例
(0)切面表达式
execution(modifiers-pattern?
ret-type-pattern
declaring-type-pattern?
name-pattern(param-pattern)?
throws-pattern?)
(1) modifiers-pattem代表修饰符,比如public,private。
(2)ret-type-pattem 代表返回值类型
(3)declaring-type-pattern代表包路径
(4)name-pattern 代表方法
(5)param-pattern参数
(6)throws-pattern代表异常
带?问号的表示可以忽略,也就是除了ret-type-pattern之外,其它都是可选的。
(1)实现前置-后置-返回-异常通知
项目结构:
aspect.Myaspect:
package com.wqq.www.aopTest54.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
@Before("execution(* com.wqq.www.aopTest54.service.*.*(..))")
// @Before("execution(* com.wqq.www.aopTest54.service.*.*(..)")
public void before(){
System.out.println("before");
}
@After("execution(* com.wqq.www.aopTest54.service.*.*(..))")
public void after(){
System.out.println("after");
}
@AfterReturning("execution(* com.wqq.www.aopTest54.service.*.*(..))")
public void afterReturning(){
System.out.println("afterReturning");
}
@AfterThrowing("execution(* com.wqq.www.aopTest54.service.*.*(..))")
public void afterThrowing(){
System.out.println("afterThrowing");
}
}
javaconfig.JavaConfig:
package com.wqq.www.aopTest54.javaconfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}
service.DB:
package com.wqq.www.aopTest54.service;
import org.springframework.stereotype.Component;
@Component
public class DB {
public void ok(){
System.out.println("ok");
}
public void no(){
System.out.println("no");
Integer.parseInt("a");
}
}
test.Test1:
package com.wqq.www.aopTest54.test;
import com.wqq.www.aopTest54.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test1 {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest54");
DB db = ac.getBean(DB.class);
db.ok();
}
}
运行结果:
test.Test2:
package com.wqq.www.aopTest54.test;
import com.wqq.www.aopTest54.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test2 {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest54");
DB db = ac.getBean(DB.class);
db.no();
}
}
运行结果:
有接口时的spring aop编程
项目结构:除了service和test包其余无变化。
service.ISaveData:
package com.wqq.www.aopTest55.service;
public interface ISaveData {
public void save();
}
service.DB:
package com.wqq.www.aopTest55.service;
import org.springframework.stereotype.Component;
@Component
public class DB implements ISaveData{
public void ok(){
System.out.println("ok");
}
public void no(){
System.out.println("no");
Integer.parseInt("a");
}
@Override
public void save() {
System.out.println("save in db");
}
}
test.Test1:
package com.wqq.www.aopTest55.test;
import com.wqq.www.aopTest55.service.DB;
import com.wqq.www.aopTest55.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test1 {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest55");
ISaveData saveData = (ISaveData) ac.getBean("DB");
saveData.save();
}
}
注意:如果业务类实现了接口,在获得业务类时,必须使用context.getBean(“beanld”)的写法,不能使用context.getBean(Bean.class)的写法,不然会出现找不到JavaBean的异常。出现异常的原因是因为根据context.getBean(Bean.class)写法获得的对象的真实数据类型是Bean的代理类BeanProxy,Bean.class和BeanProxy.class 不是同一个类型,所以出现异常。
运行结果(与上个案例一致):
总结
无异常的情况下运行顺序:
(1)前置通知@Before
(2)业务类中的方法
(3)返回通知@AfterReturning
(4)后置通知@After
(5)返回值
有异常的情况下运行顺序:
(1)前置通知@Before
(2)业务类中的方法
(3)异常通知@AfterThrowing
(4)后置通知@After
(2)对前置通知-后置通知-返回通知-异常通知传入JoinPoint参数
概述
参数JoinPoint可以实现对连接点信息的获取
项目与代码
项目结构:
aspect.MyAspect:
package com.wqq.www.test57.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAspect {
//任意返回值类型 对指定包中的包 任意类进行切面处理 任意名称方法 任意类型参数
@Before("execution(* com.wqq.www.test57.service.*.*(..))")
// @Before("execution(* com.wqq.www.aopTest54.service.*.*(..)")
public void before(JoinPoint joinPoint){
System.out.println("before");
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
@After("execution(* com.wqq.www.test57.service.*.*(..))")
public void after(JoinPoint joinPoint){
System.out.println("after");
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning("execution(* com.wqq.www.test57.service.*.*(..))")
public void afterReturning(JoinPoint joinPoint){
System.out.println("afterReturning");
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
@AfterThrowing("execution(* com.wqq.www.test57.service.*.*(..))")
public void afterThrowing(JoinPoint joinPoint){
System.out.println("afterThrowing");
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
}
javaConfig.JavaConfig:(@EnableAspectJAutoProxy使允许切面方法)
package com.wqq.www.test57.javaconfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}
service.DB:
package com.wqq.www.test57.service;
import org.springframework.stereotype.Component;
@Component
public class DB implements ISaveData{
@Override
public void ok() {
System.out.println("ok");
}
@Override
public void no() {
System.out.println("no");
Integer.parseInt("a");
}
}
service.ISaveData:
package com.wqq.www.test57.service;
import org.springframework.stereotype.Component;
@Component
public interface ISaveData {
void ok();
void no();
}
test.Test1:
package com.wqq.www.test57.test;
import com.wqq.www.test55.advice.MyMethodInterceptor;
import com.wqq.www.test56.service.DB;
import com.wqq.www.test57.service.ISaveData;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test57");
ISaveData saveData = (ISaveData) applicationContext.getBean("DB");
saveData.ok();
System.out.println();
System.out.println();
saveData.no();
}
}
运行结果:
(3)实现环绕通知
项目结构及除了MyAspect以外的类代码相同。
MyAspect:
package com.wqq.www.test58.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAspect {
@Around("execution(* com.wqq.www.test58.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
}
运行结果:
(4)使用bean表达式
概述
使用bean表达式可以限制切面应用于的目标对象。
项目结构与代码
关键代码
切面表达式中使用 &&bean(bean的id)来指定切面对哪个bean起作用
@Around("execution(* com.wqq.www.test59.service.*.*(..)) && bean(b)")
结构与代码
项目结构:
aspect.MyAspect:
package com.wqq.www.test59.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
@Around("execution(* com.wqq.www.test59.service.*.*(..)) && bean(b)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
}
javaconfig.JavaConfig:
package com.wqq.www.test59.javaconfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}
service.SaveService1:
package com.wqq.www.test59.service;
import org.springframework.stereotype.Service;
@Service("a")
public class SaveService1 {
public void ok(){
System.out.println("SaveService1 ok");
}
}
service.SaveService2:
package com.wqq.www.test59.service;
import org.springframework.stereotype.Service;
@Service("b")
public class SaveService2 {
public void ok(){
System.out.println("SaveService2 ok");
}
}
test.Test1:
package com.wqq.www.test59.test;
import com.wqq.www.test59.service.SaveService1;
import com.wqq.www.test59.service.SaveService2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test59");
SaveService1 a = (SaveService1) applicationContext.getBean(SaveService1.class);
a.ok();
SaveService2 b = (SaveService2) applicationContext.getBean(SaveService2.class);
b.ok();
}
}
运行结果:
(5)使用@PointCut定义全局切点
概述
前面4个部分多处使用相同的execution表达式,可以将execution进行全局化,以减少冗余的配置。@PointCut注解就是实现这样的功能的。
项目结构与代码
关键代码
@Pointcut进行全局定义,其他方法通过注解下发的函数进行调用即可减少代码冗余。
@Pointcut("execution(* com.wqq.www.test60.service.*.*(..))")
public void publicPointCut(){}
@Before("publicPointCut()")
public void before(){
System.out.println("before");
}
详细代码
项目结构:
aspect.MyAspect :
package com.wqq.www.test60.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.wqq.www.test60.service.*.*(..))")
public void publicPointCut(){}
@Before("publicPointCut()")
public void before(){
System.out.println("before");
}
@After("publicPointCut()")
public void after(JoinPoint joinPoint){
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning("publicPointCut()")
public void afterReturning(JoinPoint joinPoint){
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
@AfterThrowing("publicPointCut()")
public void afterThrowing(JoinPoint joinPoint){
System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
}
}
JavaConfig与之前相同。
service.DB:
package com.wqq.www.test60.service;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class DB implements ISaveData {
@Override
public void ok(String username,String password)
{
System.out.println("username = "+username + ",password = "+password);
System.out.println("ok");
}
@Override
public void no() {
System.out.println("no");
Integer.parseInt("a");
}
}
service.ISaveData:
package com.wqq.www.test60.service;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public interface ISaveData {
void ok(String username,String password);
void no();
}
test.Test1:
package com.wqq.www.test60.test;
import com.wqq.www.test60.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test60");
ISaveData saveData = (ISaveData) applicationContext.getBean("DB");
saveData.ok("中国","中国人");
System.out.println();
saveData.no();
}
}
运行结果:
(6)向切面传入参数
概述
前面的方法可以在各个连接点实现切面的功能,但是它们与我们自己写的代码缺少一种关联。
如果切面类能获得我们方法的参数值,我们便可以建立一种关联,这样就可以进行一些日志的输出了!
项目结构与代码
关键代码
首先在注解中声明参数类型并使用&&args声明参数的别名,然后在下方的切点构造方法中使用这些类型和别名进行构造。
后面在使用时,在对应方法的注解中注明形参,并在下方的方法使用这些形参。注意下面方法的形参和类型必须与之前定义的一致!
而且DB.Test2与公共切点注解的参数类型应该一致!
下面的两幅图片描述了四组对应关系:
涉及代码如下:
@Pointcut("execution(* com.wqq.www.test61.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
public void publicPointCut2(String u, String p, int a, Date i){}
@Before("publicPointCut2(usernamePara,passwordPara,agePara,insertDatePara)")
public void before2(String usernamePara,String passwordPara,int agePara,Date insertDatePara){
System.out.println("before2 username="+usernamePara + "password="+passwordPara+"age="+agePara+"date="+insertDatePara);
}
//这是另一个文件DB.java的代码
public void Test2(String username, String password, int age, Date insertDate){
System.out.println("service username" + username);
System.out.println("service password" + password);
System.out.println("service age" + age);
System.out.println("service insertDate" + insertDate);
}
详细代码
项目结构:
aspect.MyAspect:
package com.wqq.www.test61.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.wqq.www.test61.service.DB.Test1(String))&&args(xxx)")
public void publicPointCut(String xxx){}
@Pointcut("execution(* com.wqq.www.test61.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
public void publicPointCut2(String u, String p, int a, Date i){}
@Before("publicPointCut(usernamePara)")
public void before(String usernamePara){
System.out.println("before1 : username" + usernamePara);
}
@Before("publicPointCut2(usernamePara,passwordPara,agePara,insertDatePara)")
public void before2(String usernamePara,String passwordPara,int agePara,Date insertDatePara){
System.out.println("before2 username="+usernamePara + "password="+passwordPara+"age="+agePara+"date="+insertDatePara);
}
}
JavaConfig与之前相同
service.DB:
package com.wqq.www.test61.service;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class DB {
public void ok(String username,String password)
{
System.out.println("username = "+username + ",password = "+password);
System.out.println("ok");
}
public void no() {
System.out.println("no");
Integer.parseInt("a");
}
public void Test1(String username){
System.out.println("service name="+username);
}
public void Test2(String username, String password, int age, Date insertDate){
System.out.println("service username" + username);
System.out.println("service password" + password);
System.out.println("service age" + age);
System.out.println("service insertDate" + insertDate);
}
}
test.Test1:
package com.wqq.www.test61.test;
import com.wqq.www.test61.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Date;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test61");
DB db = applicationContext.getBean(DB.class);
db.Test1("中国");
System.out.println();
db.Test2("中国","中国人" ,100 ,new Date());
}
}
运行结果:
可以看到参数传入切面方法中,并且切面方法对这些参数进行了输出。
(7)使用@AfterReturning和@AfterThrowing向切面传入参数
概述
@AfterReturning和@AfterThrowing传参方式分别是向切面方法传入返回值和异常信息,与(6)中的传参方式不同。
项目结构与代码
关键代码
通过在注解里面加上returning与throwing属性并在下方方法传入,便可实现切面方法获取返回值和异常信息。
@AfterReturning(value = "publicPointCut1()",returning = "t")
public void afterReturning(Object t){
System.out.println("afterReturning t="+t);
}
@AfterThrowing(value = "publicPointCut1()",throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing t="+t.getMessage());
}
详细代码
aspect.MyAspect:
package com.wqq.www.test62.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.wqq.www.test62.service..*(..))")
public void publicPointCut1(){}
@AfterReturning(value = "publicPointCut1()",returning = "t")
public void afterReturning(Object t){
System.out.println("afterReturning t="+t);
}
@AfterThrowing(value = "publicPointCut1()",throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing t="+t.getMessage());
}
}
JavaConfig与之前相同。
service.DB:
package com.wqq.www.test62.service;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class DB {
//注意这里要有返回值
public Date Test1(String username){
System.out.println("service name="+username);
return new Date();
}
//这是一个错误的方法,测试抛出异常的切面方法传参用的
public void Test2(String username, String password, int age, Date insertDate){
System.out.println("service username" + username);
System.out.println("service password" + password);
System.out.println("service age" + age);
System.out.println("service insertDate" + insertDate);
Integer.parseInt("a");
}
}
test.Test1:
package com.wqq.www.test62.test;
import com.wqq.www.test62.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Date;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test62");
DB db = applicationContext.getBean(DB.class);
db.Test1("中国");
System.out.println();
db.Test2("中国","中国人" ,100 ,new Date());
}
}
运行结果:
可见返回值和异常信息都通过切面方法进行了输出。
(8)向环绕通知传入参数
概述
该部分和(6)中的普通通知传参类似,但要注意环绕通知比它们要多一个参数ProceedingJoinPoint,因为环绕通知需要这个参数来确定调用原方法的位置。
项目结构和代码
关键代码
@Pointcut("execution(* com.wqq.www.test63.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
public void publicPointCut2(String u, String p, int a, Date i){}
@Around("publicPointCut2(username,password,age,date)")
public Object around2(ProceedingJoinPoint joinPoint,String username,String password,int age,Date date) throws Throwable {
System.out.println("before2 username="+username + "password="+password+"age="+age+"date="+date);
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
详细代码
项目结构
aspect.MyAspect:
package com.wqq.www.test63.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.wqq.www.test63.service.DB.Test1(String))&&args(xxx))")
public void publicPointCut1(String xxx){}
@Pointcut("execution(* com.wqq.www.test63.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
public void publicPointCut2(String u, String p, int a, Date i){}
@Around("publicPointCut1(username)")
public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
System.out.println("around1 usernameParam=" + username);
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
@Around("publicPointCut2(username,password,age,date)")
public Object around2(ProceedingJoinPoint joinPoint,String username,String password,int age,Date date) throws Throwable {
System.out.println("before2 username="+username + "password="+password+"age="+age+"date="+date);
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
}
JavaConfig与之前相同。
service.DB:
package com.wqq.www.test63.service;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class DB {
public void Test1(String username){
System.out.println("service name="+username);
}
public void Test2(String username, String password, int age, Date insertDate){
System.out.println("service username" + username);
System.out.println("service password" + password);
System.out.println("service age" + age);
System.out.println("service insertDate" + insertDate);
Integer.parseInt("a");
}
}
test.Test1:
package com.wqq.www.test63.test;
import com.wqq.www.test63.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Date;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test63");
DB db = applicationContext.getBean(DB.class);
db.Test1("中国");
System.out.println();
db.Test2("中国","中国人" ,100 ,new Date());
}
}
运行结果:
(9)实现多切面的应用
概述
多个切面可以存在于多个类中,并一起生效。
项目结构与代码
关键代码
aspect包中有三个类,想让这三个类中的切面都对service.DB.Test1的方法起作用,只需要向每个类都加上该注解即可。
@Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
public void publicPointCut1(String xxx){}
@Around("publicPointCut1(username)")
public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
System.out.println("around1 usernameParam=" + username);
.....
return yourVal;
}
详细代码
项目结构:
aspect.MyAspect1:
package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
public void publicPointCut1(String xxx){}
@Around("publicPointCut1(username)")
public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
System.out.println("around1 usernameParam=" + username);
Object returnValue = null;
System.out.println("begin");
returnValue = joinPoint.proceed();
System.out.println("end");
return returnValue;
}
}
aspect.MyAspect2:
package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect2 {
@Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
public void publicPointCut1(String xxx){}
@Around("publicPointCut1(username)")
public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
System.out.println("around2 usernameParam=" + username);
Object returnValue = null;
System.out.println("begin2");
returnValue = joinPoint.proceed();
System.out.println("end2");
return returnValue;
}
}
aspect.MyAspect3:
package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect3 {
@Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
public void publicPointCut1(String xxx){}
@Around("publicPointCut1(username)")
public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
System.out.println("around3 usernameParam=" + username);
Object returnValue = null;
System.out.println("begin3");
returnValue = joinPoint.proceed();
System.out.println("end3");
return returnValue;
}
}
JavaConfig与之前相同。
service.DB:
package com.wqq.www.test64.service;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class DB {
public void Test1(String username){
System.out.println("service name="+username);
}
}
test.Test1:
package com.wqq.www.test64.test;
import com.wqq.www.test64.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Date;
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test64");
DB db = applicationContext.getBean(DB.class);
db.Test1("中国");
}
}
运行结果:
(10)使用@Order注解指定切面运行顺序
代码
该部分与上例的代码基本相同,不同的是aspect中的类都加上了Order注解。
@Component
@Aspect
@Order(3)
public class MyAspect1 {
@Component
@Aspect
@Order(2)
public class MyAspect2 {
@Component
@Aspect
@Order(1)
public class MyAspect3 {
运行结果:
得到的运行顺序与上例不同,如图所示。