这篇文章为spring回顾总结的第二篇,本篇主要分为两个部分,分别是spring的bean工厂的实现.spring的aop实现原理,这两部分也是面试当中问的比较多的.

spring的bean工厂的实现

spring的bean工厂的实现可以有以下三种方式

  1. 静态工厂实现
public class StaticCarFactory {
public static Map<String,Car> carMap = new HashMap<>();
static {
carMap.put("audi",new Car("audi","330000"));
carMap.put("ford",new Car("ford","40000"));
} public static Car getCar( String name){
return carMap.get(name);
}
}

配置文件:

<?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,不是配置静态工厂方法实例,而是bean的实例
factory-method:指向静态工厂方法的名称
constructor-arg:如果工厂方法需要传入参数,使用constructor-arg配置传入的参数
-->
<bean id="car1" class="com.springtest.beanFactory.StaticCarFactory"
factory-method="getCar">
<constructor-arg value="audi"/>
</bean> <!--配置工厂实例-->
<bean id="carFactory" class="com.springtest.beanFactory.InstanceCarFactory">
</bean>
<!-- 通过实例工厂方法来配置bean-->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="ford"/>
</bean> </beans>
  1. 实例工厂实现
public class InstanceCarFactory {
private Map<String, Car> cars = null; public InstanceCarFactory() {
cars = new HashMap<String, Car>();
cars.put("audi", new Car("audi", "300000"));
cars.put("ford", new Car("ford", "600000")); } public Car getCar(String name) {
return cars.get(name);
} }

配置文件:

<?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,不是配置静态工厂方法实例,而是bean的实例
factory-method:指向静态工厂方法的名称
constructor-arg:如果工厂方法需要传入参数,使用constructor-arg配置传入的参数
-->
<bean id="car1" class="com.springtest.beanFactory.StaticCarFactory"
factory-method="getCar">
<constructor-arg value="audi"/>
</bean> <!--配置工厂实例-->
<bean id="carFactory" class="com.springtest.beanFactory.InstanceCarFactory">
</bean>
<!-- 通过实例工厂方法来配置bean-->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="ford"/>
</bean> </beans>
  1. 实现spring提供的FactoryBean接口
/**
* 自定义的factorybean需要实现spring提供的factorybean接口
*/
public class CarFactoryBean implements FactoryBean<Car> {
private String brand; public void setBrand(String brand) {
this.brand = brand;
} @Override
public Car getObject() throws Exception {
return new Car("BMW","430000");
} @Override
public Class<?> getObjectType() {
return Car.class;
} @Override
public boolean isSingleton() {
return true;
}
}

配置文件

<?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"> <!--通过factorybean来配置bean的实例
class:指向factorybean的全类名
property:配置factorybean的属性
但是实际返回的是实例确实是factorybean的getobject()方法返回的实例
-->
<bean id="car1" class="com.springtest.factoryBean.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
</beans>

spring的aop的实现原理

学习aop,首先要了解两点

  • 什么是aop?

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(摘自百度百科)

  • 为什么要使用aop?

    使用aop主要为了解决两个问题:

    1. 代码混乱.即非核心代码和核心代码混在一起,难以维护或者提高维护成本,
    2. 代码分散.一旦非核心代码需要更改或者替换部分逻辑,那么全部方法中的相关的逻辑都要改动,相当麻烦;
    3. 几个关键词
      • Aspect(切面) 横切关注点
      • Advice(通知) 切面必须要完成的工作
      • Target(目标) 被通知的对象
      • Proxy(代理) 向目标对象应用通知之后创建的对象
      • Joinpoint(连接点) 程序执行的 某个特定的位置,比如方法调用前,调用后,方法抛出异常后等.是实际存在的物理位置.
      • pointcut(切点) 每个类都有多个连接点.aop通过切点定位到连接点.切点和连接点不是一对一的关系,一个切点可匹配多个连接点,切点通过org.springframework.aop.pointcut接口进行描述,使用类和方法作为连接点的查询条件.

        类比记忆:连接点相当于数据库中的记录,切点相当于查询条件.

        代码示例如下:

        首先定义接口:
