个人java学习路线-Spring
一、Spring介绍
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架
Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版本
Rod Johnson :Spring Framework创始人
spring理念:使现有的技术更加容易使用,本身是一个大杂烩
Spring 融合件
优点:
Spring是一个开源的免费的框架(容器)!
Spring是一个轻量级,非入侵式的框架
控制反转(IoC) 面向切面(AOP)
支持事务的处理,对框架整合的支持!
缺点:
发展了太长时间,违背了原来的理念你!配置十分繁琐。人称:配置地狱
总结:Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架
拓展
Spring Boot
一个快速开发的脚手架
基于Spring Boot可以快速的开发单个微服务
约定大于配置
Spring Cloud
基于Spring Boot实现的
学习Spring Boot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用
二、IoC
原理
控制反转:
public class UserServiceImpl implements UserService {
private UserDao userDao;
//使用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
之前,程序是主动创建对象!控制权在程序员手上!
使用set注入后,程序不在具有主动性,而是变成了被动的接受对象!
这种思想,从本质上解决了问题,我们程序员不用去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上!这时IOC的原型!
ps:个人总结,通过set方法注入,只需在调用ServiceImpl方法时new一个dao层的方法即可代替之前重复创建Service和ServiceImpl的大量重建
额外:
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从loc容器中取出需要的对象
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法
控制反转是一种通过从描述(XML或注解)并通过第三方去生产或获取特定对象的方法。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)
所谓IoC,对象由Spring来创建,管理,装配!
实例
DAO:
public interface UserDao {
void getUser();
}
public class UserDaoImpl implements UserDao{
public void getUser() {
System.out.println("UserDao执行");
}
}
public class UserDaoMysqlImpl implements UserDao{
public void getUser() {
System.out.println("Mysql处理");
}
}
Service:
public interface UserService {
void getUser();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
//使用set进行动态实现值的注入
//可见即使有两个DaoImpl,ServiceImpl通过set注入,一个即可
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
在resource中配置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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mysqlImpl" class="com.person.dao.UserDaoMysqlImpl" />
<bean id="impl" class="com.person.dao.UserDaoImpl" />
<bean id="UserServiceImpl" class="com.person.service.UserServiceImpl">
<!--
value:具体的值,基本数据类型
ref:引用Spring容器中创建好的对象
-->
<property name="userDao" ref="mysqlImpl" />
</bean>
</beans>
这里ref为mysqlImpl,绑定的就是UserDaoMysqlImpl
测试代码:
@Test
public void getUser() {
//获取ApplicationContext; 拿到Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//有了容器,需要什么可以直接get
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
这里执行后输出就为“Mysql处理”
这里ref改为impl,测试代码不变
输出为“UserDao执行”
例二(Spring注入类属性)略
类:
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
使用Spring来创建对象,在Spring这些都称为Bean
bean=对象 new Hello();
-->
<bean id="hello" class="com.person.pojo.Hello">
<property name="str" value="hello" />
</bean>
</beans>
这里的value就是hello的str属性
测试代码:
public class HelloTest {
@Test
public void MyTest() {
//获取Spring的上下文对象
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//我们的对象都在Spring中的管理了,我们要使用直接去里面取出来就可以了
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
}
输出:hello
输出的就是beans.xml的值
三、IoC创建对象的方式,原理
先来两个实体类:
public class User {
private String name;
public User() {
System.out.println("User的无参构造!");
}
public User(String name) {
this.name=name;
System.out.println(name+"的有参构造!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "name=" + name ;
}
}
public class UserT {
private String name;
public UserT() {
System.out.println("UserT的无参构造!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "name=" + name ;
}
}
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--后面不同方法需要的xml-->
</beans>
1.默认使用无参构造
beans.xml创建对象
<bean id="user" class="com.person.pojo.User" >
<!--无参构造-->
<property name="name" value="忧郁蓝" />
</bean>
<bean id="userT" class="com.person.pojo.UserT">
</bean>
测试代码:
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user=(User) context.getBean("user");
System.out.println(user);
}
/*输出:
User的无参构造! -创建对象无参构造自动运行
UserT的无参构造!
name=忧郁蓝 - 默认注入值成功---依赖于set注入
*/
@Test
public void testT() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT usert=(UserT) context.getBean("userT");
System.out.println(usert);
}
/*
输出:
User的无参构造!
UserT的无参构造!
name=null 默认没有注入
*/
2.有参构造——下标赋值
beans.xml配置
<beanid="user" class="com.person.pojo.User" >
<!-- 有参下标赋值-->
<constructor-arg index="0" value="勇者无敌" />
</bean>
测试代码:
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user=(User) context.getBean("user");
System.out.println(user);
}
/*输出:
勇者无敌的有参构造!
name=勇者无敌
*/
3.有参构造——类型赋值(不推荐)
只改bean就行,测试代码不变
<bean id="user" class="com.person.pojo.User" >
<constructor-arg type="java.lang.String" value="第二种方式类型赋值" /><!--有参类型赋值(不推荐使用)-->
</bean>
<!--输出:
第二种方式类型赋值的有参构造!
name=第二种方式类型赋值
-->
4.有参构造——直接通过参数名来设置
<beanid="user" class="com.person.pojo.User" >
<constructor-arg name="name" value="第三种方式" /><!--直接通过参数名来设置-->
</bean>
<!--输出:
第三种方式的有参构造!
name=第三种方式
-->
总结
IoC创建对象的方式:
1.使用无参构造创建对象,默认!
beans.xml配置:
<bean id="user" class="com.User" >
<property name="name" value="无敌溜溜球" />
</bean>
实例化:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user=(User) context.getBean("user");
2.假设我们要使用有参构造创建对象
第一种方式:下标赋值
实体类:
public User(String name) {
this.name=name;
System.out.println(name+"的有参构造!");
}
beans.xml配置
<bean id="user" class="com.User" >
<constructor-arg index="0" value="勇者无敌" />
</bean>
实例化同上
第二种方式:类型赋值(不推荐使用)
beans.xml配置
<bean id="user" class="com.User" >
<constructor-arg type="java.lang.String" value="第二种方式类型赋值" />
</bean>
第三种方式:直接通过参数名来设置
beans.xml配置
<bean id="user" class="com.User" >
<constructor-arg name="name" value="第三种方式" />
</bean>
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!
从spring容器中取出的对象都是同一个
四、Spring配置
实体类:
public class UserT {
private String name;
public UserT() {
System.out.println("UserT的无参构造!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "name=" + name ;
}
}
别名
beans.xml配置
<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="userT" alias="abc" />
<bean id="userT" class="com.person.pojo.UserT" >
<property name="name" value="UserT测试" />
</bean>
测试代码
@Test
public void testT() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT usert=(UserT) context.getBean("abc");
System.out.println(usert);
}
/*输出:
UserT的无参构造!
name=UserT测试
*/
Bean的配置
beans.xml
<!--
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的全限定名:包名+类型
name:也是别名,而且name可以同时取多个别名,逗号,分号和空格等都可以分割别名
scope:作用域
-->
<bean id="userT" class="com.person.pojo.UserT" name="userT2,u2 u3;u4">
<property name="name" value="UserT测试" />
</bean>
测试代码
@Test
public void testT() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT usert=(UserT) context.getBean("u4");
System.out.println(usert);
}
/*输出:
UserT的无参构造!
name=UserT测试
*/
import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个
在beans.xml同级目录下创建beans2.xml,beans3.xml,
然后创建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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml" />
<import resource="beans2.xml" />
<import resource="beans3.xml" />
</beans>
测试代码也可以正常运行
@Test
public void testT2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserT usert=(UserT) context.getBean("u4");
System.out.println(usert);
}
五、DI依赖注入
构造器注入方法上面已经说过了,这里看看其他注入
Set方式注入【重点】
- 依赖注入:Set注入!
-- 依赖:bean对象的创建依赖于容器
-- 注入:bean对象中的所有属性,由容器来注入!
看看复杂类型的对象注入
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
}
//set,get,toString方法略
public class Address {
private String address;
}
//set,get,toString方法略
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.person.pojo.Student">
<!--普通注入,value-->
<property name="name" value="范进" />
<!--bean注入,ref-->
<property name="address" ref="address" />
<!--数组-->
<property name="books">
<array>
<value>《山海经》</value>
<value>《道德经》</value>
<value>《周易》</value>
</array>
</property>
<!--list-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>睡觉</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="1号" value="许德拉" />
<entry key="2号" value="芬里尔" />
<entry key="3号" value="耶梦加得" />
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>群星</value>
<value>dark soul</value>
<value>死神</value>
</set>
</property>
<!--null-->
<property name="wife">
<null />
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="身份">无名之人</prop>
<prop key="饰品">无</prop>
<prop key="携带物">粪球</prop>
<prop key="衣物">破烂的衣物</prop>
</props>
</property>
</bean>
<bean id="address" class="com.person.pojo.Address">
<property name="address" value="无尽虚空"/>
</bean>
</beans>
测试还是一样
其它方式/拓展方式注入
p命名空间约束和c命名空间约束
实体类:
public class User {
private String name;
private int age;
}//set,get,toString方法略
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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
需要在xml头部加入下面这行
xmlns:p="http://www.springframework.org/schema/p"
后面也是
-->
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.person.pojo.User" p:name="赵无敌" p:age="17" />
<!--c命名空间注入,可以通过构造器注入属性的值:construct-args-->
<bean id="user2" class="com.person.pojo.User" c:age="18" c:name="尼德霍格" />
</beans>
测试代码和上面一样
ps:必须要有有参构造器
六、bean的作用域
1.单例模式(Spring默认)——singleton
<bean id="user2" class="com.User" c:age="18" c:name="尼德霍格" scope="singleton" />
ps:个人小结,singleton模式下在同一次会话ApplicationContext中通过 context.getBean("user2");创建出来的对象都是一个
2.原型模式——prototype
<bean id="user2" class="com.User" c:age="18" c:name="尼德霍格" scope="prototype" />
ps:prototype模式下创建出来的对象都是单独的(新的)
3.其余的request,session,application,这些只能在web开发中使用到
beans.xml
<!--c命名空间注入,可以通过构造器注入属性的值:construct-args-->
<bean id="user2" class="com.person.pojo.User" c:age="18" c:name="尼德霍格" scope="prototype" />
测试代码:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Object user = context.getBean("user2");
Object user2 = context.getBean("user2");
System.out.println(user);
System.out.println(user==user2);
}
输出:
User{name=‘尼德霍格’, age=18}
false
如果去掉beans.xml的scope=“prototype”,则默认为单例模式,输出就是:
User{name=‘尼德霍格’, age=18}
true
七、bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式!
- Spring会在上下文中中自动寻找,并自动给bean装配属性!
- Spring中有三种装配的方式
1.在xml中显示的配置
2.在java中显示配置
3.隐式的自动装配bean【重点】
ByName,ByType自动装配
三个实体类:
public class Cat {
public String bark() {
return "你是猫";
}
@Override
public String toString() {
return "Cat:" + bark();
}
}
public class Dog {
public String bark(){
return "你是狗";
}
@Override
public String toString() {
return "Dog:"+bark();
}
}
public class Person {
private Cat cat;
private Dog dog;
private String name;
@Override
public String toString() {
return "Person{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
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 ">
<bean id="cat" class="com.person.pojo.Cat" />
<bean id="dog" class="com.person.pojo.Dog" />
<!--
byName:会自动在容器上下文中查找和自己对象后面的值对应的beanId! 例:
<bean id="dog" class="com.person.pojo.Dog" />
setDog对应的bean id必须为dog
-->
<bean id="person" class="com.person.pojo.Person" p:name="头铁的公共" autowire="byName" />
<!--或者
ByType:会自动在容器上下文中查找和自己对象属性类型相同的bean! 例:
<bean id="dog22" class="com.person.pojo.Dog" />
setDog对应的bean class必须为Dog
<bean id="person" class="com.person.pojo.Person" p:name="头铁的公共" autowire="byType" />
-->
</beans>
测试代码
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person);
}
/*输出:
Person{cat=Cat:你是猫, dog=Dog:你是狗, name='头铁的公共'}
如果 <bean id="cat" class="com.person.pojo.Cat" />改为
<bean id="cat1" class="com.person.pojo.Cat" />
依旧使用autowire="byName"
那么输出:
Person{cat=null, dog=Dog:你是狗, name='头铁的公共'}
但如果改用autowire="byType"则输出还是上面的
*/
使用注解自动装配–@Autowired
-- 导入约束:context约束
-- 配置注解的支持: <context:annotation-config/>
<?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:annotation-config/>
</beans>
**@Autowired **
-- @Autowired 注解实体类,可以直接在属性上使用即可!也可以在set方式上使用!
使用@Autowired 我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在,且符合名字byname!
可见用了@Autowired 后连set方法都可以不用了
实例:
其他不变
Person类:
public class Person {
@Autowired
private Cat cat;
//如果显示定义了 @Autowired的required = false,说明这个对象可以为null,否则不允许为空
//用了@Autowired 后连set方法都可以不用了
@Autowired(required = false)
private Dog dog;
private String name;
@Override
public String toString() {
return "Person{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
beans.xml
<!--开启注解支持-->
<context:annotation-config/>
<bean id="cat" class="com.person.pojo.Cat" />
<!--因为dog注释:
@Autowired(required = false)
private Dog dog;
所以dog的bean也可以省略,只是输出会是null,
但上面的cat的bean不能省略,不然会报错
-->
<bean id="dog" class="com.person.pojo.Dog" />
<bean id="person" class="com.person.pojo.Person" p:name="山沟沟" />
测试输出:
Person{cat=Cat:你是猫, dog=Dog:你是狗, name=‘山沟沟’}
注解拓展
-
@Nullable 字段标记了这个注解,说明这个字段可以为null;
-
@Qualifier(value = “?”)
@Autowired @Qualifier(value = "dog22") private Dog dog; private String name; <bean id="dog" class="com.person.pojo.Dog" /> <bean id="dog22" class="com.person.pojo.Dog" />
-
@Resource注解:java自带的注解
ps:个人理解
@Resource注解无参时,当bean.xml中配置了单个Cat类时,采用的时byType自动装配,多个时采用的时byName自动装配,默认匹配id为cat的bean,否则报错 @Resource注解有参时, @Resource(name = "cat1") ,匹配id为name的值的bean @Resource private Cat cat; <bean id="cat1" class="com.person.pojo.Cat" /> <bean id="cat2" class="com.person.pojo.Cat" />
注解执行装配顺序等说明
-
@Autowired顺序: byType–》byName–》@Qualifier
-
@Qualifier没用之前@Autowired标签默认按byType自动装配,@Qualifier用之后按byName自动装配
-
@Qualifier标签给Dog附上ID:
@Autowired(required = false) @Qualifier(value = "dog22") private Dog dog;
-
bean Dog配置id和 @Qualifier标签ID一致:<bean id=“dog22” class=“com.person.pojo.Dog” />
八、使用注解开发
- 在Spring4之后,要使用注解开发,必须要保证aop的包导入了
- 使用注解需要导入context约束,增加注解的支持
注解说明
- @Autowired : 自动装配通过类型,名字
如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value=“xxx”) - @Nullable 字段标记了这个注解,说明这个字段可以为null
- @Resource : 自动装配通过名字,类型
- @Component : 组件,放在类上,说明这个类被Spring管理了,就是bean!
实例
实体类:
//等价于 <bean id="user" class="com.person.pojo.User" />
//Component:组件
@Component
@Scope("singleton")
public class User {
//相当于 <property name="name" value="Jerry" />
@Value("Jerry")
public String name;
}
beans.xml
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.person.pojo" />
<!--开启注解支持-->
<context:annotation-config/>
测试代码:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
System.out.println(user.name);
}
/*
输出:
Jerry
*/
纯Java的配置方式
在SpringBoot中随处可见!
实体类:
//这个注解表示这个类被Spring接管了(ps:只有说明/标记的用处,无实际意义)
@Component
public class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
//属性注入值
@Value("羲和")
public void setName(String name) {
this.name = name;
}
}
PersonConfig类:
//类似@Component,也会被Spring容器托管,代表一个配置了,相当于beans.xml
@Configuration
@ComponentScan("com.person.pojo")
@Import(PersonConfig2.class)//导入其它配置
public class PersonConfig {
//注册一个bean,相当于一个bean标签,这个方法的名字就相当于bean标签中的id属性,这个方法的返回值就相当于bean标签中的class属性
@Bean
public User getUser(){
return new User();//就是返回要注入到bean的对象!
}
}
测试代码:
@Test
public void test() {
//如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig 上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user);
}
//输出User{name='羲和'}
九、代理模式proxy
静态代理
抽象角色:一般会使用接口或者抽象类来解决
真实角色:被代理的角色
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
客户:访问代理对象的人!
步骤:
1.接口
2.真实角色
3.代理角色
4.客户访问代理角色
静态代理好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务就交给了代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理!
缺点: - 一个真实角色就会产生一个代理角色;代码量会翻倍–开发效率会变低
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理【暂时使用】
- 基于类:cglib
- java字节码实现:javasisit
需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)
动态代理好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务就交给了代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一个业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!
例一 —— 中介帮房东租房子
//租房
public interface Rent {
public void rent();
}
//房东要出租房子
public class Host {
public void rent(){
System.out.println("房东要出租房子!");
}
}
代理类Proxy:
//代理
public class Proxy implements Rent {
private Host host;
public Proxy(){
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
seeHouse();
host.rent();
contract();
fee();
}
public void seeHouse(){
System.out.println("看房");
}
public void fee(){
System.out.println("收中介费");
}
public void contract(){
System.out.println("签合同");
}
}
测试类:
public class Client {
public static void main(String[] args) {
Host host=new Host();
//代理,中介帮房东租房子,中介会有一些附属操作
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
/*
输出:
看房
房东要出租房子!
签合同
收中介费
*/
例二 —— 增删改查添加日志功能
public interface UserService {
void add();
void delete();
void update();
void query();
}
//真实对象
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("查询了一个用户");
}
}
代理UserServiceProxy类:
public class UserServiceProxy implements UserService{
private UserService userService;
public void setUserService(UserService 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+"方法");
}
}
测试类:
public class Client {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
UserServiceProxy proxy=new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
/*输出:
使用了add方法
增加了一个用户
*/
例三 —— InvocationHandler自动生成代理修改例一
//租房
public interface Rent {
public void rent();
}
//房东要出租房子
public class Host implements Rent{
public void rent(){
System.out.println("房东要出租房子!");
}
}
代理类ProxyInvocationHandler:
//用这个类,自动生成代理
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);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//动态代理的本质,就是使用反射机制
Object result = method.invoke(rent, args);
fee();
return result;
}
public void seeHouse(){
System.out.println("看房子");
}
public void fee(){
System.out.println("收中介费");
}
}
测试类:
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就是动态生成的,我们并没有写
proxy.rent();
}
}
/*
运行结果:
看房子
房东要出租房子!
收中介费
*/
例四 —— InvocationHandler类(通用类)修改例二
public interface UserService {
void add();
void delete();
void update();
void query();
}
//真实对象
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("查询了一个用户");
}
}
代理类ProxyInvocationHandler:
//用这个类,自动生成代理
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);
}
//处理代理实例,并返回结果
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 msg){
System.out.println("[log]执行了"+msg+"方法");
}
}
测试类:
public class Client {
public static void main(String[] args) {
//真实角色
UserService userService=new UserServiceImpl();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.add();
proxy.query();
proxy.update();
proxy.delete();
}
}
```java
public class Client {
public static void main(String[] args) {
//真实角色
UserService userService=new UserServiceImpl();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.add();
proxy.query();
proxy.update();
proxy.delete();
}
}
/*
输出:
[log]执行了add方法
增加了一个用户
[log]执行了query方法
查询了一个用户
[log]执行了update方法
修改了一个用户
[log]执行了delete方法
删除了一个用户
*/
十、AOP
- AOP:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切面关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切面关注点。如日志,安全,缓存,事务等等…
- 切面(ASPECT):横切关注点,被模块化的特殊对象。即,它是一个类
- 通知(Advice):切面必须要完成的工作。即它是类中的一个方法。
- 目标(Target):被通知目标
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的"地点"的定义
- 连接点(JointPoint):与切入点匹配的执行点
实现方法
方式一:使用Spring的API接口【主要是SpringAPI接口实现】
方式二;自定义来实现AOP【主要是切面定义】
方式三:使用注解实现!
不变的类
public interface UserService {
void add();
void delete();
void update();
void select();
}
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 select() {
System.out.println("查询了一个用户");
}
}
测试代码:
@Test
public void add() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的核心是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
方式一:使用原生Spring API接口
需要横切入的类
public class Log implements MethodBeforeAdvice {
/**
*
* @param method 要执行目标对象的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法");
}
}
//和
public class AfterLog implements AfterReturningAdvice {
/**
*
* @param returnValue 返回值
* @param method
* @param args
* @param target
* @throws Throwable
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法 返回结果为:"+returnValue);
}
}
applicationContext.xml配置
<!--注册bean-->
<bean id="userService" class="com.person.service.UserServiceImpl" />
<bean id="log" class="com.person.log.Log" />
<bean id="afterLog" class="com.person.log.AfterLog" />
<!--方式一:使用原生Spring API接口-->
<!--配置AOP:需要导入AOP约束-->
<aop:config>
<!--切入点 expression:表达式 execution(要执行的位置!)-->
<aop:pointcut id="pointcut" expression="execution(* com.person.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut" />
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" />
</aop:config>
输出:
执行了com.person.service.UserServiceImpl的add方法
增加了一个用户
执行了add方法 返回结果为:null
- 输出顺序和AfterLog,Log继承的类有关
- public class AfterLog implements AfterReturningAdvice{}
- public class Log implements MethodBeforeAdvice{}
方式二:自定义来实现AOP【主要是切面定义】
需要切入的类:
public class DiyPointCut {
public void before1(){
System.out.println("=======方法执行前==========");
}
public void after1(){
System.out.println("=======方法执行后==========");
}
}
applicationContext.xml:
<!--注册bean-->
<bean id="userService" class="com.person.service.UserServiceImpl" />
<!--方式二:自定义类-->
<bean id="diy" class="com.person.diy.DiyPointCut" />
<aop:config>
<!--自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.person.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before1" pointcut-ref="point"/>
<aop:after method="after1" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试输出:
=======方法执行前==========
增加了一个用户
=======方法执行后==========
方式三:使用注解实现!
切面:
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.person.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("======方法执行前=========");
}
@After("execution(* com.person.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("-------方法执行后----------");
}
//在环绕增强中,我们可用给定一个参数,代表我们要获取要处理切入的点
@Around("execution(* com.person.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature= jp.getSignature();//获得签名 可忽略
System.out.println("signature:"+signature);
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);//可忽略
}
}
applicationContext.xml:
<!--方式三-->
<!--注册bean-->
<bean id="userService" class="com.person.service.UserServiceImpl" />
<bean id="annotationPointCut" class="com.person.diy.AnnotationPointCut"/>
<!--
开启注解支持
JDK(默认) proxy-target-class="false"
cglib proxy-target-class="true"
-->
<aop:aspectj-autoproxy proxy-target-class="false" />
测试输出:
环绕前
signature:void com.person.service.UserService.add()
======方法执行前=========
增加了一个用户
-------方法执行后----------
环绕后
null