spring基于配置类详解(IOC,AOP,动态代理切面通知)

配置类版的IOC:
除了XML和注解之外,spring还提供了通过配置类的方式来实现IOC。
通过配置类来实现依赖注入,需要提供一个自定义的配置类,该类和普通类没有区别,只需要在类上添加@Configuration注解。

在配置类上添加@Configuration注解和不加是很大区别的:
在不加@ConfigurationSpring依然可以扫描类中的注解也可以完成IOC和DI。但是无法实现对象的单例模式。没吊用一次方法就会创建一个新的对象。
加上@Configuration注解以后,配置类会被代理,然后代理对象被Spring进行扫描。在代理对象的方法中会优先检查容器中是否已经存在某个类的对象,如果已经存在则从容器中取出该对象进行使用。如果容器不存在才会调用真正的配置类中的方法来进行对象的创建。

当我们要创建对象或者完成依赖注入时:
需要手动在配置类中添加对应的方法,方法的返回值为你想要创建的对象,并在方法上添加注解@Bean。那么Spring就会将该方法中创建的这个对象保存到容器中。如果要实现依赖注入,可以调用配置类中其他的bean方法,然后手动set进去。

@Configuration
public class BeanConfiguration {
    @Bean
    public UserDao userDaoBean(){
        return new UserDaoImpl();
    }
    @Bean
    public UserService userService(){
        UserServiceImpl userService=new UserServiceImpl();
        userService.setDao(userDaoBean());
        return userService;
    }
}

注意:
默认情况下bean的ID为方法名,如果要修改bean的ID就在@Bean注解中写入beanId即可。
默认情况下产生的对象时单例的,如果要修改就在方法上添加@Scope注解,里面可以传入prottype、request、session等。
在以后的开发过程中,上述三种配置方式都会使用。而且是混合的用法。
混合方式1:
通过配置文件+注解的方式:以配置文件为主
在配置文件中一些无法添加注解的Bean,例如:连接池、整合框架的bean。然后我们自己的业务层、数据层、控制层会通过注解来完成依赖注入。
案例:业务层+数据层+druid连接池+资源文件配置jdbc
通过配置文件来加载spring容器,如果还有注解需要扫描,那么需要通过context:component-scan来扫描注解。
同时也能扫描到这些包下的配置类,也会去加载配置类的内容。

<context:component-scan base-package="com.woniuxy"/>

通过配置文件初始化容器加载过程:
在这里插入图片描述
在配置文件中加载资源文件的两种方式:
1.spring提供的声明式

 <context:property-placeholder location="dataSource.properties"/><!--推荐这一种-->

<context:property-placeholder location=“dataSource.properties”/>

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="location" value="dataSource.properties"></property>
</bean>

加载了资源文件以后就可以在applicationContext.xml中使用el表达式从资源文件中取值,例如:

dataSource.maxActive=50
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/xiaoxinkeji?useUnicode=true&characterEncoding=utf-8
dataSource.username=xiaoxinkeji
dataSource.password=xiaoxinkeji
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
   <property name="maxActive" value="${dataSource.maxActive}"></property>
   <property name="driverClassName" value="${dataSource.driverClassName}"></property>
   <property name="url" value="${dataSource.url}"></property>
   <property name="username" value="${dataSource.username}"></property>
   <property name="password" value="${dataSource.password}"></property>
</bean>

方式二:以配置类(XML)和注解混合 以配置类为主
在业务层、数据层、控制层上使用注解完成依赖注入,对于一些第三方的类无法添加注解的可以用配置类或XML来进行配置。
在这里插入图片描述
在配置类中要扫描其他的注解使用@ComponentScan,在配置类要加载配置文件使用@ImportResource

@Configuration
@ImportResource("classpath:applicationContext.xml")
@ComponentScan("com.woniuxy")
public class BeanConfiguration{}

Spring的第二大能力:AOP
AOP:A是Aspect,表示切面。AOP就是面向切面编程的意思。
在业务层中为了对事务进行管理,通常会有如下的代码模板,而且是每一个业务层方法都需要执行如下的步骤。
这样出来的代码冗余程度高(可维护性差)。处理代码冗余问题最常规的思路就是将公共代码提取出来,大家一起使用。
在这里插入图片描述
那么我们现在将公共模块抽取出来。
将事务管理的公共部分抽取出来成为一个类。
该类中具备4个方法:
在这里插入图片描述
代码拆分之后如何合在一起是一个问题,我们可以使用代理模式来完成这个过程。
代理模式:
代理模式中有两个角色:代理对象和目标对象(原对象)
代理对象通过持有目标对象,对目标对象的方法进行增强处理。
动态代理:由JAVA或者第三方的程序,代替我们来完成代理对象的创建过程。
动态代理有两种JDK动态代理和CGLIB的动态代理
以JDK动态代理为例:
1.我们需要将代理中需要执行的代码(事务管理的这一部分代码)封装起来。