public interface Calculation {

    int add(int i, int j);

    int sub(int i, int j);
}

定义实现类

public class CalculationImpl implements Calculation {
@Override
public int add(int i, int j) {
//业务功能前加log记录
System.out.println("这个" + i + ";那个" + j);
int result = i + j;
System.out.println("结果是"+result);
return result;
} @Override
public int sub(int i, int j) {
System.out.println("这个" + i + ";那个" + j);
int result = i - j;
System.out.println("结果是"+result);
return result;
}
}

可以看到,模拟加了log日志的话,就要在方法核心代码前后添加无关代码,并且所有用到log的地方都要添加,难以维护;

下面上改进的代码:

public class CalculationLoggingImpl implements Calculation {
@Override
public int add(int i, int j) {
//业务功能前加log记录
int result = i + j;
return result;
} @Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
}

这里是一个代理类,

public class CalculationLoggingProxy {
/**
* 要代理的对象
*/
private Calculation target; public CalculationLoggingProxy(Calculation target){
this.target = target;
}
public Calculation getLoggingProxy() {
Calculation proxy = null;
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{Calculation.class};
//当调用代理对象其中方法时,该执行的代码
/**
* proxy:正在返回的那个代理对象,
* method:正在被调用的方法
* args:调用方法时传入的参数;
*
*/
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
System.out.println("method:"+ name+"params:"+ Arrays.asList(args));
Object invoke = method.invoke(target, args);
System.out.println(invoke);
return invoke;
}
};
proxy = (Calculation) Proxy.newProxyInstance(loader, interfaces, handler);
return proxy;
}
}

Test类:


public class Test {
public static void main(String[] args) { //1,不使用代理
// Calculation calculation = new CalculationImpl();
// calculation.add(1,2);
// calculation.sub(3,1);
//2.使用代理
Calculation target = new CalculationLoggingImpl();
Calculation proxy = new CalculationLoggingProxy(target).getLoggingProxy();
proxy.add(1, 2); }
}

一个简单的aop例子就可以跑起来了,可以看到,使用了动态代理之后,可以将无关核心业务的log代码抽取到代理类中去添加维护,并且无关被代理类究竟是哪一个,省去了很多重复和不必要的代码,提高了代码的灵活性和可维护性.

  • aop的实现框架

    aspectJ是java社区最完整,最流行的aop框架,在Spring2.0以上版本中,可以使用基于aspectJ注解或者基于xml配置的aop.

    那么我们还是分两步来看aspectJ实现aop的过程,先看基于注解实现的,再看基于xml配置实现的.

    1. 基于注解实现

      首先把类交给ioc容器管理

@Component
public class CalculationLoggingImpl implements Calculation {
@Override
public int add(int i, int j) {
//业务功能前加log记录
int result = i + j;
return result;
} @Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
}

声明一个切面,在此需要注意,@Aspect在aspectjweaver 这个jar包里面,使用idea生成spring项目时没有出现,需要自己去下载加入.

/**
* 该类声明为一个切面,需要把该类放在aop容器当中,再声明为一个切面
*/
@Aspect
@Component
public class LoggingAspect { /***
* 声明该方法为一个前置通知
*/
@Before("execution(public int com.springtest.aop.CalculationLoggingImpl.add(int,int))")
public void beforeMethod(JoinPoint point){
//获取当前方法名
String methodName = point.getSignature().getName();
//获取当前方法的参数列表
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("this is before logging...");
System.out.println("this is method "+methodName+" params is " +args);
System.out.println("this is before logging...");
} }

简单配置文件 如下:

<?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:context="http://www.springframework.org/schema/context" 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/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"> <!--配置自动扫描的包-->
<context:component-scan base-package="com.springtest.aop"></context:component-scan>
<!--使AspectJ注解起作用,自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

Test代码

         //3.使用日志切面
