咱们先认识几个咱们都耳熟能详的注解java
AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。面试
/** * 至关于一个beans */ @Configuration @ComponentScan(basePackages = "xiaodao.spring") public class SpringConfiguration { public SpringConfiguration() { System.out.println("spring 容器启动"); } /** * bean id 默认是bean的方法名 * @return */ /* @Bean public UserService userService(){ return new UserServiceImpl(); }*/ }
public class SpringConfigurationTest { @Test public void test1(){ //建立纯注解方式的spring 容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save(); } }
@import 注解的使用方法 spring
咱们在spring中有各类各样的配置文件.写在一个类中,确定是 不合适的.咱们须要分门分类编程
@Configuration @ComponentScan(basePackages = "com.kkb.spring") @Import({ JdbcConfig.class}) public class SpringConfiguration { } @Configuration @PropertySource("classpath:jdbc.properties") public class JdbcConfig{ }
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程api
AOP最先是AOP联盟的组织提出的,指定的一套规范,spring将AOP的思想引入框架之中,经过预编译方式和运行期间动态代理实现程序的统一维护的一种技术,缓存
预编译方式在spring中没有使用,由于要结合其余编译方式,和spring形成强耦合,预编译方式就是在程序没有运行前编译.安全
AOP是OOP的延续,,AOP解决的是从横向解决代码重复的问题 ,OOP是从纵向解决代码的重复问题app
AOP采起横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)讲业务逻辑和系统处理的代码进行解耦 如如:关闭链接 事物管理,操做日志的记录框架
1. Joinpoint(链接点) -- 所谓链接点是指那些被拦截到的点。在spring中,这些点指的是方法,由于spring只支持方法类型的链接点ide
2. Pointcut(切入点) -- 所谓切入点是指咱们要对哪些Joinpoint进行拦截的定义
3. Advice(通知/加强) -- 所谓通知是指拦截到Joinpoint以后所要作的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
4. Introduction(引介) -- 引介是一种特殊的通知在不修改类代码的前提下, Introduction能够在运行期为类动态地添加一些方法或Field
5. Target(目标对象) -- 代理的目标对象
6. Weaving(织入) -- 是指把加强应用到目标对象来建立新的代理对象的过程
7.Proxy(代理) -- 一个类被AOP织入加强后,就产生一个结果代理类
8. Aspect(切面) -- 是切入点和通知的结合,之后我们本身来编写和配置的
***AspectJ是一个java实现的AOP框架,它可以对java代码进行AOP编译(通常在编译期进行),让java代码具备AspectJ的AOP功能(固然须要特殊的编译器)
***能够这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序彻底兼容,几乎是无缝关联,所以对于有java编程基础的工程师,上手和使用都很是容易。
***了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,能够简单理解为aspect(切面)应用到目标函数(类)的过程。
***对于这个过程,通常分为动态织入和静态织入,动态织入的方式是在运行时动态将要加强的代码织入到目标类中,这样每每是经过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层经过反射实现)或者CGLIB的动态代理(底层经过继承实现),Spring AOP采用的就是基于运行时加强的代理技术
***ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(相似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
spring AOP 是经过动态代理技术实现的
而动态代理的技术是经过反射来实现的
动态代理技术的实现方式有两种:基于接口的JDK动态代理和基于继承的CGLib动态代理。
咱们要代理的对象
public interface UserService { public void save(); } @Service("userService") public class UserServiceImpl implements UserService { public void save() { System.out.println("userserviceimpl save方法"); } }
代理的实现
public class MyProxyUtils { /** * 使用JDK动态代理类 * @param userServiceInterface * @return */ public static UserService getProxy(final UserService userServiceInterface){ /** * proxy是jdk中的代理类 * 1.目标对象的类加载器 * 2.目标对接的接口 * 3.代理对象的执行处理器 */ UserService userService = (UserService) Proxy.newProxyInstance(userServiceInterface.getClass().getClassLoader(), userServiceInterface.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("记录日志-开始"); //下面的代码是,反射中的api用法 //方法 //2.参数 //这行代码实际仍是调用目标对象的方法 Object invoke = method.invoke(userServiceInterface, args); System.out.println("记录日志结束"); return invoke; } }); return userService; } /** * 使用cglib的方式动态代理技术实现 * 它是基于继承的方式实现的 * @param userService * @return */ public static UserService getProxyByCglib(UserService userService){ //建立加强器 Enhancer enhancer =new Enhancer(); //这里设置加强类的类对象-实现类的类对象 enhancer.setSuperclass(UserServiceImpl.class); //设置回调函数 enhancer.setCallback(new MethodInterceptor() { /** * * @param o * @param method * @param args 方法参数 * @param methodProxy 代理以后的对象的方法 * @return * @throws Throwable */ public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis(); System.out.println("cglib 记录开始时间: "+start); //代理对象是目标的对象的子类 //这行代码实际仍是调用目标对象的方法 //o 是代理对象 Object object = methodProxy.invokeSuper(o, args); long end = System.currentTimeMillis(); System.out.println("cglib 记录结束时间: "+end); return object; } }); // 获取加强以后的代理对象. return (UserService) enhancer.create(); } }
咱们的测试类
public class MyProxyUtilsTest { @Test public void testjdkProxy(){ UserService userService = new UserServiceImpl(); UserService proxy = MyProxyUtils.getProxy(userService); userService.save(); System.out.println("==========="); proxy.save(); } @Test public void testCglibProcy(){ UserService userService = new UserServiceImpl(); UserService proxy = MyProxyUtils.getProxyByCglib(userService); userService.save(); System.out.println("==========="); proxy.save(); } } userserviceimpl save方法 =========== cglib 记录开始时间: 1556369092690 userserviceimpl save方法 cglib 记录结束时间: 1556369092700
切入点表达式
execution:必需要([修饰符] 返回值类型 包名.类名.方法名(参数))
aop总共有5中通知
前置通知
后置通知
异常通知
后置通知
环绕通知
xml 配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop.xsd"> 9 10 11 12 <bean class="xiaodao.spring.service.UserServiceImpl"></bean> 13 <!--加强类--> 14 <bean id="myAdvice" class="xiaodao.spring.MyAdvice"></bean> 15 16 <!--aop 配置--> 17 <aop:config> 18 <!--配置aop切面,切面是由通知和切入点组成的--> 19 <aop:aspect ref="myAdvice"> 20 <!--指定切入点.须要经过表达式来指定 method加强类的方法--> 21 <!--<aop:before method="log" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>--> 22 <!--<aop:after method="log2" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>--> 23 <!--<aop:after-returning method="log3" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>--> 24 25 <!--<!–异常通知 –>--> 26 <!--<aop:after-throwing method="log4" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>--> 27 28 <aop:around method="log5" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/> 29 30 </aop:aspect> 31 </aop:config> 32 </beans>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class TestAOP { @Autowired private UserService userService; @Test public void test(){ userService.save(); } }
加强类:
public class MyAdvice { public void log(){ System.out.println("记录日志....."); } public void log2(){ System.out.println("记录日志 后置通知....."); } public void log3(){ System.out.println("记录日志..最终通知.."); } public void log4(){ System.out.println("记录日志..异常..."); } public void log5(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知前"); //调用目标对象 try { joinPoint.proceed(); System.out.println("后置通知"); } catch (Throwable throwable) { System.out.println("环境通知..异常"); throwable.printStackTrace(); }finally { System.out.println("最终通知"); } } }
userservice 仍是刚刚那个userservice
@Service("userService") public class UserServiceImpl implements UserService { public void save() { System.out.println("userserviceimpl save方法"); System.out.println(1/0); } }
@Aspect @Component("myaspect") //至关于<aop:aspectj-proxy> @EnableAspectJAutoProxy public class MyAspect { //定义该方法是一个前置通知 @Before(value = "execution(* xiaodao.spring.service.*.*())") public void before(){ System.out.println("注解前置通知"); } @After(value = "execution(* xiaodao.spring.service.*.*())") public void after(){ System.out.println("注解后置通知"); } @AfterReturning(value = "func()") public void end(){ System.out.println("最终通知"); } @Pointcut(value = "execution(* xiaodao.spring.service.*.*())") public void func(){ } }
原本准备全注解实现,目前还不能够junit仍是须要加载application.xml 配置文件,暂时不知道junit如何使用我自定义的注解配置类
JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。对于这一块内容,面试官问的比较多,他们每每更想听听面试者是怎么回答的,有没有看过这一块的源码等等。
针对于这一块内容,咱们看一下 Spring 5 中对应的源码是怎么说的。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 判断目标类是不是接口或者目标类是否Proxy类型,如果则使用JDK动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 配置了使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式建立代理对象 return new ObjenesisCglibAopProxy(config); } else { // 上面的三个方法没有一个为true,那使用JDK的提供的代理方式生成代理对象 return new JdkDynamicAopProxy(config); } } //其余方法略…… }
从上述源码片断能够看出,是否使用 CGLIB 是在代码中进行判断的,判断条件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 与 config.isProxyTargetClass() 默认返回都是 false,这种状况下判断结果就由 hasNoUserSuppliedProxyInterfaces(config) 的结果决定了。
public class ProxyConfig implements Serializable { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = -8409359707199703185L; private boolean proxyTargetClass = false; private boolean optimize = false; boolean opaque = false; boolean exposeProxy = false; private boolean frozen = false; ......... }
简单来讲,hasNoUserSuppliedProxyInterfaces(config) 就是在判断代理的对象是否有实现接口,有实现接口的话直接走 JDK 分支,即便用 JDK 的动态代理。
因此基本上能够总结出 Spring AOP 中的代理使用逻辑了:如果目标对象实现了接口,默认状况下会采用 JDK 的动态代理实现 AOP;若是目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。