Spring整理

149 阅读11分钟

简介

Spring 是一个轻量级的java开发框架,其核心是ioc(控制反转)和AOP(面向切面)

IOC

ioc 是一种思想,有多种实现方式。

如在类A中需要另一个类B时我们往往通过new来创建

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

而这样在我们的需求发生改变时我们我们就需要改变原有的代码

public class A{
    public B b=new C();
}

这时主动权在我们程序员手中,我们new了什么对象,用户就只能使用什么对象 当用户的需求改变时我们需要去修改我们的代码

因此我们做了这样的修改

public class A{
    public B b;
    public void set(B b){
        this.b=b;
    }
}

这样用户需要什么对象都可以自己传入,控制权在用户手上

这是很简单的一种控制反转的方法,spring中控制ioc的方法是DI(依赖注入)

依赖注入的本质是通过set注入,因此类中需要有对应的set方法

在spring中对象的创建销毁等都是由spring容器帮我们做的,我们只需告诉spring我们需要哪个对象即可

如A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。(引自网络)

参考:www.zhihu.com/question/23…

Spring的使用

  1. 首先我们需要导入一些相关的依赖包
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>
  1. 因为在spring中我们将对象的创建销毁等交给了spring容器因此我们需要将我们的对象交给spring容器 因此我们需要编写配置文件beans.xml(名称任意)
<?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-2.5.xsd"> 
  //我们将每个实体类作为bean注入到spring的容器中
  //id 作为bean的唯一标识,class为spring指明其代表的类
  <bean id="Test2" class="pojo.User">
      //property 相当于给对象的属性赋值,需要类中有相应的set方法
      //name 代表属性名,value代表要赋予的值
      // 当属性为一个对象时可通过 ref=""来赋值,ref引用的是spring容器中的对象
       <property name="test" value="testHello"/>
  </bean>
</beans>
  1. 当我们在编写好配置文件后,我们如何获取?
public static void main(String[] args) {
        //加载配置文件,在加载Beans.xml文件时会自动创建所有bean的实例,默认单例模式
        ApplicationContext Context = new ClassPathXmlApplicationContext("Beans.xml");
        //从spring中根据id获取类对象
        Test2 testHello2 = (Test2) Context.getBean("Test2");
    }

关于配置文件

IOC创建对象的方式

  1. 默认通过无参构造创建
  2. 当要使用有参构造时有三种方法
//下标指定
<bean id="user" class="cn.sxt.vo.User">
    <!-- index指构造方法参数下标 -->
        <constructor-arg  index="0"  value=""></constructor-arg>
    </bean>
//类型指定
<bean id="user" class="cn.sxt.vo.User">
    <!-- type指参数类型,同类型参数按顺序传 -->
        <constructor-arg  type="java.lang.String" value=""></constructor-arg>
    </bean>
<bean id="user" class="cn.sxt.vo.User">
    <!-- name指参数名称 -->
        <constructor-arg  name="name"  value=""></constructor-arg>
        <!--类型为对象时用ref引用-->
         <constructor-arg ref="user"  ></constructor-arg>
    </bean>

别名

<alias name="exampleBean" alias="bean"/>

name 也是别名,更高级,可以同时取多个

<bean id="uers" class="" name="user,u1,u2;u3"></bean>

为id为users的实体类配置别名为user,u1,u2,u3 分隔符可以用空格,逗号,分号等

import 导入配置文件,将多个配置文件合并

<import resource="bean.xml"></import>

依赖注入

构造器注入

在上面 "IOC创建对象的方式" 中已经说过了

set注入