//3.1 创建springIOC容器
ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext-aop.xml");
//3.2 从IOC容器中获取bean
Calculation bean = cxt.getBean(Calculation.class);
//3.3 使用bean
System.out.println(bean.add(1,2));

实现结果:

this is before logging...
this is method add params is [1, 2]
this is before logging...
3

可以看到,已经使用loggingAspect切面将log日志信息加在了add方法执行之前.

将@before替换为@after,即变更为后置通知,无论是否出现异常,后置通知都会执行.

将@before替换为@AfterReturning,变更为方法结束后通知.在方法正常结束后执行.

@AfterThrowing抛出异常后通知,在方法抛出异常后执行,可以访问到方法抛出的异常.

@around 环绕通知,需要携带ProceddingJoinpoint类型的参数,类似于动态代理实现的过程.ProceddingJoinpoint类型的参数可以决定是否执行目标方法,环绕通知必须要有返回值,没有返回值会报错,如下代码:

    @Around("execution(public int com.springtest.aop.CalculationLoggingImpl.add(int,int))")
public void aroundMethod(ProceedingJoinPoint point) {
try {
//获取当前方法名
String methodName = point.getSignature().getName();
//获取当前方法的参数列表
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("this is around loggingbegin...");
System.out.println("this is method " + methodName + " params is " + args); Object result = point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("this is around loggingend..."); }

没有返回值,控制台打印如下:

this is around loggingbegin...
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int com.springtest.aop.Calculation.add(int,int)
this is method add params is [1, 2]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:227)
this is around loggingend...
at com.sun.proxy.$Proxy8.add(Unknown Source)
at com.springtest.aop.Test.main(Test.java:24) Process finished with exit code 1

表明没有返回值,加上返回值之后,不再报错.

另外需要注意的几点:

1.当有多个切面同时使用时,可以使用@order标签来指定优先级;

2. 切面表达式是可以重用的,定义一个方法,用于声明切面表达式,一般该方法内部不需要再写任何实现代码,使用@Pointcut来声明切入点表达式.格式如下:

 /**
* 定义一个方法,用于声明切入点表达式,不需要在方法内部再加入其它代码
*/
@Pointcut("execution(public int com.springtest.aop.Calculation.*(int, int))")
public void declarePointCutExpressionEP(){} @Around(value ="declarePointCutExpressionEP()")
public Object aroundMethod(ProceedingJoinPoint point) {...}

注意:这里可能会报错:error at ::0 can't find referenced pointcut declarePointCutExpressionEP,经查证,是因为Aspectweaver这个jar包版本的问题,我使用jdk1.8,最初 使用aspectjweaver-1.6.2.jar,报这个错误,更换为aspectjweaver-1.9.2.jar之后,错误消失.

总结一下,这篇文章详细介绍了spring框架的bean工厂的实现以及aop的简单实现和AspectJ框架的运用.想要熟练运用spring,在面试中不是简单的背记叙述aop的原理,这些基本的东西是要过一遍的,并且掌握它的原理.

俗话说"好记性不如烂笔头",在IT行业里,不能只是一味去看视频,看书,而是要在听说看的同时,多写代码.有的时候,比较难理解的原理,其实写一个简单的helloworld的demo就可以帮助自己快速掌握和回顾,与诸君共勉.

