spring
准备工作
新建一个java project即可,学spring一个java项目足够了,后期需要什么jar包,导包就可以了
工程结构如下,导入以下几个jar包即可,本篇博客只是快速讲解ioc以及aop的入门知识,具体的每一个后续会逐个讲解
为什么会诞生Spring
最主要的几点如下:
- 1、使用spring可以降低代码间的耦合度
- 2、使用spring可以解决循环依赖问题
- 3、我们以前的service实现类中,充斥着各种try,catch以及事务相关的代码,大部分代码是冗余的,但又是不能不写的,spring帮助我们关注具体的业务开发
ioc
什么是ioc
控制反转(Inversion of Control,缩写为IoC),可以用来减低计算机代码之间的耦合度。接下来我们看几个具体的例子,ioc容器中存储bean,我们通过配置bean,让ioc容器帮助我们创建实例,然后我们通过依赖注入,将ioc帮助我们创建的实例,注入到我们所需要的位置。
没有ioc之前
那么有意思的来了,假如我HelloworldDao的实现类改变了,那我必须修改我们的Service实现类,但是我们要满足一个基本的原则,即对扩展开放,对修改关闭,那么显然,上述是不符合的。
怎么配置bean
有了ioc之后
property标签,是通过调用类的setter方法。如果类中没有对应属性的setter方法,就会报错。这里值得注意的是属性是由setter方法决定的,然而变量则是在类中直接定义的。属性与变量不能混为一谈。
内部bean,顾名思义,在内部使用的bean,可以保证安全性。上述xml配置文件可以修改为
<bean class="serviceImpl.HelloWorldServiceImpl" id="helloWorldService">
<property name="helloWorldDao">
<bean class="daoImpl.HelloWorldDaoImpl"/>
</property>
</bean>
我们就没必要给内部bean配置id了,因为id就是给这个bean起的类似于唯一编号的东西,就像java中每个成员变量一样,名称不能重复,那么我们在使用匿名对象的时候我们是不会用一个成员变量来接收的。什么意思呐?参考下列代码:
//Class A
public class A{
private B b;
private A(){
}
public A(B b){
this.b = b;
}
}
//Class B
public class B{
private int value;
private B(){
}
public B(int value){
this.value = value
}
}
//Main函数
A a = new A(new B(2));
//看到这里就明白了,new B(2)就是个匿名对象,匿名对象只为它一个人服务
同样的内部bean也只为外部bean服务。
只需要在xml文档中简单地进行配置,就可以让ioc容器帮助我们创建一个对象,我们使用注解便可以注入进去。准确地来说使用了依赖注入(DI),接下来让我们细致的深入了解ioc。那么我们实际上如果dao的实现类发生改变,我并不需要去修改具体的service实现类,只需简单地修改bean的配置就好了,由此得出spring可以帮助我们降低代码的耦合度。
而且注意了,只有某个类被ioc容器管理,我们可以明显的看到左上角有个小小的s
bean的实例化方式
bean的实例化方式一共有4种,目前讲解最为常见的构造器实例化,采用的是无参数构造器
如果你没有无参数构造器,就会报错
bean的属性注入方式
有参构造器注入
这种方式不能够解决循环依赖问题
什么是循环依赖
很明显,构造A对象,首先要有B对象,构造B对象又要有A对象,就这样无休止下去,禁止套娃。
尝试使用构造器注入解决循环依赖
整体结构如下
public class A {
private B b;
public A(B b) {
this.b = b;
System.out.println("A构造成功");
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
System.out.println("B构造成功");
}
}
applicationContext.xml
<bean class="test.B" id="b">
<constructor-arg name="a" ref="a"/>
</bean>
<bean class="test.A" id="a">
<constructor-arg name="b" ref="b"/>
</bean>
很明显,我们只要能在控制台得到**构造成功,就证明我们解决了循环依赖问题
为什么我们执行main方法也能出现下面的错误?这里简单解释一下,因为这里并不是懒加载,程序开始执行时会直接把所有资源一次性加载。
错误信息
Error creating bean with name 'b': Requested bean is currently in creation:
Is there an unresolvable circular reference?
setter注入
只有类中有set方法,就可以通过property进行注入,值得注意的是,引用类型使用ref,简单类型使用value,集合类型使用对应的标签。而且对于这一规则构造器注入以及setter注入是通用的。
setter解决循环依赖
整体结构如下
xml文件
<bean class="test.B" id="b">
<property name="a" ref="a"></property>
</bean>
<bean class="test.A" id="a">
<property name="b" ref="b"></property>
</bean>
A,B构造成功,解决了循环依赖问题,下面讲解给bean注入值的三种方式。
ref
value
新建一个类,结构如下
package entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class User {
private long id;
private List<String> list;
}
applicationContext.xml
<bean class="entity.User" id="user">
<property name="id" value="10"/>
</bean>
测试
对应集合类型标签
修改上述applicationContext.xml
<bean class="entity.User" id="user">
<property name="id" value="10"/>
<property name="list">
<list>//对应集合类型的标签
<value>test1</value>//同样的简单类型使用value
<value>test2</value>
<null/>//注意设置为空比较特殊
</list>
</property>
</bean>
其余的就集合类型不再展示了,下面给出官方给出的文档,里面有具体的使用方法
<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>
测试
P命名空间
p命名空间不太常用,因为不直观
上述的
<bean class="test.B" id="b">
<property name="a" ref="a"></property>
</bean>
<bean class="test.A" id="a">
<property name="b" ref="b"></property>
</bean>
使用p命名空间即
<bean class="test.B" id="b" p:a-ref="a"/>
<bean class="test.A" id="a" p:b-ref="b"/>
获取bean的常用三种方式
上述我们直接使用了di注解,autowired,那么加入我们不使用注解,我们该怎么获取bean?
applicationContext.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">
<bean class="serviceImpl.HelloWorldServiceImpl" id="helloWorldService">
<property name="helloWorldDao">
<bean class="daoImpl.HelloWorldDaoImpl"/>
</property>
</bean>
</beans>
beanFactory
什么是beanFactory
BeanFactory是Spring最古老的接口,表示Spring IoC容器- -生产bean对象的工厂,负责配置,创建和管理bean被Spring IoC容器管理的对象称之为bean。是延迟加载的
使用beanFactory获取Bean
下面列举最常见的三种
通过id值去找到bean
@Test
void main() {
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//通过bean的id值,去找到bean(不推荐)
HelloWorldService hws = (HelloWorldService)beanFactory.getBean("helloWorldService");
hws.hello();
}
为什么不推荐使用这种?
获取bean以后需要强制转化
通过类的字节码去找到bean
@Test
void main() {
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//通过bean的字节码,去找到bean(不推荐)
HelloWorldService hws = beanFactory.getBean(HelloWorldService.class);
hws.hello();
}
优点:不用强制转化了
缺点:容易出现bean注入歧义性问题
下面来演示一下歧义性问题(修改applicationContext.xml)
<bean class="serviceImpl.HelloWorldServiceImpl" id="helloWorldService">
<property name="helloWorldDao">
<bean class="daoImpl.HelloWorldDaoImpl"/>
</property>
</bean>
<bean class="serviceImpl.HelloWorldServiceImpl" id="helloWorldService1">
<property name="helloWorldDao">
<bean class="daoImpl.HelloWorldDaoImpl"/>
</property>
</bean>
我们可以看到,对于上述配置文件,两个bean的class是一致的
再次执行main()方法,错误信息如下
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'service.HelloWorldService' available: expected single matching bean but found 2: helloWorldService,helloWorldService1
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:422)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345)
at controller.MyTest.main(MyTest.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:515)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:105)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
我们提取最重要的
NoUniqueBeanDefinitionException: No qualifying bean of type ‘service.HelloWorldService’ available: expected single matching bean but found 2
大概意思就是我找到两个bean,我不知道你要使用哪一个,这就是歧义性问题
通过id值以及class去找到bean
@Test
void main() {
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//通过id值以及class去找到bean(推荐)
HelloWorldService hws = beanFactory.getBean("helloWorldService",HelloWorldService.class);
hws.hello();
}
通过这种方式就可以解决注入时候的歧义性问题
applicationContext
什么是applicationContext
我们可以看到applicationContext是一个BeanFactory的子接口,我们使用其实现类ClassPathXmlApplicationContext
使用applicationContext获取Bean
跟BeanFactory一样,有三种,这里就不再一一演示了。
@Test
void main2() {
ApplicationContext atx = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorldService hws = (HelloWorldService)atx.getBean("helloWorldService");
HelloWorldService hws1 =atx.getBean(HelloWorldService.class);
HelloWorldService hws2 = atx.getBean("helloWorldService", HelloWorldService.class);
}
beanFactory以及applicationContext对比
修改service实现类
public class HelloWorldServiceImpl implements HelloWorldService {
private HelloWorldDao helloWorldDao;
public void setHelloWorldDao(HelloWorldDao helloWorldDao) {
this.helloWorldDao = helloWorldDao;
}
public HelloWorldServiceImpl() {//用来判断是否创建出该对象
System.out.println("我被实例化了");
}
@Override
public void hello() {
helloWorldDao.hello();
}
}
beanFactory
@Test
void main() {
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//通过bean的字节码,去找到bean(不推荐)
//HelloWorldService hws = beanFactory.getBean("helloWorldService",HelloWorldService.class);
}
结果
applicationContext
@Test
void main2() {
ApplicationContext atx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
结果
发现我们获取bean,但是bean已经创建了,这就是没有延迟初始化。
aop
准备工作
applicationContext.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">
<bean class="serviceImpl.UserServiceImpl" id="userService">
<property name="userDao">
<bean class="daoImpl.UserDaoImpl" id="userDao"/>
</property>
</bean>
</beans>
测试能否跑通
基础知识
代理模式
静态代理
引入静态代理类
public class StaticProxy implements UserService{
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
@Override
public void addUser() {
begin();
try {
userService.addUser();
commit();
} catch (Exception e) {
rollback();
}
}
}
UserServiceImpl
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser() {
userDao.addUser();
}
}
applicationContxt.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">
<bean class="transaction.StaticProxy" id="staticProxy">
<property name="userService" ref="userService"/>
</bean>
<bean class="serviceImpl.UserServiceImpl" id="userService">
<property name="userDao">
<bean class="daoImpl.UserDaoImpl" id="userDao"/>
</property>
</bean>
</beans>
测试
可以看到实例的实际类型为StaticProxy
静态代理面临的问题
优点:可以看到我们具体业务(UserServiceImpl)中没有大量的事务相关代码,以及tryCatch,看起来相当清爽
缺点
1、我们可以看到,静态代理直接实现一个接口,如果接口里面增加或者删除方法,那么我的代理类也需要做相应操作
2、我们可以看到,在静态代理类中有实际的业务实现类对象,也就是说,我们需要为每一个具体的业务实现类,写静态代理类,任务量大。
jdk动态代理
public class JdkProxy implements InvocationHandler{
private Object target;
private StaticProxy staticProxy;
public void setStaticProxy(StaticProxy staticProxy) {
this.staticProxy = staticProxy;
}
public void setTarget(Object target) {
this.target = target;
}
public Object getInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
staticProxy.begin();
try {
result = method.invoke(target, args);
staticProxy.commit();
} catch (Exception e) {
staticProxy.rollback();
}
return result;
}
}
applicationContxt.xm配置
<?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 class="transaction.JdkProxy" id="jdkProxy">
<property name="staticProxy" ref="staticProxy"/>
<property name="target" ref="userService"/>
</bean>
<bean class="transaction.StaticProxy" id="staticProxy">
<property name="userService" ref="userService"/>
</bean>
<bean class="serviceImpl.UserServiceImpl" id="userService">
<property name="userDao">
<bean class="daoImpl.UserDaoImpl" id="userDao"/>
</property>
</bean>
</beans>
测试:
我们可以看到已经使用jdk动态代理了
CGlib动态代理
public class CglibProxy implements InvocationHandler{
private Object target;
private StaticProxy staticProxy;
public void setStaticProxy(StaticProxy staticProxy) {
this.staticProxy = staticProxy;
}
public void setTarget(Object target) {
this.target = target;
}
public Object getInstance() {
return Enhancer.create(target.getClass(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
staticProxy.begin();
try {
result = method.invoke(target, args);
staticProxy.commit();
} catch (Exception e) {
staticProxy.rollback();
}
return result;
}
}
applicationContxt.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">
<bean class="transaction.CglibProxy" id="cglibProxy">
<property name="staticProxy" ref="staticProxy"/>
<property name="target" ref="userService"/>
</bean>
<bean class="transaction.StaticProxy" id="staticProxy">
<property name="userService" ref="userService"/>
</bean>
<bean class="serviceImpl.UserServiceImpl" id="userService">
<property name="userDao">
<bean class="daoImpl.UserDaoImpl" id="userDao"/>
</property>
</bean>
</beans>
测试
什么是aop
aop(面向切面变编程)原理其实就是动态代理,只不过我们发现,我们使用动态代理,会把真实对象的所有方法做增强。使用aop可以对某一个方法做增强
有aop之后
修改各个类
applicationContxt.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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean class="serviceImpl.UserServiceImpl" id="userService">
<property name="userDao">
<bean class="daoImpl.UserDaoImpl"/>
</property>
</bean>
<bean class="transaction.StaticProxy" id="staticProxy"/>
<aop:config>
<aop:aspect ref="staticProxy"><!-- what -->
<aop:pointcut expression="execution(* serviceImpl.UserServiceImpl.addUser(..))" id="prf"/> <!-- where -->
<aop:before method="begin" pointcut-ref="prf"/><!-- when -->
<aop:after-returning method="commit" pointcut-ref="prf"/>
<aop:after-throwing method="rollback" pointcut-ref="prf"/>
</aop:aspect>
</aop:config>
</beans>
对于上述我们只对addUser()进行了增强
结果
从上述真实类型我们可以得到使用了jdk动态代理
现在使用updateUser()看是否增强
我们可以看到updateUser()这个方法并未被增强,这也就解决了我们开头的问题,aop可以解决只对某一个方法进行增强。