Spring 的 getBean 方法源码解析spring
AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,能够用来管理一些和主业务无关的周边业务,如日志记录,事务管理等;陌生是由于在工做中基本没有使用过,AOP 的相关概念也是云里雾里;最近在看 Spring 的相关源码,因此仍是先来捋一捋 Spring 中 AOP 的一个用法。编程
在学习 Spring AOP 的用法以前,先来看看 AOP 的相关概念,ide
Spring AOP 的详细介绍,请参考官网 Aspect Oriented Programming with Spring学习
1. Join point :链接点,表示程序执行期间的一个点,在 Spring AOP 表示的就是一个方法,即一个方法能够看做是一个 Join point测试
2. pointcut :切点,就是与链接点匹配的谓词,什么意思呢,就是须要执行 Advice 的链接点就是切点this
3. Advice :加强,在链接点执行的操做,分为前置、后置、异常、最终、环绕加强五种spa
4. Aspect :切面,由 pointcut 和 Advice 组成,能够简单的认为 @Aspect 注解的类就是一个切面.net
5. Target object :目标对象,即 织入 advice 的目标对象
6. AOP proxy :代理类,在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象
7. Weaving :织入,将 Aspect 应用到目标对象中去
注:上述几个概念中,比较容易混淆的是 Join point 和 pointcut,能够这么来理解,在 Spring AOP 中,全部的可执行方法都是 Join point,全部的 Join point 均可以植入 Advice;而 pointcut 能够看做是一种描述信息,它修饰的是 Join point,用来确认在哪些 Join point 上执行 Advice,
在了解了 AOP 的概念以后,接下来就来看看如何使用 Spring Aop
1. 要想使用 Spring AOP ,首先先得在 Spring 配置文件中配置以下标签:
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
该标签有两个属性, expose-proxy 和 proxy-target-class ,默认值都为 false;
expose-proxy :是否须要将当前的代理对象使用 ThreadLocal 进行保存,这是什么意思呢,例如 Aop 须要对某个接口下的全部方法进行拦截,可是有些方法在内部进行自我调用,以下所示:
public void test_1() { this.test_2(); } public void test_2() { }
调用 test_1,此时 test_2 将不会被拦截进行加强,由于调用的是 AOP 代理对象而不是当前对象,而 在 test_1 方法内部使用的是 this 进行调用,因此 test_2 将不会被拦截加强,因此该属性 expose-proxy 就是用来解决这个问题的,即 AOP 代理的获取。
proxy-target-class :是否使用 CGLIB 进行代理,由于 Spring AOP 的底层技术就是使用的是动态代理,分为 JDK 代理 和 CGLIB 代理,该属性的默认值为 false,表示 AOP 底层默认使用的使用 JDK 代理,当须要代理的类没有实现任何接口的时候才会使用 CGLIB 进行代理,若是想都是用 CGLIB 进行代理,能够把该属性设置为 true 便可。
2. 定义须要 aop 拦截的方法,模拟一个 User 的增删改操做:
接口:
public interface IUserService { void add(User user); User query(String name); List<User> qyertAll(); void delete(String name); void update(User user); }
@Service("userServiceImpl") public class UserServiceImpl implements IUserService { @Override public void add(User user) { System.out.println("添加用户成功,user=" + user); } @Override public User query(String name) { System.out.println("根据name查询用户成功"); User user = new User(name, 20, 1, 1000, "java"); return user; } @Override public List<User> qyertAll() { List<User> users = new ArrayList<>(2); users.add(new User("zhangsan", 20, 1, 1000, "java")); users.add(new User("lisi", 25, 0, 2000, "Python")); System.out.println("查询全部用户成功, users = " + users); return users; } @Override public void delete(String name) { System.out.println("根据name删除用户成功, name = " + name); } @Override public void update(User user) { System.out.println("更新用户成功, user = " + user); } }
3. 定义 AOP 切面
在 Spring AOP 中,使用 @Aspect 注解标识的类就是一个切面,而后在切面中定义切点(pointcut)和 加强(advice):
3.1 前置加强,@Before(),在目标方法执行以前执行
@Component @Aspect public class UserAspectj { // 在方法执行以前执行 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void before_1(){ System.out.println("log: 在 add 方法以前执行...."); } }
上述的方法 before_1() 是对接口的 add() 方法进行 前置加强,即在 add() 方法执行以前执行,
测试:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/resources/myspring.xml") public class TestBean { @Autowired private IUserService userServiceImpl; @Test public void testAdd() { User user = new User("zhangsan", 20, 1, 1000, "java"); userServiceImpl.add(user); } } // 结果: // log: 在 add 方法以前执行.... // 添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
若是想要获取目标方法执行的参数等信息呢,咱们可在 切点的方法中添参数 JoinPoint ,经过它了获取目标对象的相关信息:
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void before_2(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); User user = null; if(args[0].getClass() == User.class){ user = (User) args[0]; } System.out.println("log: 在 add 方法以前执行, 方法参数 = " + user); }
从新执行上述测试代码,结果以下:
// log: 在 add 方法以前执行, 方法参数 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} // 添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
3.2 后置加强,@After(),在目标方法执行以后执行,不管是正常退出仍是抛异常,都会执行
// 在方法执行以后执行 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void after_1(){ System.out.println("log: 在 add 方法以后执行...."); }
执行 3.1 的测试代码,结果以下:
// 添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} // log: ==== 方法执行以后 =====
3.3 返回加强,@AfterReturning(),在目标方法正常返回后执行,出现异常则不会执行,能够获取到返回值:
@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") public void after_return(Object object){ System.out.println("在 query 方法返回后执行, 返回值= " + object); }
测试:
@Test public void testQuery() { userServiceImpl.query("zhangsan"); } // 结果: // 根据name查询用户成功 // 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
当一个方法同时被 @After() 和 @AfterReturning() 加强的时候,先执行哪个呢?
@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") public void after_return(Object object){ System.out.println("===log: 在 query 方法返回后执行, 返回值= " + object); } @After("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") public void after_2(){ System.out.println("===log: 在 query 方法以后执行...."); }
测试:
// 根据name查询用户成功 // ===log: 在 query 方法以后执行.... // ===log: 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
能够看到,即便 @After() 放在 @AfterReturning() 的后面,它也先被执行,即 @After() 在 @AfterReturning() 以前执行。
3.4 异常加强,@AfterThrowing,在抛出异常的时候执行,不抛异常不执行。
@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex") public void after_throw(Exception ex){ System.out.println("在 query 方法抛异常时执行, 异常= " + ex); }
如今来修改一下它加强的 query() 方法,让它抛出异常:
@Override public User query(String name) { System.out.println("根据name查询用户成功"); User user = new User(name, 20, 1, 1000, "java"); int a = 1/0; return user; }
测试:
@Test public void testQuery() { userServiceImpl.query("zhangsan"); } // 结果: // 在 query 方法抛异常时执行, 异常= java.lang.ArithmeticException: / by zero // java.lang.ArithmeticException: / by zero ...........
3.5 环绕加强,@Around,在目标方法执行以前和以后执行
// 目标方法: @Override public void update(User user) { System.out.println("更新用户成功, user = " + user); } @Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))") public void test_around(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); System.out.println("log : delete 方法执行以前, 参数 = " + args[0].toString()); joinPoint.proceed(); System.out.println("log : delete 方法执行以后"); }
测试:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } // 结果: // log : delete 方法执行以前, 参数 = zhangsan // 根据name删除用户成功, name = zhangsan // log : delete 方法执行以后
以上就是 Spring AOP 的几种加强。
上面的栗子中,在每一个方法上方的切点表达式都须要写一遍,如今能够使用 @Pointcut 来声明一个可重用的切点表达式,以后在每一个方法的上方引用这个切点表达式便可。:
// 声明 pointcut @Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") public void pointcut(){ } @Before("pointcut()") public void before_3(){ System.out.println("log: 在 query 方法以前执行"); } @After("pointcut()") public void after_4(){ System.out.println("log: 在 query 方法以后执行...."); }
在上面的栗子中,使用了 execution 指示符,它用来匹配方法执行的链接点,也是 Spring AOP 使用的主要指示符,在切点表达式中使用了 通配符 (*) 和 (.. ),其中,(* )能够表示任意方法,任意返回值,(..)表示方法的任意参数 ,接下来来看下其余的指示符。
匹配特定包下的全部类的全部 Joinpoint(方法),包括子包,注意是全部类,而不是接口,若是写的是接口,则不会生效,如 within(main.tsmyk.mybeans.impl.* 将会匹配 main.tsmyk.mybeans.impl 包下全部类的全部 Join point;within(main.tsmyk.mybeans.impl..* 两个点将会匹配该包及其子包下的全部类的全部 Join point。
栗子:
@Pointcut("within(main.tsmyk.mybeans.impl.*)") public void testWithin(){ } @Before("testWithin()") public void test_within(){ System.out.println("test within 在方法执行以前执行....."); }
执行该包下的类 UserServiceImpl 的 delete 方法,结果以下:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } // 结果: // test within 在方法执行以前执行..... // 根据name删除用户成功, name = zhangsan
匹配全部持有指定注解类型的方法,如 @within(Secure),任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起做用。
匹配的是一个目标对象,target(main.tsmyk.mybeans.inf.IUserService) 匹配的是该接口下的全部 Join point :
@Pointcut("target(main.tsmyk.mybeans.inf.IUserService)") public void anyMethod(){ } @Before("anyMethod()") public void beforeAnyMethod(){ System.out.println("log: ==== 方法执行以前 ====="); } @After("anyMethod()") public void afterAnyMethod(){ System.out.println("log: ==== 方法执行以后 ====="); }
以后,执行该接口下的任意方法,都会被加强。
匹配一个目标对象,这个对象必须有特定的注解,如
@target(org.springframework.transaction.annotation.Transactional) 匹配任何 有 @Transactional 注解的 方法
匹配当前AOP代理对象类型的执行方法,this(service.IPointcutService),当前AOP对象实现了 IPointcutService接口的任何方法
匹配参数,
// 匹配只有一个参数 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)") public void test_arg(){ } // 匹配第一个参数为 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)") public void test_arg2(){ } // 匹配第二个参数为 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)") public void test_arg3(){ }
匹配参数,参数有特定的注解,@args(Anno)),方法参数标有Anno注解。
匹配特定注解
@annotation(org.springframework.transaction.annotation.Transactional) 匹配 任何带有 @Transactional 注解的方法。
匹配特定的 bean 名称的方法
// 匹配 bean 的名称为 userServiceImpl 的全部方法 @Before("bean(userServiceImpl)") public void test_bean(){ System.out.println("==================="); } // 匹配 bean 名称以 ServiceImpl 结尾的全部方法 @Before("bean(*ServiceImpl)") public void test_bean2(){ System.out.println("+++++++++++++++++++"); }
测试:
执行该bean下的方法:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } //结果: // =================== // +++++++++++++++++++ // 根据name删除用户成功, name = zhangsan
以上就是 Spring AOP 全部的指示符的使用方法了。
Spring AOP 的底层使用的使用 动态代理;共有两种方式来实现动态代理,一个是 JDK 的动态代理,一种是 CGLIB 的动态代理,下面使用这两种方式来实现以上面的功能,即在调用 UserServiceImpl 类方法的时候,在方法执行以前和以后加上日志。
实现 JDK 动态代理,必需要实现 InvocationHandler 接口,并重写 invoke 方法:
public class UserServiceInvocationHandler implements InvocationHandler { // 代理的目标对象 private Object target; public UserServiceInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("log: 目标方法执行以前, 参数 = " + args); // 执行目标方法 Object retVal = method.invoke(target, args); System.out.println("log: 目标方法执行以后....."); return retVal; } }
测试:
public static void main(String[] args) throws IOException { // 须要代理的对象 IUserService userService = new UserServiceImpl(); InvocationHandler handler = new UserServiceInvocationHandler(userService); ClassLoader classLoader = userService.getClass().getClassLoader(); Class[] interfaces = userService.getClass().getInterfaces(); // 代理对象 IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler); System.out.println("动态代理的类型 = " + proxyUserService.getClass().getName()); proxyUserService.query("zhangsan"); // 把字节码写到文件 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserServiceImpl.class}); FileOutputStream fos =new FileOutputStream(new File("D:/$Proxy.class")); fos.write(bytes); fos.flush(); }
结果:
动态代理的类型 = com.sun.proxy.$Proxy0 log: 目标方法执行以前, 参数 = [Ljava.lang.Object;@2ff4acd0 根据name查询用户成功 log: 目标方法执行以后.....
能够看到在执行目标方法的先后已经打印了日志;刚在上面的 main 方法中,咱们把代理对象的字节码写到了文件里,如今来分析下:
反编译 &Proxy.class 文件以下:
能够看到它经过实现接口来实现的。
JDK 只能代理那些实现了接口的类,若是一个类没有实现接口,则没法为这些类建立代理。此时能够使用 CGLIB 来进行代理。
接下来看下 CGLIB 是如何实现的。
首先新建一个须要代理的类,它没有实现任何接口:
public class UserServiceImplCglib{ public User query(String name) { System.out.println("根据name查询用户成功, name = " + name); User user = new User(name, 20, 1, 1000, "java"); return user; } }
如今须要使用 CGLIB 来实如今方法 query 执行的先后加上日志:
使用 CGLIB 来实现动态代理,也须要实现接口 MethodInterceptor,重写 intercept 方法:
public class CglibMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("log: 目标方法执行以前, 参数 = " + args); Object retVal = methodProxy.invokeSuper(obj, args); System.out.println("log: 目标方法执行以后, 返回值 = " + retVal); return retVal; } }
测试:
public static void main(String[] args) { // 把代理类写入到文件 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImplCglib.class); enhancer.setCallback(new CglibMethodInterceptor()); // 建立代理对象 UserServiceImplCglib userService = (UserServiceImplCglib) enhancer.create(); System.out.println("动态代理的类型 = " + userService.getClass().getName()); userService.query("zhangsan"); }
结果:
动态代理的类型 = main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85 log: 目标方法执行以前, 参数 = [Ljava.lang.Object;@77556fd 根据name查询用户成功, name = zhangsan log: 目标方法执行以后, 返回值 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
能够看到,结果和使用 JDK 动态代理的同样,此外,能够看到代理类的类型为 main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85,它是 UserServiceImplCglib 的一个子类,即 CGLIB 是经过 继承的方式来实现的。
1. JDK 的动态代理是经过反射和拦截器的机制来实现的,它会为代理的接口生成一个代理类。
2. CGLIB 的动态代理则是经过继承的方式来实现的,把代理类的class文件加载进来,经过修改其字节码生成子类的方式来处理。
3. JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
4. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,可是由于采用的是继承, 因此 final 类或方法没法被代理。
5. Spring AOP 中,若是实现了接口,默认使用的是 JDK 代理,也能够强制使用 CGLIB 代理,若是要代理的类没有实现任何接口,则会使用 CGLIB 进行代理,Spring 会进行自动的切换。
上述实现 Spring AOP 的栗子采用的是 注解的方法来实现的,此外,还能够经过配置文件的方式来实现 AOP 的功能。以上就是 Spring AOP 的一个详细的使用过程。