public class TransactionHandler implements InvocationHandler {
    private Object target;
    public TransactionHandler(Object target){
        this.target=target;
    }
    @Override
    //第二个参数 正在执行的这个方法对象
    //第三个参数 执行这个方法所需要的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取连接
        Connection connection = ConnectionHelper.getConnection();
        connection.setAutoCommit(false);//关闭自动提交
        System.out.println(connection);
        Object result=null;
        try {
            //执行目标对象的方法
            result=method.invoke(target,args);
            connection.commit();
        }catch (Exception e){
            connection.rollback();
        }finally {
            connection.close();
        }
        return result;
    }
}

2.调用动态代理的API创建动态代理对象

 UserService proxy = (UserService) Proxy.new
     ProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), h);
//创建代理对象时
//第二个参数:目标对象实现的接口,因为代理对象也要实现这个接口
//第三个参数:InvocationHandler对象,因为这里面封装了我们要去执行的核心代码(事务管理)
//创建出来的代理对象集成了Proxy实现了UserService接口,父类对象的h属性中存储了InvocationHandler对象

3.通过创建出来的代理对象执行方法

proxy.transfer();

在调用动态代理的方法时,根据源码分析可以得知执行的是传入的InvocationHandler中的invoke方法。执行Invoke方法时传入了正在执行的这个transfer方法对象,同时还有执行该方法的参数。在InvocationHandler的invoke方法中封装事务管理的代码,然后通过反射执行目标对象的transfer方法。
在这里插入图片描述
以下是创建好的动态代理对象的源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.woniuxy.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
//JDK动态代理创建出来的代理类实现了UserService
//UserService proxy = (UserService) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), h);
//在创建代理对象时的第二个参数正是为了说明目标对象实现的接口,代理对象也会去实现这个接口
public final class $Proxy0 extends Proxy implements UserService {
    //定义了4个Mehtod对象 (Method对象可以用于执行方法)
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
	//代理对象中的Transfer方法
    public final void transfer() throws SQLException, Exception {
        try {
            //super.h就是我们在创建代理对象时传进来TransactioHanlder
            //h.invode就是在执行我们写的TransactionHandler中的invoke方法
            //m3是transfer的Method对象
            //无参所以传null
            super.h.invoke(this, m3, (Object[])null);
        } catch (Exception | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            //m1指代equals方法
            //m2指代toString方法
            //m3指代的是transfer方法
            //m0指代hashCode方法
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.woniuxy.service.UserService").getMethod("transfer");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

代理模式总结:
动态代理共两种:JDK CGLIB
JDK只能代理实现了接口的类,CGLIB都可以
JDK代理对象是目标对象的兄弟,他们实现了同一个接口。CGLIB代理对象是目标对象的子类。
我们在上面的案例中使用动态代理完成了统一的事务管理。那这种解决问题的方式就是面向切面的雏形。
面向切面编程编程的重要概念:
面向切面:将代码以横向的形式切入到某些方法中。
advice:通知,封装公共部分代码的类。根据切入位置的不同通知一共可分为5种。
前置增强、后置增强、异常增强、最终增强、环绕增强
join point:连接点,进行代码切入的某一个具体的方法。
point cut:切入点,描述所有连接点集合的一种规则。
通知代表了要切入的内容,切入点代表要切入的方法。
aspect:切面,切入点和通知构成一个切面。
weave :织入(编织),在判定目标对象处于切入点之上时,通过代理对象的方式将通知和目标对象编制在一起成为代理对象的过程。
SpringAOP就是建立在AOP思想之上的一套框架程序。我们可以通过配置或者注解将切入点信息和通知信息全部告知给Spring,Spring的IOC在创建对象时如果发现该对象处于切入点之上的,就会采用动态代理的方式来创建这个对象并且将通知代码织入进去。

SpringAOP配置版:
1.业务层数据层使用bean标签创建好对象完成依赖注入。

<bean id="userDao" class="com.woniuxy.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.woniuxy.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>

2.编写通知类,将各种通知对应的方法全部写好。

public class TransactionAdvice {
    public void openConnection(){
        System.out.println("获取连接,打开事务");//对应前置增强(aop:before)
    }
    public void commit(){
        System.out.println("提交事务");//对应后置增强(aop:after-returning)
    }
    public void rollback(){
        System.out.println("回滚事务");//对应异常增强(aop:after-throwing)
    }
    public void close(){
        System.out.println("关闭连接");//对应最终增强(aop:after)
    }
    public void around(ProceedingJoinPoint pj){//对应环绕增强(aop:around)
        try {
            System.out.println("获取连接,打开事务(环绕)");
            pj.proceed();
            System.out.println("提交事务(环绕)");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("回滚事务(环绕)");
        }finally {
            System.out.println("关闭连接(环绕)");
        }
    }
}

3.使用bean标签创建通知类对象

4.进行AOP的配置
最外层是aop:config标签。
在aop:config嵌套一个aop:aspect标签,aop:aspect标签需要添加一个属性ref引入通知对象的id。
aop:aspect中嵌套一个aop:ponitcut标签用于描述切入点,aop:ponitcut标签中需要添加两个属性id和expresstion(表达式)。
表达式的语法为:
execution(返回值类型 包.包.包.类.方法(参数类型)),我们可以使用通配符表示返回值类型,包,类,方法。
例如:execution(
com.woniuxy.service.impl..(…))就表示业务层包下的所有方法(无论任何返回值,任何参数)都作为我们的切入点。
使用aop:before、aop:after-returning、aop:after-throwing、aop:after、aop:around标签来进行不同种类增强的配置。

<!--aop的配置-->
<aop:config>
  <aop:aspect ref="transactionAdvice">
    <aop:pointcut id="point-cut" expression="execution(* com.woniuxy.service.impl.*.*(..))"/>
    <aop:before method="openConnection" pointcut-ref="point-cut"></aop:before>
    <aop:after-returning method="commit" pointcut-ref="point-cut"></aop:after-returning>
    <aop:after-throwing method="rollback" pointcut-ref="point-cut"></aop:after-throwing>
    <aop:after method="close" pointcut-ref="point-cut"></aop:after>
    <aop:around method="around" pointcut-ref="point-cut"></aop:around>
  </aop:aspect><!--配置通知-->
</aop:config>

备注:通常情况下这多种增强方式大家需要根据实际的应用场景来选择,而不是全部都使用。环绕增强和其他4个增强是等效的。如果既有四种增强又有环绕,执行顺序为:前置-环绕(环绕前置-目标方法-环绕后置/异常-环绕最终)-后置/异常-最终

SpringAOP注解版:
1.打开AOP注解版的开关
在配置类上添加@EnableAspectJAutoProxy注解开启注解版的AOP

@Configuration
@ComponentScan("com.woniuxy")
@EnableAspectJAutoProxy
public class BeanConfiguration {

}

2.在通知类上添加两个注解@Aspect和@Componet

@Aspect
@Component
public class TransactionAdvice {}

3.在通知类的方法上添加对应的注解进行增强
@Before:前置增强注解
@AfterReturning:后置增强
@AfterThrowing:异常增强
@After:最终增强
@Around:环绕增强
上述几个注解中都需要配置切入点,切入点的配置方式和配置文件是相同的


@Aspect
@Component
public class TransactionAdvice {
    @Around("execution(* com.woniuxy.service.impl.*.*(..))")
    public void around(ProceedingJoinPoint pj){
        try {
            System.out.println("获取连接,打开事务(环绕)");
            pj.proceed();
            System.out.println("提交事务(环绕)");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("回滚事务(环绕)");
        }finally {
            System.out.println("关闭连接(环绕)");
        }
    }
    @Before("execution(* com.woniuxy.service.impl.*.*(..))")
    public void openConnection(){
        System.out.println("获取连接,打开事务");
    }
    @AfterReturning("execution(* com.woniuxy.service.impl.*.*(..))")
    public void commit(){
        System.out.println("提交事务");
    }
    @AfterThrowing("execution(* com.woniuxy.service.impl.*.*(..))")
    public void rollback(){
        System.out.println("回滚事务");
    }
    @After("execution(* com.woniuxy.service.impl.*.*(..))")
    public void close(){
        System.out.println("关闭连接");
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值