spring
1. spring的介绍
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
- 目的:解决企业应用开发的复杂性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
- 范围:任何Java应用
- Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
2. IOC的解释
IOC控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
我们正常的java代码要调用对象用的最多的就是new 如
建一个Dao
public interface UserDao {
public void getUser();
}
实现类
public class UserDaoIpml implements UserDao {
public void getUser() {
System.out.println("OK");
}
}
Service
public class UserService {
UserDaoIpml userDaoIpml=new UserDaoIpml();
public void getUser(){
userDaoIpml.getUser();
}
}
我们通过new的方式获得一个对象是我们敲代码的人选择去new出哪个对象来使用 而IOC是将这些类交给一个容器,使用的人需要了容器就将该类拿出来使用 实现注入
类似
这里我们改变Service的写法
public class UserService {
private UserDaoIpml userDaoIpml;
public UserService(UserDaoIpml userDaoIpml) {
this.userDaoIpml = userDaoIpml;
}
public void getUser(){
userDaoIpml.getUser();
}
public static void main(String[] args) {
//*****
UserDaoIpml userDaoIpml=new UserDaoIpml();
//*****
UserService userService=new UserService(userDaoIpml);
userService.getUser();
}0
}
我们吧main中被//*****包围的部分当成一个容器来管理我们的一些类像UserDaoIpml这些类被这个容器管理 在容器启动的时候这些类被创建 我们现在需要一个服务需要调用UserDaoIpml中的方法 UserDaoIpml被容器管理之后我们不需要再去new一个UserDaoIpml 只需要把容器中创建的UserDaoIpml拿出来注入到UserService中(类似于工厂模式)当然这里说的只是这种思想的入门,更深的同志们会在后面的学习中遇到
3. 介绍了IOC的概念我们正式进入Spring的学习
在Spring框架中上述所说的被容器管理的类被称为Bean
在Spring框架中 :org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
现在我们来看spring如何工作的
首先创建一个Maven工程
导入spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
- 编写一个Hello实体类
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
- 配置Spring IoC容器的元数据 此配置元数据表示您作为应用程序开发人员如何告诉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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
- 测试 ApplicationContext构造函数的一个或多个位置路径是资源字符串,可让容器从各种外部资源(例如本地文件系统,Java等)加载配置元数据CLASSPATH 是实例化容器的一种方式
@Test
public void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id .
Hello hello = (Hello) context.getBean("hello");
hello.show();
}
当一个spring服务中有多个以xml配置的Spring IoC容器的元数据时而且在一个bean的xml中需要用到另一个bean的xml中所注册的bean 那么可以通过import将多个bean的xml联系起来 (可以用于多人合作每个人负责一个模块的bean的管理)
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
3. 依赖注入
- Dependency Injection
- 依赖注入(Dependency Injection,DI)。
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
DI存在两个主要方式:基于构造函数的依赖注入和基于Setter的依赖注入
3.1 基于构造函数的依赖注入
public class SimpleMovieLister {
//SimpleMovieLister依赖于MovieFinder
private MovieFinder movieFinder;
private String name;
// 一个构造函数,以便Spring容器可以注入一个MovieFinder
public SimpleMovieLister(MovieFinder movieFinder,String name ) {
this.movieFinder = movieFinder;
}
// 实际使用注入的MovieFinder的业务逻辑被省略了……
}
在bean.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.xsd">
<beans>
<beans>
<bean id="SimpleMovieLister" class="x.x.ThiSimpleMovieListerngOne">
<!--constructor-arg 表示以构造器的方式注入 <constructor-arg ref=""/> ref除java自有的类型
type指定简单类型如String int等
-->
<constructor-arg ref="movieFinder"/>
<!--可以使用构造函数参数名称来消除歧义-->
<constructor-arg name="name" value="xiaopeng"/>
<!--通过使用type属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配-->
<constructor-arg type="java.lang.String" value="xiaopeng"/>
<!--
index表示构造函数中的的参数下标
<constructor-arg index="0" value="7500000"/>
-->
<constructor-arg index="0" value="7500000"/>
</bean>
<bean id="movieFinder" class="x.y.ThingTwo"/>
</beans>
基于Setter的依赖注入
定义一个需要注入的类
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在bean.xml中
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 使用嵌套的ref元素进行setter注入 -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- 使用更整洁的ref属性进行setter注入 -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
关于一些类型注入的方式
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
使用depends-on
如果一个bean是另一个bean的依赖项,则通常意味着将一个bean设置为另一个bean的属性使用
<!--在容器初始化beanOne时 读取到depends-on属性读到“manager” 会先注册manager depends-on的值可以为多个 用‘,’分开 -->
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
自动装配模式
使用基于XML的配置元数据时,您可以使用元素的autowire属性为 bean定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:
自动装配模式
no
(默认)无自动装配。Bean引用必须由ref元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName
按属性名称自动布线。Spring查找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配并且包含一个master属性(即它具有一个 setMaster(…)方法),那么Spring将查找一个名为的bean定义,master并使用它来设置该属性。
byType
如果容器中恰好存在一个属性类型的bean,则使该属性自动连接。如果存在多个错误,则会引发致命异常,这表明您可能无法byType对该bean使用自动装配。如果没有匹配的bean,则什么都不会发生(未设置该属性)。
constructor
类似于byType但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。
还有一些集合合并可查看官网
关于详细的注入说明可以查看源码
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies
使用注解来简化开发
创建一个spring工程
在bean.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.xxx" />
</beans>
这样就会自动扫描配置中的包
@Autowired @Autowired如果目标Bean仅定义一个以其开头的构造函数,则不再需要在此类构造函数上添加注释。但是,如果有几个构造函数可用,并且没有主/默认构造函数,则必须至少注释一个构造函数,@Autowired以指示容器使用哪个构造函数
@Autowired 它可以对类成员变量、方法及构造函数进行标注
@Qualifier(“xxx”) 和@Autowired配合使用 指定名称 注入给bean
public class MovieRecommender {
private MovieCatalog movieCatalog;
@Autowired
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
}
@Resource:可以根据类型注入,可以根据名称注入
@Resource //根据类型进行注入
@Resource(name = “userDaoImpl1”) //根据名称进行注入
private UserDao userDao;
@Value:注入普通类型属性
@Value(value = “abc”)
private String name;
完全注解开发
(1)创建配置类,替代 xml 配置文件
@Configuration //作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.xxx"})
public class SpringConfig {
}
AOP
百度百科(面向切面编程) 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP也是spring的核心部分,其存在的意义是 当我们一个功能开发完毕 但是我们又想添加一个功能插入的原有的功能上时 就可以利用AOP思想(代理) 将我们这个功能插入到原有的功能上
有两种情况动态代理
-
第一种 有接口情况,使用 JDK 动态代理创建接口实现类代理对象,增强类的方法
-
没有接口情况,使用 CGLIB 动态代理建子类的代理对象,增强类的方法
使用jdk动态代理实现aop
创建一个UserDao接口
public interface UserDao {
void add();
void update();
}
其实现类
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("add了一个user");
}
@Override
public void update() {
System.out.println("update了一个user");
}
}
利用Proxy 类里面的方法创建代理对象
(1)调用 newProxyInstance 方法
方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分
创建一个
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces={UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
// newProxyInstance用于返回一个代理实现类
UserDao userDao1 = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
userDao1.add();
userDao1.update();
}
//创建代理对象代码
static class UserDaoProxy implements InvocationHandler {
//1 把创建的是谁的代理对象,把谁传递过来
//有参数构造传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
//方法之前
System.out.println("方法之前执行...."+method.getName()+" :传递的参 数..."+ Arrays.toString(args));
//被增强的方法执行 method表示被代理的类中方法集合
Object res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后执行...."+obj);
return res;
}
}
}
结果为
方法之前执行....add :传递的参 数...null
add了一个user
方法之后执行....DynamicProxy.UserDaoImpl@2503dbd3
方法之前执行....update :传递的参 数...null
update了一个user
方法之后执行....DynamicProxy.UserDaoImpl@2503dbd3
在spring AOP中有一些术语
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
AOP有两种使用方式
第一种
创建接口和实体类
public interface UserDao{
public void add();
public void update();
}
public class UserDaoImpl implements UserDao{
public void add() {
System.out.println("add了一个user");
}
public void update() {
System.out.println("update了一个user");
}
}
写我们的要切入的类 这些类继承aop的之前 之后类
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
配置aop的xml
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
详细介绍可以看 作者@loongshawn
<?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">
可以通过Java配置类引入
<!-- <bean class="config.SpringConfig"/>-->
<!--注册bean-->
<bean id="userDaoImpl" class="AOPs.UserDaoImpl"/>
<bean id="log" class="AOPs.Log"/>
<bean id="afterLog" class="AOPs.AfterLog"/>
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut2" expression="execution(* AOPs.UserDaoImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut2"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut2"/>
</aop:config>
</beans>
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
UserDao userDaoImpl = context.getBean("userDaoImpl", UserDao.class);
userDaoImpl.add();
System.out.println("-----------------------");
userDaoImpl.update();
}
输出
AOPs.UserDaoImpl的add方法被执行了
add了一个user
执行了AOPs.UserDaoImpl的add方法,返回值:null
-----------------------
AOPs.UserDaoImpl的update方法被执行了
update了一个user
执行了AOPs.UserDaoImpl的update方法,返回值:null
第二种方式
创建一个 自定义的切入类
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
在xml中配置
<!--注册bean-->
<bean id="userDaoImpl" class="Aop2.UserDaoImpl2"/>
<bean id="diyPointcut2" class="Aop2.DiyPointcut"/>
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diyPointcut2">
<aop:pointcut id="diyPonitcut" expression="execution(* Aop2.UserDaoImpl2.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
第三种使用注解的形式
编写一个注解实现的增强类
@Aspect
public class AnnotationPointcut {
//@Before 注解表示作为前置通知
@Before("execution(* Aop3.UserDaoImpl3.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* Aop3.UserDaoImpl3.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* Aop3.UserDaoImpl3.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
常用注解
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。