<bean id="Test2" class="pojo.Test2">
    <!--bean注入-->
    <property name="test" ref="testHello"/>
    
    
    <!--数组-->
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>西游记</value>
            <value>水浒传</value>
        </array>
    </property>
    
    
    <!--set-->
    <property name="games">
        <set>
            <value>coc</value>
            <value>lol</value>
        </set>
    </property>
    
    
    <!--list-->
    <property name="hobby">
        <list>
            <value>swing</value>
            <value>run</value>
            <value>basketball</value>
        </list>
    </property>
    
    
    <!--map-->
    <property name="cards">
        <map>
            <entry key="身份证" value="123456"></entry>
            <entry key="银行卡" value="13456"></entry>
        </map>
    </property>
    
    
    <!--空值设置-->
    <property name="name">
        <null/>
    </property>
    
    
    <!--properties注入-->
    <property name="pop">
        <props>
            <prop key="键"></prop>
            <prop key="学号">134656</prop>
        </props>
    </property>
    
    
</bean>

拓展注入

p注入:直接set注入

导入命名空间

 xmlns:p="http://www.springframework.org/schema/p"
 <bean id="testHello" class="pojo.Test" p:str="d">
</bean>

c注入:通过构造器注入

xmlns:c="http://www.springframework.org/schema/c"
<bean id="testHello" class="pojo.Test" c:str="c">
</bean>

作用域

ScopeDescription
singleton在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

修改作用域

<bean id="testHello" class="pojo.Test" scope="singleton"></bean>

自动装配

autowrite

<bean id="dog" class="Dog"></bean>
<bean id="cat" class="Cat"></bean>
<bean id="people" class="people" autowire="byType"></bean>
autowrite=""
		   byName 通过id进行匹配,通过set方法注入,id需与set的参数名一致(需要保证beanid唯一)
		   byType 通过class匹配,通过set注入,要保证该bean所需和属性类型一样(保证bean的class唯一)
		   constructor 与byType类似,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个 bean,则将引发致命错误。
		   default
		   no (默认)无自动装配。 Bean 引用必须通过ref元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。

注解实现

前提

        <!--1. 添加约束--> 
       <?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"
              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 ">
   </beans>
        <!--2. 开启注解支持-->
       <context:annotation-config/> 

@Autowired

可直接在属性上使用,无需定义set方法

也可在set方法上使用

通过byType方式匹配,如果有多个则通过byName匹配,根据变量名与beanid

public class people {
    @Autowired
    private Dog dog;
    //required 默认为true 为false则该属性可以为空
    @Autowired(required = false)
    private Cat cat;
}

当有多个类型相同的bean时可通过@Qualifier(value=“beanid”)类唯一指定一个bean

public class people {

    @Autowired
    @Qualifier(value=“dog1”)
    private Dog dog;
    //required 默认为true 为false则该属性可以为空
    @Autowired(required = false)
    private Cat cat;
}

@Resource(name = "")

与Autowrited相似,但优先使用byname

public class TestServiceImpl {
 @Resource(name= "userDao")
 private UserDao userDao; 
}

注解开发

前提

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<context:annotation-config/>
<!--指定扫描的包,该包下的注解生效-->
<context:component-scan base-package="java"/>

@Component

@Component  //等价于<bean id ="test" class= "Test"/> id默认为类名的小写
@Scope("singleton") //等价于于<bean id ="test" class= "Test" scope="singleton"/> 
public class Test{
    //@Value()可以放在属性或set方法,()内为赋值
    @Value("xxxxx")//等价于 <property name="name" value="xxxx"/>
    public String name;
}

衍生注解

mvc模式

dao【@Repository】

service【@Service】

controller【@Controller】

功能同Component一样

@Configuration

@Configuration本身也是一个Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//说明Configuration也是一个组件
public @interface Configuration {
   boolean proxyBeanMethods() default true;

}

@Configuration有两种方式来注册bean,一种是通过 @ComponentScan("")和@Component 配合来扫描注册,一种是通过@Bean

@Configuration //本身也是一个组件
@ComponentScan("")//配置扫描的包
@Import(Config2.class)//导入其他配置
public class Config {
    @Bean //等价于注册一个bean id为方法名,class为返回值的类型
    public User getUser(){
        return  new User();
    }
}

