转:Spring5最新完整教程IDEA版通俗易懂
文档笔记是根据B站狂神视频来的。笔记中融入了我一些简单看法,笔记来源狂神微信公众号。
1.IOC注册对象的方式
1.无参构造方法(默认)
实体类默认有无参构造方法。如果是有参会报错
1.User.java
public class User { private String name; public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }
当实体类存在时,即可通过无参构造创建对象
2.beans.xml
xml文件在resources文件夹下。且此文件夹为根目录
id="user":用于获取对象时的绑定,否则怎么知道获取哪个
<?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 id="user" class="com.kuang.pojo.User"> <property name="name" value="kuangshen"/> </bean> </beans>
3.测试类
@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //在执行getBean的时候, user已经创建好了 , 通过无参构造 User user = (User) context.getBean("user"); //调用对象的方法 . user.show(); }
2.有参构造
实体类在无参情况多了有参构造方法,但是注册bean的方法有三种。
1.User.java
public class UserT { private String name; public UserT(String name) { this.name = name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }
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的方式--> </beans>
1.下标赋值
假如只有一个属性参数,那么它的下标就是0
<!-- 第一种根据index参数下标设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="kuangshen2"/> </bean>
2.参数类型创建<不建议>
<!-- 第三种根据参数类型设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>
3.参数名创建<主要>
<!-- 第二种根据参数名字设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean>
4.测试
@Test public void testT(){ //可以读取多个xml ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserT user = (UserT) context.getBean("userT"); user.show(); }
结论:在配置文件加载的时候。其中管理的对象都已经初始化了!
3.总结
对象在注册时候就被实例化了,而且只有一个实例,默认单例模式
也就是说,虽然我们获取的是不同对象,但是内存中只有一个
2.spring相关配置
1.bean别名
通过bean的id对bean起别名,可以起多个,相当于在获取对象时,就可以通过这个新的id获取。
bean:
<bean id="userT" class="com.kuang.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean>
别名
<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="userT" alias="userNew"/>
2.bean配置
<!--bean就是java对象,由Spring创建和管理--> <!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开<首选用此别名> 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名 --> <bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello" name="user1,user2"> <property name="name" value="Spring"/> </bean>
3.import
用于团队开发,整合多人的不同xml任务到一起,所以我们创建一个总的xml文件在导入其他人的。
<!--示例:--> <import resource="{path}/beans.xml"/> <!--应用:--> <import resource="beans1.xml"/> <import resource="beans2.xml"/> <import resource="beans3.xml"/>
3.依赖注入
-
依赖注入(Dependency Injection,DI)。
-
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
-
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
-
这些不同的注入方式可以都在一个bean下进行
1.构造器注入
就是有参和无参构造的注入
2.Set方式注入(重要)
注入条件:
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 ,
如果属性是boolean类型 , 没有set方法 , 是 is .
测试pojo类:
创建两个实体类
Address.java
public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
Student.java
package com.kuang.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setWife(String wife) { this.wife = wife; } public void setInfo(Properties info) { this.info = info; } public void show(){ System.out.println("name="+ name + ",address="+ address.getAddress() + ",books=" ); for (String book:books){ System.out.print("<<"+book+">>\t"); } System.out.println("\n爱好:"+hobbys); System.out.println("card:"+card); System.out.println("games:"+games); System.out.println("wife:"+wife); System.out.println("info:"+info); } }
1.常量注入
<bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> </bean>
测试
@Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.getName()); }
2.Bean注入
注意点:这里的值是一个引用,ref
说白了就是将一个bean注入到这个bean中,类似C引用
<bean id="addr" class="com.kuang.pojo.Address"> <property name="address" value="重庆"/> </bean> <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> </bean>
3.数组注入
<bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property> </bean>
4.List注入
<property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>爬山</value> </list> </property>
5.Map注入
<property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property>
6.set注入
<property name="games"> <set> <value>LOL</value> <value>BOB</value> <value>COC</value> </set> </property>
7.Null注入
<property name="wife"><null/></property>
8.Properties注入
<property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>
3.扩展方式注入
User.java :【注意:这里没有有参构造器!】
public class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
对于P或C约束/注入需要加命名空间
1、P命名空间注入
需要在头文件中加入约束文件,可以直接注入属性值
导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
2、c 命名空间注入
需要在头文件中加入约束文件,需要有有参无参构造
用到了构造器注入
导入约束 : xmlns:c="http://www.springframework.org/schema/c" <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
3.测试
@Test public void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); System.out.println(user); }
4.bean作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
1.Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.****.service.ServiceImpl" scope="singleton">
测试
@Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User user2 = (User) context.getBean("user"); System.out.println(user==user2); }
2.Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
3.Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.****.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
4.Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
4.自动装配
-
自动装配是使用spring满足bean依赖的一种方法
-
spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制,分别是:
-
在xml中显式配置;
-
在java中显式配置;
-
隐式的bean发现机制和自动装配。(重要)
这里我们主要讲第三种:自动化的装配bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
-
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
-
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .
1.环境搭建
此时只是做一个基本的搭建,但不是基础搭建。
新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat { public void shout() { System.out.println("miao~"); } }
public class Dog { public void shout() { System.out.println("wang~"); } }
建立一个用户类User,前两个实体类可以属于用户类,记得加上set方法等
public class User { private Cat cat; private Dog dog; private String str; }
编写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 id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="str" value="qinjiang"/> </bean> </beans>
测试
public class MyTest { @Test public void testMethodAutowire() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.getCat().shout(); user.getDog().shout(); } }
结果正常输出,环境OK,上面没有用到自动装配。
之所以用两个类属于一个类,是希望注册对应的bean以后,不用单独注入值,而是通过一定方法让其匹配,从而获取到对象。
2.byName
autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire="byName"
<bean id="user" class="com.kuang.pojo.User" autowire="byName"> <property name="str" value="qinjiang"/> </bean>
2、再次测试,结果依旧成功输出!
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
byName:换句话就是set后的单词被隔离出来作为id匹配。比如setcat被独立的id就是cat,在set方法中。
小结:
当一个bean节点带有 autowire byName的属性时。
-
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
-
去spring容器中寻找是否有此字符串名称id的对象。
-
如果有,就取出注入;如果没有,就报空指针异常。
3.byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。bean的class唯一就行。就是那一串
class="com.kuang.pojo.User"
byname是bean的id唯一
id="user"
NoUniqueBeanDefinitionException
测试:
1、将user的bean配置修改一下 : autowire="byType"
2、测试,正常输出
3、在注册一个cat 的bean对象!
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byType"> <property name="str" value="qinjiang"/> </bean>
4、测试,报错:NoUniqueBeanDefinitionException
5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
4.注解自动装配<重点>
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/beans/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/beans/spring-aop.xsd"> <context:annotation-config/> </beans>
2、开启属性注解支持!
直接加在文件头后面
<context:annotation-config/>
@Autowired
-
@Autowired是按类型自动转配的,不支持id匹配。所以在多个相同类型时候会无法匹配
-
需要导入 spring-aop的包!<文件头中加上就行>
直接在属性或set方法上运用
不再编写set方法,但是需要注册了bean.
作用:之前有通过名字或者类型的自动装配,将人中的猫狗作为属性装配到容器。但是我们也要去xml文件配置,使用注解避免了配置xml,只需要在人的猫狗属性前加就代表装配进人的bean里了。
1、将User类中的set方法去掉,使用@Autowired注解
public class User { @Autowired private Cat cat; @Autowired private Dog dog; private String str; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getStr() { return str; } }
2、此时配置文件内容
<context:annotation-config/> <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>
【小狂神科普时间】
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;
@Nullable 字段标记以后,则此字段可以为空
public People(@Nullable Sreing name){ this.name=name; }
当多个相同类型时候,可以组合@Qualifier解决此问题
@Qualifier
-
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
-
@Qualifier不能单独使用。
测试实验步骤:
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.kuang.pojo.Dog"/> <bean id="dog2" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/>
2、没有加Qualifier测试,直接报错
3、在属性上添加Qualifier注解
@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog;
测试,成功输出!
@Autowired @Qualifier(value = "cat2") //配套使用,value通过绑定id定位
@Resource
-
@Resource如有指定的name属性,先按该属性进行byName方式查找装配;
-
其次再进行默认的byName方式进行装配;
-
如果以上都不成功,则按byType的方式自动装配。
-
都不成功,则报异常。
-
java的注解
实体类:
public class User { //如果允许对象为null,设置required = false,默认为true @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; private String str; }
beans.xml
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>
测试:结果OK
配置文件2:beans.xml , 删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类上只保留注解
@Resource private Cat cat; @Resource private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
小结
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
5.注解开发(半注解)
在spring4之后,想要使用注解形式,必须得要引入aop的包
1.bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解装配!
1、配置扫描哪些包下的注解
<!--指定注解扫描包--> <context:component-scan base-package="com.kuang.pojo"/>
2、在指定包下编写类,增加注解
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> //此时由于没有id了,所以默认以类名的小写为id public class User { public String name = "秦疆"; }
3、测试
@Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); //此处强转,也可以通过getBean("user",User.class)实现 User user = (User) applicationContext.getBean("user"); System.out.println(user.name); }
2.属性注入
使用注解注入属性
1、可以不用提供set方法,直接在直接名上添加@value("值")
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { @Value("秦疆") // 相当于配置文件中 <property name="name" value="秦疆"/> public String name; }
2、如果提供了set方法,在set方法上添加@value("值");
@Component("user") public class User { public String name; @Value("秦疆") public void setName(String name) { this.name = name; } }
注意:一般来说可以通过注解注入属性值,但如果是List数组等九不再适合,复杂的通过xml注入
3.衍生注解
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
-
@Controller:web层
-
@Service:service层
-
@Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
4.自动装配
在Bean的自动装配已经讲过了,可以回顾
5.作用域
@scope
之前在xml里面是有单例多例模式的,注解也有
-
singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
-
prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user") @Scope("prototype") public class User { @Value("秦疆") public String name; }
6.小结
XML与注解比较
-
XML可以适用任何场景 ,结构清晰,维护方便
-
注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
-
xml管理Bean
-
注解完成属性注入
-
使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
-
进行注解驱动注册,从而使注解生效
-
用于**那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
-
如果不扫描包,就需要手动配置bean
-
如果不加注解驱动,则注入的值为null!
6.java类配置(纯注解配置)
纯java注解开发,一点xml都不用
为了分层管理,我们也会创建一个包专门存放配置类
只需要加一个注解就能变成配置类,类似beans<bean的集合>
注解bean==bean
1、实体类User.java
//导入相应的包 //此类注册到容器中 @Component public class User{ private String name; public String getName(){ return name; } //属性注入值,在set方法这里 @Value("值") public void setName(String name){ this.name=name; } @Override public String toString(){ return "User{" + "name='" +name +'\'' + '}'; } }
2、配置类Config.java
//也是注册到容器,本质也是基于@Component //@Configuration表示这是一个配置类,等价于beans.xml,实体类的@Component是xml的beans @Configuration //显示扫描包 @ComponentScan("com.kuang") //引入其他配置类 @Import(Config2.class) public class Config{ //注册bean,相当于之前的bean //方法名==bean的id @bean public User getUser(){ return new User(); //返回要注入的对象 } }
3、测试类
public class MyTest{ public static void main(String[] args){ //通过配置类的class对象加载 ApplicationContext context = new AnnotationConfigApplicationContext(Config.class) User getUser = (User) context.getBean("getUser"); System.out.print(getUser.getName); } }
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
7.动/静态代理模式
代理模式是SpringAOP的底层
1.静态代理
1.租房例子
静态代理角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作 .
静态代理角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作 .
静态代理角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作 .
代码实现
抽象角色:Rent.java
//抽象角色:租房 public interface Rent { public void rent(); }
真实角色:Host.java
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
代理角色:Proxy.java
//代理角色:中介 public class Proxy implements Rent { private Host host; public Proxy() { } //客户传来一个服务于客户的真实类 //通过这个真实类,我们就得到了他的方法 //最后整合这些方法 public Proxy(Host host) { this.host = host; } //租房 public void rent(){ seeHouse(); host.rent(); fare(); } //看房,附属操作 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费,附属操作 public void fare(){ System.out.println("收中介费"); } }
客户:Client.java
//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //中介帮助房东,将真实角色传过去代理 Proxy proxy = new Proxy(host); //你去找中介! proxy.rent(); } }
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
静态代理的好处:
-
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
-
公共的业务由代理来完成 . 实现了业务的分工 ,
-
公共业务发生扩展时变得更加集中和方便 .
缺点 :
-
类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
2.日志功能
需求:我们需要了解用到了什么方法。控制台输出具体的方法名。
实现思路:
1.抽象类:包含我们业务的操作,增删改查,通常我们可以理解为代理和被代理的共同目标
2.真实类:抽象类的实现,同时也是我们的被代理对象
3.代理类:切入的对象
具体实现
1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务 public interface UserService { void add(); void delete(); void update(); void query(); }
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人 public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户"); } public void delete() { System.out.println("删除了一个用户"); } public void update() { System.out.println("更新了一个用户"); } public void query() { System.out.println("查询了一个用户"); } }
3、需求来了,现在我们需要增加一个日志功能,怎么实现!
-
思路1 :在实现类上增加代码 【麻烦!】
-
思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
4、设置一个代理类来处理日志!代理角色
/代理角色,在这里面增加日志的实现 public class UserServiceProxy implements UserService { private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("执行了"+msg+"方法"); } }
5、测试访问类:
public class Client { public static void main(String[] args) { //真实业务 UserServiceImpl userService = new UserServiceImpl(); //代理类 UserServiceProxy proxy = new UserServiceProxy(); //使用代理类实现日志功能! proxy.setUserService(userService); proxy.add(); } }
理解:
静态代理,其实就是将对象的使用率。
要明白,代理类方面,我们new了一个对象,代理类内部创建了一个空的对象,此对象是真实对象的引用,这个空的对象才是真正代理发挥作用的对象,因为new的代理对象传入了一个需要被代理对象的地址。所以原来的空对象可以通过set方法将空代理对象指向传来的被代理对象地址。这样的话代理空对象摇身一变可以直接调用真实对象的方法,也就是原本的业务,我们需要基于业务开发。这么说似乎没什么用,但是我们的目的在于基于业务,高于业务,多出来的部分就是代理加入的。
2.动态代理
-
动态代理的角色和静态代理的一样 .
-
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
-
-
基于接口的动态代理----JDK动态代理
-
基于类的动态代理--cglib
-
现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
-
我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
-
JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
Proxy 生成代理对象。
【InvocationHandler:调用处理程序】
InvocationHandler是一个接口,通过代理实例的名为“调用处理程序"的程序实现。
每个代理实例都有关联的“调用处理程序"。当我们通过代理实例去调用方法时,该方法调用被进行编码且分派到对应的“调用处理程序"的invoke方法。
初步理解:invoke方法是一个方法中间载体。供应调用执行。
0.原理分析
动态代理和静态代理有些相似,都是将真实对象(方法的主人)的地址set给空值对象引用。
不同点在于,set进去的真实对象地址会被运用,最终动态生成一个动态代理类对象,
为什么就得需要动态的代理对象呢?我们也可以每次都调用原来静态代理类实现的啊。
原因
我们的目的不是生成代理类,而是对业务进行加工处理,不同类大的业务都不相同,方法名也是不同的,因此,静态代理无法调用到真实的方法,因为静态代理调用的方法都是写死的。
我们的改造目的在于真实方法也可以作为公共方法的参数,那么代理类对象实现的同时,方法也是动态的。
所以,需要分层。
一部分:生成代理类对象
另一部分:执行方法<可以在执行方法中加入前后缀方法,比如日志>
生成代理类对象和静态差不多,也是set,只不过拿到真实对象的地址后,我们需要将这个地址交付给一段程序执行生成代理对象<多此一举的原因在于执行方法片段要受控于代理对象>
我们传入了一个真实对象的地址<唯一参数>
//生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }
执行方法,我们会通过处理代理程序<自己实现>调用
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前缀 seeHouse(); //执行方法 //核心:本质利用反射实现! Object result = method.invoke(rent, args); //后缀 fare(); return result; }
1.租房例子
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色
//抽象角色:租房 public interface Rent { public void rent(); }
Host . java 即真实角色
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }
测试:
Client . java
//租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
2.动态代理万能用例
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
我们还可以将前缀和后缀剥离执行代码区。
public class ProxyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); } }
测试
public class Test { public static void main(String[] args) { //真实对象 UserServiceImpl userService = new UserServiceImpl(); //代理对象的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); //设置要代理的对象 UserService proxy = (UserService)pih.getProxy(); //动态生成代理类! proxy.delete(); } }
3.动态代理优点
静态代理有的它都有,静态代理没有的,它也有!
-
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
-
公共的业务由代理来完成 . 实现了业务的分工 ,
-
公共业务发生扩展时变得更加集中和方便 .
-
一个动态代理 , 一般代理某一类业务
-
一个动态代理可以代理多个类,代理的是接口!
8.AOP学习
相关介绍:
AOP的目标是多出来的业务方法,而不是原本的,因此在思维上偏向”多余“方法。
比如相关的定义:
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知 Log类
(2)Advice(通知):AOP在特定的切入点上执行的增强处理,有 before,after,afterReturning,afterThrowing,around Log类中方法
(3)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
0.Spring实现AOP前提
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
2.通过 Spring API 实现
首先编写我们的业务接口和实现类
public interface UserService { public void add(); public void delete(); public void update(); public void search(); }
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强。
至于方法怎么匹配都是spring解决的
public class Log implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } }
public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .
<?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.xsd"> <!--注册bean--> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <bean id="log" class="com.kuang.log.Log"/> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
测试
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
个人理解
在xml中,你只需要告诉spring,你写好的增强类,以及你想增强的作用范围就行了,spring会自动帮我们匹配执行。相当于调用方法。
3.自定义类
1.Spring Aspectj切入点语法定义
在使用spring框架配置AOP的时候,pointcut``"切入点"
例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
``整个表达式可以分为五个部分:
``1、execution(): 表达式主体。
``2、第一个*号:表示返回类型,*号表示所有的类型。
``3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
``4、第二个*号:表示类名,*号表示所有的类。
``5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
2.实现
自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
第一步 : 写我们自己的一个切入类
public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }
去spring中配置
<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.kuang.config.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>
测试:
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); } }
spring API实现的没有对增强部分分离,规则太多
才有了自定义的方式,自定义的基于aop标签
4.注解实现
和xml思想没啥区别
只不过翻译为注解,和之前的注解装配差不多思想
也有aop标签的代替
第一步:编写一个注解实现的增强类
package com.kuang.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") 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); } }
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
实现动态代理的方式,spring默认jdk
通过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动态代理。