Spring特性:DI,AOP

时间:2022-07-22
本文章向大家介绍Spring特性:DI,AOP,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Spring核心理念

基于pojo的轻量级和最小侵入性的编程

通过依赖注入和面向切面实现松耦合

基于切面和惯例进行声明式编程

通过切面和模版减少样板代码

依赖注入

Spring不会强制的让你去继承框架中的类,从而让你的项目与框架绑定,最坏的场景是使用Spring注解进行注入(DI),每一个类需要管理与自己相互协作的类的对象,会造成两个类耦合度很高。

public class A{
    
    private B b;
    
    public A(){
        this.b = new B();
    }
}

这样导致两个类的耦合度很高。之后的编程一般使用工厂类来管理耦合的类。

使用(DI)依赖注入,对象的依赖注入关系将有系统中协调各对方组件在创建时设定。依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖。

注入方法
构造器注入:
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
    <constructor-arg ref="userDaoJdbc"></constructor-arg>
</bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>

需要在Spring配值文件中配置注入的构造器所引用的类即可,在类中只要存在只含有配值的带唯一参数的构造器就可注入成功。

public class UserService implements IUserService {
 
    private IUserDao userDao;
 
    public UserService(IUserDao userDao) {
        this.userDao = userDao;
    }
    public void loginUser() {
        userDao.loginUser();
    }
}
注意:如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,只会注入到只含有一个参数的构造器中。
如果只有一个构造方法,但是有两个参数,一个是待注入的参数,另一个是其他类型的参数,不会注入成功的。

多参的构造函数注入配值见下:

<bean id="userService" class="com.lyu.spring.service.impl.UserService">
    <constructor-arg name="userDao" ref="userDaoJdbc"></constructor-arg>
    <constructor-arg name="user" ref="user"></constructor-arg>
</bean>

如果有多个构造方法,每个构造方法只有参数的顺序不同,那通过构造方法注入多个参数会注入到哪一个呢?哪个构造方法在前就注入哪一个,这种情况下就与构造方法顺序有关。

setter注入
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
    <!-- 写法一 -->
    <!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
    <!-- 写法二 -->
    <property name="userDao" ref="userDaoMyBatis"></property>

在配值property时,都会把name的首字母变为大写,并在前拼接set,

public class UserService implements IUserService {
 
    private IUserDao userDao1;
    public void setUserDao(IUserDao userDao1) {
        this.userDao1 = userDao1;
    }
 
    public void loginUser() {
        userDao1.loginUser();
    }
}

这样只要set方面名相同则可注入成功。

注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
基于注解

@Component:可以用于注册所有bean

@Repository:主要用于注册dao层的bean

@Controller:主要用于注册控制层的bean

@Service:主要用于注册服务层的bean

@Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。

@Autowired:spring注解,默认也是以byName的方式去匹配与属性名相同的bean的id,如果没有找到,就通过byType的方式去查找,如果查找到多个,用@Qualifier注解限定具体使用哪个。

所以如果只有一个实现类,则:

@Autowired
private IUserDao userDao;

就注入成功 但是如果IUserDao有多个实现类则需要:

@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;

指明是哪一个实现类。

同时注意,在注入引入的时候一般我们使用一个接口,这样的话,只有实现这个接口,就可与A进行协作,所以A与实现B接口的很多类进行了耦合,这样实现了完全的松耦合。

applicationContext

spring 通过上下文,装载并组装bean,它为每一个bean生成一个Id,通过配值得到引用。

面向切面

使服务于项目的组件模块化,并通过声明的方式应用到组件中去,使组件具有高内聚。

采用横向抽取机制,取代了传统纵向继承体系重复性代码。

AOP底层使用动态代理实现。包括两种方式:

  • (1)使用JDK动态代理实现。
  • (2)使用cglib来实现

之前的方法都是通过纵向的方式完成抽取机制

public class BaseUser{
    //添加日志
    public void writelog(){
        
    }
}

public class User extends BaseUser{
    public void add{
     super.writelog();
    }
}

面向切面是横向抽取机制

假设我们要使用execution()指示器选择Hello类的sayHello()方法,表达式如下:

execution(* com.test.Hello.sayHello(..))

方法表达式以* 号开始,说明不管方法返回值的类型。然后指定全限定类名和方法名。对于方法参数列表,我们使用(**)标识切点选择任意的sayHello()方法,无论方法入参是什么。

同时我们可以使用&&(and),||(or),!(not)来连接指示器,如下所示:

execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu)
xml配值实现
<bean id="audience" class="com.example.springtest.Audience"/>
<aop:config>
  <aop:aspect ref="audience">
    <!-- 申明切点 -->
    <aop:pointcut id="performance" expression="execution(* com.example.springtest.Performer.perform(..))"/>
    <!-- 声明传递参数切点 -->
    <aop:pointcut id="performanceStr" expression="execution(* com.example.springtest.Performer.performArg(String) and args(word))"/>  
    <!-- 前置通知 -->
    <aop:before pointcut-ref="performance" method="takeSeats"/>
    <!-- 执行成功通知 -->
    <aop:after-returning pointcout-ref="performance" method="applaud"/>
    <!-- 执行异常通知 -->
    <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    <!-- 环绕通知 -->
    <aop:around pointcut-ref="performance" method="timing"/> 
    <!-- 传递参数 -->
    <aop:before pointcut-ref="performanceStr" arg-names="word" method="dealString"/>
  </aop:aspect>
</aop:config>
注解实现
@Aspect
public class Audience{
    //定义切点
    @Pointcut(execution(* com.example.springtest.Performer.perform(..)))
    public void perform(){}

    //定义带参数切点
    @Pointcut(execution(* com.example.springtest.Performer.performArg(String) and args(word)))
    public void performStr(String word){}

    //搬凳子
    @Before("perform()")
    public void takeSeats(){}

    //欢呼
    @AfterReturning("perform()")
    public void applaud(){}

    //计时,环绕通知需要一个ProceedingJoinPoint参数
    @Around("perform()")
    public void timing(ProceedingJoinPoint joinPoint){
        joinPoint.proceed();
    }

    //演砸了
    @AfterThrowing("perform()")
    public void demandRefund(){}

    //带参数
    @Before("performStr(word)")
    public void dealString(String word){}
}
模版消除样式代码,spring中有很多模版代码省去了那些,重复必要的代码