注意:使用 @Configuration 后通过 new AnnotationConfigApplicationContext(Config.class); 来实例化容器

public class Mytest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
        User u=context.getBean("getUSer",User.class);
        System.out.println(u.getName());
    }
}

AOP(面向切面编程)

  1. OOP(面向对象编程)的延续,主要应用在日志切面、事务控制切面、权限控制切面、异步切面和缓存切面,利用AOP可以对任务解耦,通过重用性,提升效率。

  2. 只是一种编程范式,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。

  3. 编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。我们将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,在需要时将其织入到对应的切面中,这样我们修改这些日志记录等代码时不会影响核心业务的进行

  4. 在spring中通过动态代理的方式实现了AOP

代理模式

意图:为其他对象提供一种代理以控制对这个对象的访问。

角色:

  • 抽象角色︰一般会使用接口或者抽象类来解决(租房)

  • 真实角色︰被代理的角色(房东)

  • 代理角色︰代理真实角色(中介),代理真实角色后,我们一般会做一些附属操作(收取中介费)。

  • 客户: 访问代理对象的人

作用:

中介隔离作用:将客户与真实对象(房东)隔离,其特征是代理类和委托类实现相同的接口。(租房)

开闭原则,增加功能:当我们需要增加功能(收取中介费)时,只需修改代理类无需对真实对象进行修改。

缺点:

  1. 由于在客户和真实角色之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。

静态代理

在程序运行前,由程序员创建或特定工具自动生成源代码并对其编译生成.class
代理角色和真实角色的关系在运行前就确定了。

缺点:1. 一个真实角色对应一个代理角色,代码量翻倍

1.创建一个抽象类(租房)

public interface Rent {
    void rent();
}
  1. 创建真实对象(房东)
public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房东出租");
    }
}
  1. 创建代理对象(中介)
public class Proxy implements Rent{
    private Host host;

    public Proxy(Host host) {
        this.host = host;
    }

    public Proxy() {
    }

    @Override
    public void rent() {//代理Host
        host.rent();
        getmoney();
    }
    public void getmoney(){
        System.out.println("收取中介费");
    }
}
  1. 创建客户
public class Client {
    public static void main(String[] args) {
        Proxy proxy=new Proxy(new Host());
        proxy.rent();//通过代理使用Host的方法
    }
}

动态代理

   由jdk创建代理角色,我们不用手工写了,代理的真实角色是可变的

可以分为两大类:

  1. 基于接口的动态代理

  2. 基于类的动态代理 这里我们主要了解JDK基于接口的动态代理

动态代理的步骤:

  1. 实现InvocationHandler,实现invoke方法
  2. 在invoke方法中,编写原本代理类(中介)的功能
  3. 在客户端通过Proxy类获取代理类
  4. 通过代理类调用方法
//1.实现InvocationHandler,实现invoke方法
public class ProxyIH implements InvocationHandler {
    private Object target;//真实对象
    public ProxyIH(Object target){
        this.target=target;
    }
    @Override
    // proxy  jdk创建的代理对象
    // Method 真实对象的方法 
    // agrs 真实对象方法的参数
    // 以上均由jdk提供
    //2. 在invoke方法中,编写原本代理类(中介)的功能
    public Object invoke(Object proxy, Method method, Object[] args){
        // 调用传入对象target的方法,args为方法参数
        method.invoke(target,args);//执行目标方法(租房)
        System.out.println("代理执行");//代理类拓展的功能
        return null;
    }

}

一个代理类需要:

  1. 真实对象------> h.getClass().getClassLoader()
  2. 继承抽象接口------> h.getClass().getInterfaces()
  3. 实现代理方法 ------>proxyIH
public class Mytest {
    public static void main(String[] args) {
       //创建真实对象
       Rent h= new Host();
       //根据真实对象创建InvocationHandler
       InvocationHandler proxyIH = new ProxyIH(h);
       // 获取代理对象
       // 3. 在客户端通过Proxy类获取代理类
        Rent o = (Rent)Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), proxyIH);
        //4. 通过代理类调用方法
        o.rent();

    }
}
//-------------------运行结果----------------
房东出租
代理执行

