上篇博文中模拟了IOC的底层实现,接下来对spring的IOC和AOP部分进行总结。
1、首先看看spring框架是怎么替我们创建对象的,spring_ioc入门。
导入相关的jar包,然后copy下面的程序,放入指定的位置。就可以体验到spring的ioc服务。
User.java:
public class User {
public void test() {
System.out.println("test...");
}
}
log4j.properties:
#控制父类日志记录器的日志级别为info,默认所有模块下只输出info级别以上的日志
log4j.rootLogger=info,console
############# 日志输出到控制台 #############
#日志输出到控制台使用的api类
log4j.appender.console=org.apache.log4j.ConsoleAppender
#指定当前输出源的日志级别,有了前面的配置,就不需要配置该项了
#log4j.appender.console.Threshold = info
#指定日志输出的格式:灵活的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#具体格式的内容
log4j.appender.console.layout.ConversionPattern=%d %-2p [%c.%M()] - %m%n
ApplicationContext.xml:(图中没有展开,该文件可以放在src目录下,也可以放在其他的路径下,同样名字可以是其他,如bean.xml,而笔者为了方便管理,把该文件放入springConfig文件夹中)
<?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="h.l.pojo.User"></bean>
</beans>
UserTest.java:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class UserTest {
ApplicationContext context;
@BeforeEach
void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("springConfig/ApplicationContext.xml");
}
@Test
void test() {
User user = (User) context.getBean("user");
System.out.println(user);
user.test();
}
}
运行结果:
2018-11-25 10:28:18,864 INFO [org.springframework.context.support.ClassPathXmlApplicationContext.prepareRefresh()] - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@29b5cd00: startup date [Sun Nov 25 10:28:18 CST 2018]; root of context hierarchy
2018-11-25 10:28:18,943 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions()] - Loading XML bean definitions from class path resource [springConfig/ApplicationContext.xml]
h.l.pojo.User@61dd025
test...
可以发现,User对象的创建不用我们自己动手,spring框架会自动线dom4j解析ApplicationContext.xml文件,然后根据id找到对应的bean,得到该bean中的class属性,通过反射,创建出指定对象,好处是什么呢?class属性所指定的类路径,无论该类放哪个路径或者文件夹下,都可以通过更改class属性中的值,让框架准确帮你创建对象,解耦!(可以参考Spring_IOC底层实现模拟)
2、入门了spring_ioc后,下面总结spring中bean实例化的三种方式。
(1)使用类的无参构造创建:(下面的写法就是使用的是pojo类的无参方法实现bean实例化的,如果想测试的话,pojo类中带有参构造方法,不带无参构造方法即可发现,此时对象是无法创建的)
<bean id="user" class="h.l.pojo.User"></bean>
无法创建时的报错信息:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [springConfig/ApplicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [h.l.pojo.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: h.l.pojo.User.<init>()
此时解决方式,就是在pojo类中添加无参构造方法就可以了。
(2)使用静态工厂进行创建:
配置如下:
<bean id="userFactory" class="h.l.pojo.UserFactory" factory-method="getBean"></bean>
静态工厂如下:
public class UserFactory {
public static User getBean() {
return new User();
}
}
测试类如下:
@Test
void test() {
User user = (User) context.getBean("user");
System.out.println(user);
user.test();
}
(3)使用实例工厂进行创建:
配置如下:
<bean id="userFactory" class="h.l.pojo.UserFactory"></bean>
<bean id="user" factory-bean="userFactory" factory-method="getBean"></bean>
实例工厂如下:
public class UserFactory {
public User getBean() {
return new User();
}
}
测试如下:
@Test
void test() {
User user = (User) context.getBean("user");
System.out.println(user);
user.test();
}
ps:实际上开发过程中,我们更多的是使用第一种方法,即使用类的无参构造创建对象,配置bean也来得更加方便。
3、bean标签中的属性:
id属性:bean的标识,根据id得到配置对象。注意的是id值不能包含特殊属性。
class属性:创建对象所在类的全路径。
name属性:功能类似id,在name属性中可以包含特殊字符。
scope属性:
- singleton:默认值,单例的。
- prototype:多例的。
- request:web项目中,Spring创建一个Bean对象,将对象存入request域中。
- session:web项目中,Spring创建一个Bean对象,将对象存入session域中。
- globalSession:web项目中,Spring创建一个Bean对象,将对象存入globalSession中。
至于单例和多例的测试,仅仅是通过IOC容器创建两个对象,比较一下是否相等即可,单例肯定为true,多例为false。
<bean id="user" class="h.l.pojo.User" scope="prototype或者singleton,默认单例singleton"></bean>
4、属性的注入:
属性的注入方式有三种:
使用set方法注入、有参构造注入、使用接口注入
在Spring框架中,支持前两种方式
(1)有参构造注入
<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
<constructor-arg name="username" value="hello"></constructor-arg>
</bean>
public class PropertyDemo1 {
private String username;
public PropertyDemo1(String username) {
this.username = username;
}
@Override
public String toString() {
return "PropertyDemo1 [username=" + username + "]";
}
}
(2)set方法的注入
<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
<property name="username" value="world"></property>
</bean>
public class PropertyDemo1 {
private String username;
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "PropertyDemo1 [username=" + username + "]";
}
}
(3)使用set方法注入对象类型的属性:
<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void test() {
System.out.println("service...");
userDao.test();
}
}
public class UserDao {
public void test() {
System.out.println("Dao...");
}
}
class UserServiceTest {
ApplicationContext context;
@BeforeEach
void setUp() throws Exception {
context = new ClassPathXmlApplicationContext(
"springConfig/ApplicationContext.xml");
}
@Test
void test() {
UserService userService=(UserService)context.getBean("userService");
userService.test();
}
}
当然,也可以使用带参构造函数注入:
<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void test() {
System.out.println("service...");
userDao.test();
}
}
对应的地方作相应的修改即可,但是实际开发中使用set的方法注入会更多些,因为pojo类中有set方法,能有更多的灵活性。
(4)p名称空间注入:
public class Person {
private String username;
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "Person [username=" + username + "]";
}
}
通过上面的方式也能够实现属性的注入。如果属性中有对象类型属性,p名称空间注入即可按如下方式进行:
<bean id="user" class="h.l.pojo.User" p:username="I am ok"></bean>
<bean id="person" class="h.l.pojo4.Person" p:username="Is-Me-HL" p:user-ref="user"></bean>
除了p名称空间,还有c名称空间,用法类似,只是语法有些差异,请自行百度。
(5)复杂类型注入:
public class Person {
private String[] arrs;
private List<String>list;
private Map<String,String>map;
private Properties properties;
public void setArrs(String[] arrs) {
this.arrs = arrs;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void show() {
System.out.println("arrs:"+"=========="+arrs);
System.out.println("list:"+"=========="+list);
System.out.println("map:"+"=========="+map);
System.out.println("properties:"+"=========="+properties);
}
}
<bean id="person" class="h.l.pojo4.Person">
<property name="arrs">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="list">
<list>
<value>11</value>
<value>22</value>
<value>33</value>
</list>
</property>
<property name="map">
<map>
<entry key="1" value="11"></entry>
<entry key="2" value="22"></entry>
<entry key="3" value="33"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
<prop key="jdbc.url">jdbc:mysql://localhost:3306/XXX</prop>
<prop key="jdbc.user">root</prop>
<prop key="jdbc.password">root</prop>
</props>
</property>
</bean>
(6)IOC和DI的区别:
IOC:控制反转,把对象创建交给spring进行配置。
DI:依赖注入,向类里面的属性中设置值。
关系:依赖注入不能单独存在,需要在ioc基础之上完成。
5、spring注解开发:在核心jar包的基础上添加spring-aop-4.3.14.RELEASE.jar。
(1)创建对象的四个注解:
import org.springframework.stereotype.Component;
@Component(value="user")
public class User {
public void show() {
System.out.println("User...");
}
}
其余三个注解分别是@Controller(web层)、@Service(业务层)、@Repository(持久层),实际上这四个注解的功能都是一样的,目前来说。
(2)使用注解注入属性:
前提:配置注解扫描:
<?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/context/spring-context.xsd"> <!-- bean definitions here -->
<!-- 开启注解扫描 -->
<context:component-scan base-package="h.l.pojo"></context:component-scan>
</beans>
第一个注解:@Autowired
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {
@Autowired // 需要注意的是Autowired是根据类名找到对应类实现自动注入的,如找到下面的UserDao
private UserDao userDao;
public void test() {
System.out.println("service...");
userDao.test();
}
}
第二个注解:@Resource(name="")
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {
@Resource(name="userDao")
private UserDao userDao;
public void test() {
System.out.println("service...");
userDao.test();
}
}
6、AOP:面向切面编程,扩展功能不通过修改源代码实现。采用横向抽取机制,取代传统纵向继承体系重复性代码。
关于动态代理:请移步:JDK动态代理
AOP的常见操作术语:
- 连接点:类里面哪些方法可以被增强,这些方法称为增强点。
- 切入点:在类里面可以有很多的方法被增强,但实际上我们很有可能挑其中的几个方法增强,因此,实际增强的方法称为切入点。
- 通知/增强:增强的逻辑,称为增强,比如扩展日志功能,这个日志功能就称为增强,包括前置通知(在方法前执行),后置通知(在方法之后执行),异常通知(方法出现异常),最终通知(在后置之后执行),环绕通知(在方法之前和之后执行)。
- 切面:把增强应用到具体方法上,过程称为切面(即把增强用到切入点过程)。
关于AOP的demo如下:
先导入相关jar包
Book.java:
package h.l.pojo6;
public class Book {
public void add() {
System.out.println("add....");
}
}
MyBook.java:
package h.l.pojo6;
public class MyBook {
public void before1() {
System.out.println("前置增强...");
}
}
相关配置文件:
<?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 definitions here -->
<!-- 配置对象 -->
<bean id="book" class="h.l.pojo6.Book"></bean>
<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
<!-- 配置aop操作 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* h.l.pojo6.Book.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:aspect ref="myBook">
<aop:before method="before1" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
</beans>
BookTest.java测试类:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class BookTest {
ApplicationContext context;
@BeforeEach
void setUp() throws Exception {
context = new ClassPathXmlApplicationContext(
"springConfig/ApplicationContext.xml");
}
@Test
void test() {
Book book = (Book) context.getBean("book");
book.add();
}
}
测试结果:
前置增强...
add....
上述就是aop(面向切面编程)的一个实例。关于Aspects框架使用表达式配置切入点:下面再进行些总结:
模板:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
常用表达式:
(1)execution(* h.l.pojo6.Book.add(..)):表示切入点是h.l.pojo6包下的Book类中的add方法
(2)execution(* h.l.pojo6.Book.*(..)):表示切入点是h.l.pojo6包下的Book类中的所有方法
(3)execution(* *.*(..)):表示切入点是所有包下的所有类中的所有方法
(4)execution(* save*(..)):表示切入点是save开头的所有方法
上面是AOP的xml方式开发,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 definitions here -->
<!-- 配置对象 -->
<bean id="book" class="h.l.pojo6.Book"></bean>
<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
<!-- 开启aop操作 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Book.java:
package h.l.pojo6;
public class Book {
public void add() {
System.out.println("add....");
}
}
MyBook.java:
package h.l.pojo6;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyBook {
@Before(value="execution(* h.l.pojo6.Book.*(..))")
public void before1() {
System.out.println("before1...");
}
}
测试结果:
before1...
add....
以上仅是部分Spring知识总结,其余部分将在下一篇文章进行总结。
注:以上文章仅是个人学习过程总结,若有不当之处,望不吝赐教