这篇文章为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. ubuntu 装机步骤表

    步骤 1. root 步骤 apt-get update ; apt-get upgrate apt-get install git zsh apt-get install -y make build ...

  2. Linux下导入CA证书

    在部署路由器的时候,发现路由器不支持从https安装应用,经过调查,发现是路由器里面没导入证书 安装ca-certificates即可解决. opkg install ca-certificates

  3. C# & JAVA:读写文件

    using System; using System.IO; using System.Text; namespace ConsoleApplication4 { class Program { pu ...

  4. Odoo二次开发

    Odoo 点击进入

  5. 关于微信内置浏览器在ios上播放提示音的经验分享

    document.addEventListener("WeixinJSBridgeReady", function () { window.audio= new Audio() w ...

  6. css中的定位属性

  7. Linux守护进程管理利器——Supervisor

    Supervisor是采用 Python(2.4+) 开发的,它是一个允许用户管理 基于 Unix 系统进程的 Client/Server 系统,提供了大量功能来实现对进程的管理.安装: yum in ...

  8. C 语言 判断

    if if (im < 0) { im = 60 + im; ih--; } 如果 (im < 0) 小于零,那么 做{ } 中内容 如果 (im < 0) 不小于零,那么 { }中 ...

  9. 直达核心的快速学习PHP入门技巧

    PHP(外文名:PHP: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言.语法吸收了C语言.Java和Perl的特点,利于学习,使用广泛,是目前最火的 ...

  10. matlab-画个拱桥和倒影?

    matlab可用于各行各业的应用中,现在我们就简单画一个拱桥试一试. r=input('请输入半径: '); z=input('请输入弧度: '); figure() %r=4; %画拱洞,用李萨如图 ...