至此假设我们有多个真实对象我们只需变更Rent h= new Host();就可以获得不同的代理对象, 不需要我们手动创建,由jdk根据反射创建,代理的真实角色是可变的

AOP的使用

AOP 也是一种思想,有多种实现方式。在spring中通过动态代理实现

有几个概念需要了解

  • Advice(增强):指你想要给目标对象增加的功能

  • Target(目标对象):织入 Advice 的目标对象.。

  • Joint point(连接点):即spring允许你插入Advice的地方,如每个方法执行的前后,抛出异常的前后等,即我们可以插入Advice的地方

  • Pointcut(切点):当一个类中有多个方法,而我们只想要在其中一个或几个方法的前后插入Advice,那么我们可以通过切点来指定哪个方法要被插入,即切点是我们要插入Advice的地方

  • Aspect(切面) Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

  • Weaving(织入):将 Aspect 和其他对象连接起来并创建 Adviced object 的过程,

导入依赖

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

在spring配置文件中添加以下代码

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

有三种实现方式**

UserserviceImpl

public class UserserviceImpl implements Userservice{
    public void add() {
        System.out.println("add");
    }
}

通过SpringAPI实现

定义要织入的操作

//执行前插入的操作
public class BeforeLog implements MethodBeforeAdvice {
    //目标对象的方法,参数,目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass()+"的"+method.getName()+"被执行了");
    }
}
//执行后插入的操作
public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass()+"的"+method.getName()+"被执行并返回:"+returnValue);
    }
}

在spring配置文件中定义切点

<bean id="BeforeLog" class="log.BeforeLog" ></bean>
<bean id="AfterLog" class="log.AfterLog"></bean>
<!--在UserserviceImpl类的任意方法及其参数都是一个可以插入代码的点 poincut-->
<aop:config>
    <!--                                      目标位置:execution( 修饰词 返回值 类名 方法名 参数-->
    <aop:pointcut id="pointcut" expression="execution(* server.UserserviceImpl.*(..))"/>
    <!--执行环绕增加-->
    <!--active-ref指定要插入的操作,pointcut-ref指定切点-->
    <!--将log类中的方法插入到UserserviceImpl方法执行之前执行-->
    <aop:advisor advice-ref="BeforeLog" pointcut-ref="pointcut" ></aop:advisor>
    <aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>

运行结果:

class server.UserserviceImpl的add被执行了
add
class server.UserserviceImpl的add被执行并返回:null

自定义类实现

public class Diypointcut {
    public void before(){
        System.out.println("before exe");
    }
    public void after(){
        System.out.println("after out");
    }
}
<bean id="Diypointcut" class="Diypointcut"></bean>
<aop:config>
    <!--选择自定义类-->
    <aop:aspect ref="Diypointcut">
        <!--设置切点-->
        <aop:pointcut id="point" expression="execution(* server.UserserviceImpl.*(..))"/>
        <!--选择执行的方法和切点-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>

</aop:config>

测试结果:

before exe
add
after out

注解实现

在spring配置文件中开启注解支持

<bean id="anno" class="AnnoPointcut"></bean>
<aop:aspectj-autoproxy/>
@Aspect //标注为切面
public class AnnoPointcut {
    @Before("execution(* server.UserserviceImpl.*(..))")
    public void before(){
        System.out.println("before anno");
    }
    @After("execution(* server.UserserviceImpl.*(..))")
    public void after(){
        System.out.println("after anno");
    }
    //Around 环绕,即在方法执行前后
    @Around("execution(* server.UserserviceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around before");
        Object proceed = joinPoint.proceed();
        System.out.println("around after");
    }
}

运行结果:

around before
before anno
add
after anno
around after

AOP 小结

运用动态代理的方式将一些重复的功能插入到核心业务的前后,对原有代码毫无入侵性。