Spring
概述
体系结构(核心:IOC、AOP)
优点
1、轻量型框架
2、针对接口编程,解耦合各模块
3、AOP编程
4、方便集成各种框架
IOC
概述
IOC:控制反转,是一个理论、概念。技术实现是DI(依赖注入)。最底层还是JAVA的反射机制。
把对象的创建,赋值,管理工作交给代码之外的容器实现管理。(权力上交给容器)
控制:创建对象,对象的属性赋值,对象之间的关系管理。
反转:把创建对象等权力转移给容器,由容器代替管理人员管理对象。
spring配置文件
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
1、beans:是根标签,spring把java对象称为bean。
2、spring-beans.xsd是约束文件,和mybatis指定的dtd是一样的。
<bean id="" class=""></bean>
id:对象的自定义名称,唯一值,spring在map集合中通过这个名称找到对象。
class:对象类的全限定名称,spring通过这个地址来找到这个类,必须得是类,不能是接口。
调用spring配置文件实例
注:bean中类对象创建时机为读取config文档,即读即创建,且创建时候调用的是该类的无参构造方法。
ApplicationContext中方法
ac.getBean("");//获取指定id(名称)的对象
ac.getBeanDefinitionCount();//获取容器中定义的对象的数量
String[] names=ac.getBeanDefinitionNames();//获取容器中定义对象的名字
DI(依赖注入)
根据注入方式的不同,分为:
1、set注入(设值注入):spring调用类的set方法,实现属性的赋值。
2、构造注入:spring调用类的有参数构造方法,来实现属性的赋值。
根据对引用类型可以进行自动注入,根据规则的不同,分为:
1、byName方式自动注入
根据java类中引用类型的属性名和spring容器中的id一样,且数据类型是一致的,则可以自动注入。
<bean id="" class="" autowire="byName"><!--加入了自动注入,只需要自己加入简单类型的赋值即可,引用类型可根据规则进行自动注入-->
<property name="" value=""/>
<property name="" value=""/>
<property name="" value=""/>
<!--<property name="" ref=""/> 这条语句不用了-->
</bean>
2、byType方式自动注入
根据java类中引用类型的数据类型和spring容器中的 class属性是同源关系的,这样的bean能够赋值给引用类型。
注:同源关系:相同类、父子类关系、接口和实现类关系。
如果根据类型自动注入中,同源关系的类有多个,就会报错。
<bean id="" class="" autowire="byType"><!--加入了自动注入,只需要自己加入简单类型的赋值即可,引用类型可根据规则进行自动注入-->
<property name="" value=""/>
<property name="" value=""/>
<property name="" value=""/>
<!--<property name="" ref=""/> 这条语句不用了-->
</bean>
使用.properties数据
<!--加载写到.properties中的数据-->
<context:property-placeholder locatiron=""/>
用${ }来进行取值
基于配置文件(xml)的DI
set注入
简单类型及String类型的set注入
<!--set注入 简单类型的属性值注入-->
<bean id="" class="">
<property name="" value=""/>
<property name="" value=""/>
<property name="" value=""/>
<!--name中是属性名,value是其值,由于xml规则规定,不管是string类型和int类型的value都要加“”-->
</bean>
注:spring在创建相关类的对象后首先调用无参构造函数,然后根据其属性调用相对应的setter方法。例:name —> getName()
引用类型的set注入
<!--set注入 引用类型的属性值注入-->
<bean id="" class="">
<property name="" value=""/>
<property name="" value=""/>
<property name="" ref="xxx" />
<!--name中是属性名,ref中填入引用对象的id名称,如下文xxx-->
</bean>
<bean id="xxx" class="">
</bean>
构造注入
简单类型及String类型的构造注入
<!--构造注入 简单类型的属性值注入--><bean id="" class=""> <constructor-arg name="" value=""></constructor-arg> <!--name中是属性名,value是其值--></bean><!--第二种方法--><bean id="" class=""> <constructor-arg index="" value=""></constructor-arg> <!--index中是索引,即参数在全参构造方法中的位置,从0开始,value是其值。可以省略index,但此时必须按形参顺序进行传参--></bean>
引用类型的构造注入
<!--构造注入 引用类型的属性值注入--><bean id="" class=""> <constructor-arg name="" ref="xxx"></constructor-arg> <!--name中是属性名,ref中填入引用对象的id名称,如下文xxx--></bean><bean id="xxx" class=""></bean><!--第二种--><bean id="" class=""> <constructor-arg index="" ref="xxx"></constructor-arg></bean><bean id="xxx" class=""></bean>
多配置文件的构建(包含关系的配置文件)
在主配置文件中如spring-total中,指定其他配置文件,告诉spring在该路径下读取相关spring配置文件。
关键字:"classpath:"表示类路径(class文件所在目录)
主配置文件:
<!--可以使用通配符如*,此时注意应该将主配置文件的名称移除这个通配符范围,即通配符范围内不能包括主配置文件--><import resource="classpath:xxx/xxxx.xml" /><import resource="classpath:xxx/xxxx.xml" /><import resource="classpath:xxx/xxxx.xml" />
基于注解的DI
必须加入的依赖:spring-context,主要依赖其中的spring-aop依赖
相对于基于配置文件的DI来说侵入性太强,如果需要更改必须更改源代码。
在spring的配置文件中加入一个组件扫描器的标签,说明注解在项目中的位置,这样才能把注解进行解释。
<!--组件扫描器--><!--工作方式:spring会扫描便利base-package中指定的包,在包中和子包中的所有类中寻找注解,并根据注解的功能进行相应操作如创建对象--><context:component-scan base-package="包名"/><!--指定多个包 方式1--><context:component-scan base-package="包名"/><context:component-scan base-package="包名"/><!--指定多个包 方式2 使用;或,隔开即可--><context:component-scan base-package="包名;包名;包名"/><!--指定多个包 方式3 指定父包--><context:component-scan base-package="com.sinotruk"/>
1、@Component
作用位置:类名上方
创建对象,标准一个普通的spring Bean类,等同于作用,其中有value属性,也就是bean的id值,唯一值,创建的对象在容器中也是唯一的。
@Component("student1")//student1是该对象在spring中的idpublic class Student { }
@Component//不指定value时,默认创建的id是类名的首字母小写如 studentpublic class Student { }
@Repository
功能与@Component相似,放在持久层 Dao的实现类上,用于创建dao对象,其对象用来访问数据库进行数据的传递。
@Service
功能与@Component相似,主要用于业务层 Service层对象上,用来创建service对象,service对象是做业务处理,可以有事务等功能。
@Controller
功能与@Component相似,主要用于控制器类的上面,用于创建控制器对象,其主要功能是接受前端传过来的数据,并且显示给前端其处理结果。
2、@Value
作用位置:属性上方或setter方法上方
简单类型的属性赋值,其value是String类型,需要加引号,表示给这个属性赋值。
@Value("zjh") private String name; @Value("22") private Integer age;
3、@Autowired
作用位置:引用类型的属性上方或setter方法上方或构造方法上方
属性:value、required(boolean类型,默认true,表示引用类型赋值失败时,程序报错,并终止执行)
引用类型的属性赋值,默认使用byType类型的自动注入规则。属于 Spring 的容器配置的一个注解,与它同属容器配置的注解还有:@Required,@Primary, @Qualifier 等等。因此 @Autowired 注解是一个用于容器 ( container ) 配置的注解。
@Autowired //默认通过byType规则进行自动注入 private School school;
当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注解和 @Autowired 注解通过指定哪一个真正的 bean 将会被装配来消除混乱。
@Autowired //使用byName规则进行自动注入时,需要结合 @Qualifier("")注解使用 @Qualifier("school") private School school;
4、@Required(已弃用)
作用位置:setter方法上方
它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,或在注解中赋值,否则容器就会抛出一个 BeanInitializationException 异常。
5、@Resource
这个功能是jdk注解,spring提供了对这个注解的支持。
作用位置:属性上方或setter方法上方
属性:name、type
与@Autowired注解功能类似,引用类型的属性赋值,默认使用byName类型的自动注入规则,如果byName自动注入规则失败了,则使用byType自动注入规则。
@Resource private School school; @Resource(name="") //只使用byName方式匹配 private School school; @Resource(type="") //只使用byType方式匹配 private School school;
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
6、@Bean
作用位置:方法上方
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
@Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名,相对于@Component来说,可以将他人的类进行spring管理,而不必在其源代码中更改。
@Configurationpublic class AppConfig { // 使用@Bean 注解表明myBean需要交给Spring进行管理 // 未指定bean 的名称,默认采用的是 "方法名" + "首字母小写"的配置方式 @Bean public MyBean myBean(){ return new MyBean(); }} public class MyBean { public MyBean(){ System.out.println("MyBean Initializing"); }}
AOP
动态代理
在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。
1)代理类数量可以很少,
2)当你修改了接口中的方法时,不会影响代理类。
动态代理:在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类
换句话说: 动态代理是一种创建java对象的能力,让你不用创建TaoBao类,就能创建代理类对象。
在java中,要想创建对象:
创建类文件, java文件编译为class
使用构造方法,创建类的对象。
CGLIB
CGLIB(Code Generation Library),是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.7</version></dependency>
CGLIB动态代理的原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以CGLIB生成动态代理,要求目标类必须能够被继承,而不能是final的类。
jdk动态代理
使用java反射包中的类和接口实现动态代理的功能。反射包java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy.
简单来说,就是在不更改源代码的基础上,将目标类进行功能增强,这个过程是在程序执行过程中。
public class JDKDynamicProxy implements InvocationHandler { private Object target; public JDKDynamicProxy(Object target) { this.target = target; } /** * 获取被代理接口实例对象 * @param <T> * @return */ public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Do something before"); Object result = method.invoke(target, args); System.out.println("Do something after"); return result; }}public class Client { public static void main(String[] args) { // 保存生成的代理类的字节码文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // jdk动态代理测试 Subject subject = new JDKDynamicProxy(new RealSubject()).getProxy(); subject.doSomething(); }}
概述
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
个人感觉,就是将动态代理进行规范化、简单化与统一化,使使用者可以更加容易理解和使用动态代理了。
Aspect
切面,表示增强的功能,即在原有功能中新加入的功能,一般为非业务功能,常见的切面功能有日志、事务、统计信息、参数检查、权限验证。
JoinPoint
连接点,可以被切入的位置。
Pointout
切入点,切入业务方法和切面的位置,即类中的业务方法,可以是多个连接点。
目标对象
指目标类,即要给那个类的方法增强功能,这个类就是目标对象。
Advice
通知,表示切面功能执行的时间,方法之前或方法之后等等。
适用范围
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
aspectJ
aspectJ是一个开源的aop框架,是eclipse中的开源项目。spring框架中继承了aspectJ框架。在目标类没有接口是使用的是CGLIB的动态代理,如果有接口默认的使用JDK的动态代理。
实现AOP
1、Advice
@Before
前置通知,在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式
@After
后置通知, 在目标方法(切入点)执行之后执行。
和@Before用法类似,总是被执行的,即使出现异常,也会被执行。
@Around
环绕通知,目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。通常用于统计方法耗时,参数校验等等操作。环绕通知早于前置通知,晚于返回通知。
加入此注解方法必须有返回值推荐Object,且其参数为ProceedingJoinPoint,这个类是JoinPoint的继承类。等同于JDK动态代理中的Method,用来执行目标方法的。
@Around(value = "execution(public void com.sinotruk..zjh.MyService.doSome())") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知开始"); Object res=joinPoint.proceed(); System.out.println("环绕通知结束"); return res; }
@AfterReturning
返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行
pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “”
@AfterReturning(value = "execution(public void com.sinotruk..zjh.MyService.doSome())",returning = "res") public void myAfterReturning(JoinPoint joinPoint,Object res){ System.out.println(joinPoint.getSignature()); System.out.println("==================="); }
注:returning属性中的值必须与该方法参数的名称一致
@AfterThorwing
异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知,pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “”。注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数。
除了有value属性外,还有一个属性就是throwing,与AfterReturning类似,必须与形参中的一致。
主要用于监控执行过程中有没有异常,如果有异常可以发送邮件或者短信进行通知。
@AfterThrowing(value = "execution(public void com.sinotruk..zjh.MyService.doSome())",throwing = "ex") public void myAround(JoinPoint joinPoint,Exception ex) throws Throwable { System.out.println("出现异常"); }
2、poingcut
?表示可以没有
可以使用通配符
*匹配0到多个任意字符
…用在方法参数中,表示任意多个参数;用在包名后,表示当前包及子包路径
+用在类名后表示当前类及其子类;用在接口后表示当前接口及其实现类
execution(访问权限符? 方法的返回类型 方法声明(参数) 异常类型?)
切入点表达式要匹配的对象就是目标方法的方法名。
实现步骤(以@Before为例)
1、加入spring依赖以及aspectJ依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.9</version> </dependency>
2、创建业务方法的一个接口以及其实现类,即目标类
3、创建切面类
在切面类上方加入注解@Aspect,是aspectJ框架中的注解,用来表示当前类是切面类,也就是用来给业务方法增加功能的类。
然后定义这个类中的方法,用来实现切面功能,其要求如下:
1)公共方法public
2)方法没有返回值
3)方法名称自定义
4)方法可以有参数也可以没有参数,如果有参数,参数不是自定义的,有几个参数类型可以使用。如JoinPoint,可以在这个类的对象中获取方法执行的信息,例如方法名称,方法的实参,此参数必须为第一个参数。
@Aspectpublic class AspectJ { @Before("execution(public void com.sinotruk.MyServiceImpl.doSome(String,int))") public void myBefore(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature()); System.out.println("==================="); }}
或者
@Pointcut("execution(* com.ysu.zjh.test1.Service1.*(..))") public void pointcut1() { } //@3:定义了一个前置通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效 @Before(value = "pointcut1()") public void before(JoinPoint joinPoint) { //输出连接点的信息 System.out.println("前置通知," + joinPoint); }
4、在配置文件中声明自动代理生成器
<aop:aspectj-autoproxy /><!--会把spring容器中的所有的对象目标,一次性都生成--><!--<aop:aspectj-autoproxy proxy-target-class="true"/>--><!--有接口情况下,一样使用CGLIB动态代理-->