一篇教你看懂spring bean工厂和aop的更多相关文章

  1. 教你看懂Code128条形码

    首     页 条码控件 条码技术 条码新闻 合作伙伴 联系我们 常见问题 电话:010-84827961 当前位置:条形码控件网 > 条形码控件技术文章 > >正文   教你看懂C ...

  2. 一张图搞懂Spring bean的完整生命周期

    一张图搞懂Spring bean的生命周期,从Spring容器启动到容器销毁bean的全过程,包括下面一系列的流程,了解这些流程对我们想在其中任何一个环节怎么操作bean的生成及修饰是非常有帮助的. ...

  3. 小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式:

    小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式: 直接引用VUE; 将vue.js下载到本地后本目录下使用; 安装Node环境下使用; ant-desig ...

  4. emmm 深入浅出教你看懂现代金融游戏

    3303只信仰公平[网易陕西省西安网友]1 比特币是骗人的.你们都被“现代帼家纸币”概念茜脑了,而且茜的很彻底,所以你们看不透比特币的骗局.简单来说,现代纸币是“空气纸”,比特币是“空气币(空气数据) ...

  5. 展开说说,Spring Bean IOC、AOP 循环依赖

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 延迟满足能给你带来什么? 大学有四年时间,但几乎所有人都是临近毕业才发现找一份好工作 ...

  6. Spring bean工厂配置头文件

    命名 beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=" ...

  7. 教你看懂网上流传的60行JavaScript代码俄罗斯方块游戏

    早就听说网上有人仅仅用60行JavaScript代码写出了一个俄罗斯方块游戏,最近看了看,今天在这篇文章里面我把我做的分析整理一下(主要是以注释的形式). 我用C写一个功能基本齐全的俄罗斯方块的话,大 ...

  8. 教你看懂C++类库函数定义之二---STDMETHOD介绍

    一切从一个C++ 类库头文件开始,现在在做一个C++的项目,期间用到一个开源的界面库DUILib(类似MFC),这个东西还不错能很容易的写出漂亮的界面,比如QQ的界面,可以去下载下来研究研究,地址:h ...

  9. 教你看懂C++类库函数定义之三---_stdcall

    一切从一个C++ 类库头文件开始,现在在做一个C++的项目,期间用到一个开源的界面库DUILib(类似MFC),这个东西还不错能很容易的写出漂亮的界面,比如QQ的界面,可以去下载下来研究研究,地址:h ...

随机推荐

  1. java中抽象类是否可以继承实体类?

    一道java 常见面试题,网上找到的几乎每个 java 面试笔试题大全或集锦里都能找到这道题. 题目如下:问: 抽象类是否可继承实体类 (concrete class) 答: 抽象类是可以继承实体类, ...

  2. 02 jmeter性能测试系列_JForum测试论坛的环境搭建

    软件测试高端专家培训 QQ 讨论群498721021 网站http://www.szwpinfo.com 1.进入jforum的官方网站,地址http://jforum.net/,下载 2.放入到to ...

  3. docker的核心概念和安装

    里Dcoker的安装要求 我这里安装的是在vmware下的centos7 64位 并且通过模拟远程连接xshell 我在安装好之后就配置了静态ip,这里我就不多说怎么配置了 具体静态ip配置可以参考 ...

  4. python if,循环的练习

    1.变量值的交换 ​ s1='alex'​ s2='SB' (s1,s2) = (s2,s1) 2.有存放用户信息的列表如下,分别存放用户的名字.年龄.公司信息 userinfo={ 'name':' ...

  5. express+websocket+exec+spawn=webshell

    var child_process = require('child_process'); var ws = require("nodejs-websocket"); consol ...

  6. redis--解析字符串

    # coding=utf-8import codecs if __name__ == '__main__': cmdlist = ("Decode") while True: cm ...

  7. Teradata简介

    Teradata是受欢迎的关系数据库管理系统之一. 它主要适用于构建大规模数据仓库应用程序.Teradata通过并行性的概念实现了这一点. 它是由Teradata公司开发的. 无限并行化-  Tera ...

  8. springboot问题总结

    前端使用jsp界面,但是jsp界面中引用的静态资源无论如何也加载不出来,弄一天了,哎 最后把pom文件里的jar全干掉,代码移除,就剩下登录界面,看css能不能进来,结果没问题, 然后看类里面的注解, ...

  9. web跨域问题(No 'Access-Control-Allow-Origin'..)

    1. 问题 angular开发中连接java服务时出现跨域问题(No 'Access-Control-Allow-Origin'..). 如下图 解决方法 2,原因分析 这个与安全机制有关,默认情况下 ...

  10. 解决laravel使用QQ邮箱发邮件失败

    在 laravel 中使用 QQ 发送邮件的时候莫名其妙的出现了如下错误:Connection could not be established with host smtp.exmail.qq.co ...