一篇教你看懂spring bean工厂和aop

时间:2023-03-08 22:19:05

这篇文章为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就可以帮助自己快速掌握和回顾,与诸君共勉.